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; }());
Möglicherweise möchten Sie den Threading-Vertrag für die Rückrufe dokumentieren. – Dad
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
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:') –