我应该如何处理Objective-C库中的日志?

时间:2016-01-11 23:07:33

标签: objective-c logging

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

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

1 个答案:

答案 0 :(得分: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);

注意log 消息是一个返回字符串而不是字符串的块。这样,如果定义的日志处理程序决定不评估消息(例如,基于上面的默认日志处理程序中的日志级别),则可以避免昂贵的计算。这使您可以编写具有可能代价高昂的日志消息的单线日志,以便在丢弃日志时不会影响性能,例如:

MYLibraryLogDebug(@"Object: %@", ^{ return object.debugDescription; }());