CocoaLumberjack log level笔记

524 阅读4分钟

深入理解代替单纯记忆

本文对CocoaLumberjack(3.5.3版本)中log level(日志级别)能力进行学习和记录

关于什么是log level,log leve有什么作用,本文不做说明,网上可以找到很多资料

CocoaLumberjack官方对log leve有这样的描述:

Configure your logging however you want. Change log levels per file (perfect for debugging). Change log levels per logger (verbose console, but concise log file). Change log levels per xcode configuration (verbose debug, but concise release). Have your log statements compiled out of the release build. Customize the number of log levels for your application.

总结一下CocoaLumberjack的log level能做哪些事:

  • 可以为每个文件设置独立的log level
  • 可以每个logger设置独立的log level
  • 可以为不同的Xcode configuratioin设置不同的log level
  • 可以自定义log level数量

LogLevel工作原理

CocoaLumberjack内部使用两个类型(DDLogFlag和DDLogLevel)来控制哪些日志可以输出,哪些需要被过滤掉

typedef NS_OPTIONS(NSUInteger, DDLogFlag){
    /**
     *  0...00001 DDLogFlagError
     */
    DDLogFlagError      = (1 << 0),
    
    /**
     *  0...00010 DDLogFlagWarning
     */
    DDLogFlagWarning    = (1 << 1),
    
    /**
     *  0...00100 DDLogFlagInfo
     */
    DDLogFlagInfo       = (1 << 2),
    
    /**
     *  0...01000 DDLogFlagDebug
     */
    DDLogFlagDebug      = (1 << 3),
    
    /**
     *  0...10000 DDLogFlagVerbose
     */
    DDLogFlagVerbose    = (1 << 4)
};
typedef NS_ENUM(NSUInteger, DDLogLevel){
    /**
     *  No logs
     */
    DDLogLevelOff       = 0,
    
    /**
     *  Error logs only
     */
    DDLogLevelError     = (DDLogFlagError),
    
    /**
     *  Error and warning logs
     */
    DDLogLevelWarning   = (DDLogLevelError   | DDLogFlagWarning),
    
    /**
     *  Error, warning and info logs
     */
    DDLogLevelInfo      = (DDLogLevelWarning | DDLogFlagInfo),
    
    /**
     *  Error, warning, info and debug logs
     */
    DDLogLevelDebug     = (DDLogLevelInfo    | DDLogFlagDebug),
    
    /**
     *  Error, warning, info, debug and verbose logs
     */
    DDLogLevelVerbose   = (DDLogLevelDebug   | DDLogFlagVerbose),
    
    /**
     *  All logs (1...11111)
     */
    DDLogLevelAll       = NSUIntegerMax
}

那么满足什么样条件的日志会被放行即输出到日志文件或控制台呢?

只要LogLevel & LogFlag判断结果为true,就放行

因为涉及到与和位移运算,使用表格看起来更容易理解

DDLogFlag二进制表示十进制DDLogLevel二进制表示十进制
DDLogFlagError00011DDLogLevelOff00
DDLogFlagWarning00102DDLogLevelError00011
DDLogFlagInfo01004DDLogLevelWarning00113
DDLogFlagDebug10008DDLogLevelInfo01117
DDLogFlagVerbose1000016DDLogLevelDebug111115
DDLogLevelVerbose1111131
DDLogLevelAllNSUIntegerMax很大

随便举几个例子:

  • 我们使用DDLogInfo宏打印日志,对应着LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagInfo, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)

    • 假设此时我们设置LOG_LEVEL_DEF = DDLogLevelDebug
    • LogLevel & LogFlagDDLogLevelDebug & DDLogFlagInfo,等价于0100 & 1111,结果是0100,判断结果为true,放行!
    • 通俗的解释就是,我们将logleve设置为debug级别,所以DDLogInfo的日志肯定会输出
  • 同样使用DDLogInfo,假设我们设置LOG_LEVEL_DEF = DDLogLevelError

    • LogLevel & LogFlagDDLogLevelError & DDLogFlagInfo,等价于0100 & 1111,结果是0000,判断结果为false,不放行!
    • 通俗的解释就是,我们将logleve设置为error级别,所以使用DDLogInfo输出日志时最终不会输出

设置全局LogLevel

Objective C

CocoaLumberjack定义了LOG_LEVEL_DEF宏,取值为ddLogLevel

#ifndef LOG_LEVEL_DEF
    #define LOG_LEVEL_DEF ddLogLevel
#endif

然后在执行写日志方法处使用了LOG_LEVEL_DEF

#define DDLogDebug(frmt, ...)   LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagDebug,   0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)
#define DDLogVerbose(frmt, ...) LOG_MAYBE(LOG_ASYNC_ENABLED, LOG_LEVEL_DEF, DDLogFlagVerbose, 0, nil, __PRETTY_FUNCTION__, frmt, ##__VA_ARGS__)

所以,将ddLogLevel当做一个全局变量时,就实现了全局LogLevel能力

使用方法如下:

DDLogLevel ddLogLevel;
ddLogLevel = DDLogLevelVerbose;
DDLogError(@"%@: Error", THIS_FILE);

ddLogLevel = DDLogLevelInfo;
DDLogInfo(@"log flag info : %ld", DDLogFlagInfo);

Swift

由于Swift不支持C语言中的宏定义,所以全局LogLevel有所不同

通过查看CocoaLumberjack.swift中的日志上报逻辑,发现log leve是从DDDefaultLogLevel取的,这是一个全局静态常量

public func DDLogInfo(_ message: @autoclosure () -> String,
                      level: DDLogLevel = DDDefaultLogLevel,
                      context: Int = 0,
                      file: StaticString = #file,
                      function: StaticString = #function,
                      line: UInt = #line,
                      tag: Any? = nil,
                      asynchronous async: Bool = asyncLoggingEnabled,
                      ddlog: DDLog = .sharedInstance) {
    _DDLogMessage(message(), level: level, flag: .info, context: context, file: file, function: function, line: line, tag: tag, asynchronous: async, ddlog: ddlog)
}
#ifndef DD_LOG_LEVEL
// #warning 'DD_LOG_LEVEL' is not defined. Using 'DDLogLevelAll' as default. Consider defining it yourself.
#define DD_LOG_LEVEL DDLogLevelAll
#endif

static const DDLogLevel DDDefaultLogLevel = DD_LOG_LEVEL;

不过,需要注意的是,由于DDDefaultLogLevel是常量,无法动态修改,所以已经不再建议使用了,改为建议使用dynamicLogLevel,且默认值是DDLogLevelAll,具体使用方法如下

public var dynamicLogLevel = DDLogLevel.all

dynamicLogLevel = .info
DDLogInfo("123")

不同文件设置不同LogLevel

Objective C

// File1.m
static DDLogLevel ddLogLevel = DDLogLevelInfo;
@implementation  File1
- (void)method {
    DDLogInfo(@"xxxx");
}
@end

// File2.m
static DDLogLevel ddLogLevel = DDLogLevelError;
@implementation  File2
- (void)method {
    DDLogInfo(@"xxxx");
}
@end

Swift

Swift中稍微麻烦点

// File1.swift
let file1LogLevel: DDLogLevel = .debug
class File1: NSObject {
    func log() {
        DDLogInfo("123", level:file1LogLevel)
    }
}

// File2.swift
let file2LogLevel: DDLogLevel = .warning
class File2: NSObject {
    func log() {
        DDLogInfo("123", level:file2LogLevel)
    }
}

不同Xcode configuratioin设置不同LogLevel

Objective C

#ifdef DEBUG
    static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
    static const DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif

Swift

Swift下类似

注意,以下代码需要设置Swift other flag

#if DEBUG
dynamicLogLevel = .verbose
#else
dynamicLogLevel = .error
#endif
DDLogError("error 123")

不同Logger设置不同的LogLevel

Objective C和Swift的API是一致的

// OC
[DDLog addLogger:logger withLevel:DDLogLevelError];
// Swift
DDLog.add(logger, with: .error)

自定义LogLevel

自定义loglevel时,可以参考官方的LogLevel和LogFlag增加、或重写自己的loglevel和logflag,最终将参数传入对应的log方法即可。可以参考Demo中的CustomLogLevels工程