2016-01-11 4 views
9

Ich schreibe eine Objective-C-Bibliothek und an einigen Stellen möchte ich einige Informationen protokollieren. Die Verwendung von NSLog ist nicht ideal, da es nicht konfigurierbar ist und weder Level-Unterstützung noch Tag-Unterstützung bietet. CocoaLumberjack und NSLogger sind beide beliebte Logging-Bibliotheken, die Ebenen und Kontexte/Tags unterstützen, aber ich würde es vorziehen, nicht von einer Protokollierungs-Bibliothek eines Dritten abhängig zu sein.Wie sollte ich mit Protokollen in einer Objective-C-Bibliothek umgehen?

Wie kann ich Protokolle auf eine konfigurierbare Weise erstellen, die meinen Benutzern keine bestimmte Protokollbibliothek auferlegt?

Antwort

19

TL; DR Einen Log-Handler in Ihrer API blockieren.

Hier ist ein Vorschlag, Logging sehr einfach mit einer Logger-Klasse als Teil Ihrer öffentlichen API konfigurierbar zu machen. Nennen wir es 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 

Diese Klasse hat eine einzige Methode, die Client ein Protokoll-Handler-Block registrieren können. Dies macht es für einen Client trivial, die Protokollierung mit seiner bevorzugten Bibliothek zu implementieren. Hier ist, wie ein Kunde mit NSLogger verwenden würde:

[MYLibraryLogger setLogHandler:^(NSString * (^message)(void), MYLogLevel level, const char *file, const char *function, NSUInteger line) { 
    LogMessageRawF(file, (int)line, function, @"MYLibrary", (int)level, message()); 
}]; 

oder mit 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]; 
}]; 

Hier ist eine Implementierung von MYLibraryLogger mit einem Standard-Protokollhandler, der nur Fehler und Warnungen protokolliert:

// 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 

Das letzte fehlende Teil für diese Lösung ist eine Reihe von Makros, die Sie in Ihrer Bibliothek verwenden können.

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

dann so Sie es nur in Ihrer Bibliothek verwenden:

MYLibraryLogError(@"Operation finished with error: %@", error); 

Beachten Sie, wie das Protokoll Nachricht ein Block ist eine Zeichenfolge, anstatt nur einen String zurück. Auf diese Weise können Sie möglicherweise kostspielige Berechnungen vermeiden, wenn der definierte Protokollhandler sich entscheidet, die Nachricht nicht auszuwerten (z. B. basierend auf der Protokollebene wie im Standardprotokollhandler oben). Auf diese Weise können Sie One-Liner-Protokolle mit potenziell kostspieligen Protokollmeldungen schreiben, um ohne Leistungseinbußen zu rechnen, wenn das Protokoll verworfen wird, z. B .:

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

Möglicherweise möchten Sie den Threading-Vertrag für die Rückrufe dokumentieren. – Dad

+0

Der Protokollhandler wird einfach für den Thread aufgerufen, der das Protokoll ausgegeben hat. Beide [CocoaLumberjack] (https://github.com/CocoaLumberjack/CocoaLumberjack/blob/f57de5cb54c9334e0685c7de2f33f1da5658d2f8/Classes/DDLog.m#L963) und [NSLogger] (https://github.com/fpillet/NSLogger/blob/2259236c4ba1f5070c58f31797888fe96abcc492/ Client% 20Logger/iOS/LoggerClient.m # L2123) Verwenden Sie die * aktuelle * Queue/den aktuellen Thread, um den Namen zu erhalten. – 0xced

+0

Dies ist, was Apple tut in [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) (Siehe 'customHTTPProtocol: Protokoll logWithFormat: arguments:') –

Verwandte Themen