我在写一个Objective-C库,并且在某些地方我想记录一些信息。使用NSLog
并不理想,因为它不可配置,既没有级别支持也没有标签支持。 CocoaLumberjack和NSLogger都是支持级别和上下文/标签的流行日志库,但我不想依赖第三方日志库。我应该如何处理Objective-C库中的日志?
如何以可配置的方式生成日志,而不会强制特定的日志记录库存在我的用户上?
我在写一个Objective-C库,并且在某些地方我想记录一些信息。使用NSLog
并不理想,因为它不可配置,既没有级别支持也没有标签支持。 CocoaLumberjack和NSLogger都是支持级别和上下文/标签的流行日志库,但我不想依赖第三方日志库。我应该如何处理Objective-C库中的日志?
如何以可配置的方式生成日志,而不会强制特定的日志记录库存在我的用户上?
TL; DR在您的API中公开日志处理程序块。
这是一个建议,使日志记录类作为公共API的一部分很容易配置。让我们把它叫做MYLibraryLogger
:
// MYLibraryLogger.h
#import <Foundation/Foundation.h>
typedef 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());
}];
[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 <Foundation/Foundation.h>
#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; }());
可能要记录回调的线程合约。 – Dad
日志处理程序仅在日志发出的线程上调用。 [CocoaLumberjack](https://github.com/CocoaLumberjack/CocoaLumberjack/blob/f57de5cb54c9334e0685c7de2f33f1da5658d2f8/Classes/DDLog.m#L963)和[NSLogger](https://github.com/fpillet/NSLogger/blob/2259236c4ba1f5070c58f31797888fe96abcc492/客户端%20Logger/iOS/LoggerClient.m#L2123)使用* current *队列/线程获取其名称。 – 0xced
这是苹果确实在[CustomHTTPProtocol(https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Listings/CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_h.html#//apple_ref/doc/uid/DTS40013653-CustomHTTPProtocol_Core_Code_CustomHTTPProtocol_h-DontLinkElementID_9)(请参阅'customHTTPProtocol:protocol logWithFormat:arguments:') –