2016-01-11 36 views
9

我在写一个Objective-C库,并且在某些地方我想记录一些信息。使用NSLog并不理想,因为它不可配置,既没有级别支持也没有标签支持。 CocoaLumberjack和NSLogger都是支持级别和上下文/标签的流行日志库,但我不想依赖第三方日志库。我应该如何处理Objective-C库中的日志?

如何以可配置的方式生成日志,而不会强制特定的日志记录库存在我的用户上?

回答

19

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()); 
}]; 

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 <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; }()); 
+0

可能要记录回调的线程合约。 – Dad

+0

日志处理程序仅在日志发出的线程上调用。 [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

+0

这是苹果确实在[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:') –

相关问题