我正在编写一个Objective-C库,在某些地方我想记录一些信息.使用NSLog
并不理想,因为它不可配置,既没有级别支持也没有标记支持.CocoaLumberjack和NSLogger都是流行的日志库,支持级别和上下文/标签,但我不想依赖第三方日志库.
如何以可配置的方式生成日志,而不会强制我的用户使用特定的日志记录库?
TL; DR 在API中公开日志处理程序块.
这是一个建议,使用记录器类作为公共API的一部分,可以非常轻松地配置日志记录.我们称之为MYLibraryLogger
:
// MYLibraryLogger.h #importtypedef NS_ENUM(NSUInteger, MYLogLevel) { MYLogLevelError = 0, MYLogLevelWarning = 1, MYLogLevelInfo = 2, MYLogLevelDebug = 3, MYLogLevelVerbose = 4, }; @interface MYLibraryLogger : NSObject + (void) setLogHandler:(void (^)(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line))logHandler; @end
该类有一个允许客户端注册日志处理程序块的方法.这使得客户端使用他们喜欢的库实现日志记录变得微不足道.以下是客户端如何将其与NSLogger一起使用:
[MYLibraryLogger setLogHandler:^(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line) { LogMessageRawF(file, (int)line, function, @"MYLibrary", (int)level, message()); }];
或者使用CocoaLumberjack:
[MYLibraryLogger setLogHandler:^(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line) { // The `MYLogLevel` enum matches the `DDLogFlag` options from DDLog.h when shifted [DDLog log:YES message:message() level:ddLogLevel flag:(1 << level) context:MYLibraryLumberjackContext file:file function:function line:line tag:nil]; }];
以下是MYLibraryLogger
使用默认日志处理程序的实现,该处理程序仅记录错误和警告:
// MYLibraryLogger.m #import "MYLibraryLogger.h" static void (^LogHandler)(NSString * (^)(void), MYLogLevel, const char *, const char *, NSUInteger) = ^(NSString *(^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line) { if (level == MYLogLevelError || level == MYLogLevelWarning) NSLog(@"[MYLibrary] %@", message()); }; @implementation MYLibraryLogger + (void) setLogHandler:(void (^)(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line))logHandler { LogHandler = logHandler; } + (void) logMessage:(NSString * (^)(void))message level:(MYLogLevel)level file:(const char *)file function:(const char *)function line:(NSUInteger)line { if (LogHandler) LogHandler(message, level, file, function, line); } @end
此解决方案最后一个缺失的部分是一组宏,供您通过库使用.
// MYLibraryLogger+Private.h #import#import "MYLibraryLogger.h" @interface MYLibraryLogger () + (void) logMessage:(NSString * (^)(void))message level:(MYLogLevel)level file:(const char *)file function:(const char *)function line:(NSUInteger)line; @end #define MYLibraryLog(_level, _message) [MYLibraryLogger logMessage:(_message) level:(_level) file:__FILE__ function:__PRETTY_FUNCTION__ line:__LINE__] #define MYLibraryLogError(format, ...) MYLibraryLog(MYLogLevelError, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) #define MYLibraryLogWarning(format, ...) MYLibraryLog(MYLogLevelWarning, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) #define MYLibraryLogInfo(format, ...) MYLibraryLog(MYLogLevelInfo, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) #define MYLibraryLogDebug(format, ...) MYLibraryLog(MYLogLevelDebug, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; })) #define MYLibraryLogVerbose(format, ...) MYLibraryLog(MYLogLevelVerbose, (^{ return [NSString stringWithFormat:(format), ##__VA_ARGS__]; }))
然后你只需在你的库中使用它:
MYLibraryLogError(@"Operation finished with error: %@", error);
注意日志消息是一个块返回一个字符串而不是一个字符串.这样,如果定义的日志处理程序决定不评估消息,则可以避免昂贵的计算(例如,基于上面的默认日志处理程序中的日志级别).这使您可以编写带有可能代价高昂的日志消息的单线日志,以便在丢弃日志时不会出现性能损失,例如:
MYLibraryLogDebug(@"Object: %@", ^{ return object.debugDescription; }());