| #import "DDDispatchQueueLogFormatter.h" |
| #import <libkern/OSAtomic.h> |
| |
| /** |
| * Welcome to Cocoa Lumberjack! |
| * |
| * The project page has a wealth of documentation if you have any questions. |
| * https://github.com/CocoaLumberjack/CocoaLumberjack |
| * |
| * If you're new to the project you may wish to read the "Getting Started" wiki. |
| * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted |
| **/ |
| |
| #if ! __has_feature(objc_arc) |
| #error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). |
| #endif |
| |
| |
| @implementation DDDispatchQueueLogFormatter |
| { |
| int32_t atomicLoggerCount; |
| NSDateFormatter *threadUnsafeDateFormatter; // Use [self stringFromDate] |
| |
| OSSpinLock lock; |
| |
| NSUInteger _minQueueLength; // _prefix == Only access via atomic property |
| NSUInteger _maxQueueLength; // _prefix == Only access via atomic property |
| NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock |
| } |
| |
| - (id)init |
| { |
| if ((self = [super init])) |
| { |
| dateFormatString = @"yyyy-MM-dd HH:mm:ss:SSS"; |
| |
| atomicLoggerCount = 0; |
| threadUnsafeDateFormatter = nil; |
| |
| _minQueueLength = 0; |
| _maxQueueLength = 0; |
| _replacements = [[NSMutableDictionary alloc] init]; |
| |
| // Set default replacements: |
| |
| _replacements[@"com.apple.main-thread"] = @"main"; |
| } |
| return self; |
| } |
| |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark Configuration |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| @synthesize minQueueLength = _minQueueLength; |
| @synthesize maxQueueLength = _maxQueueLength; |
| |
| - (NSString *)replacementStringForQueueLabel:(NSString *)longLabel |
| { |
| NSString *result = nil; |
| |
| OSSpinLockLock(&lock); |
| { |
| result = _replacements[longLabel]; |
| } |
| OSSpinLockUnlock(&lock); |
| |
| return result; |
| } |
| |
| - (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel |
| { |
| OSSpinLockLock(&lock); |
| { |
| if (shortLabel) |
| _replacements[longLabel] = shortLabel; |
| else |
| [_replacements removeObjectForKey:longLabel]; |
| } |
| OSSpinLockUnlock(&lock); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark DDLogFormatter |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| - (NSString *)stringFromDate:(NSDate *)date |
| { |
| int32_t loggerCount = OSAtomicAdd32(0, &atomicLoggerCount); |
| |
| NSString *calendarIdentifier = nil; |
| #if defined(__IPHONE_8_0) || defined(__MAC_10_10) |
| calendarIdentifier = NSCalendarIdentifierGregorian; |
| #else |
| calendarIdentifier = NSGregorianCalendar; |
| #endif |
| |
| if (loggerCount <= 1) |
| { |
| // Single-threaded mode. |
| |
| if (threadUnsafeDateFormatter == nil) |
| { |
| threadUnsafeDateFormatter = [[NSDateFormatter alloc] init]; |
| [threadUnsafeDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; |
| [threadUnsafeDateFormatter setDateFormat:dateFormatString]; |
| } |
| |
| [threadUnsafeDateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier]]; |
| return [threadUnsafeDateFormatter stringFromDate:date]; |
| } |
| else |
| { |
| // Multi-threaded mode. |
| // NSDateFormatter is NOT thread-safe. |
| |
| NSString *key = @"DispatchQueueLogFormatter_NSDateFormatter"; |
| |
| NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary]; |
| NSDateFormatter *dateFormatter = threadDictionary[key]; |
| |
| if (dateFormatter == nil) |
| { |
| dateFormatter = [[NSDateFormatter alloc] init]; |
| [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; |
| [dateFormatter setDateFormat:dateFormatString]; |
| |
| threadDictionary[key] = dateFormatter; |
| } |
| |
| [dateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier]]; |
| return [dateFormatter stringFromDate:date]; |
| } |
| } |
| |
| - (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage |
| { |
| // As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue |
| |
| NSUInteger minQueueLength = self.minQueueLength; |
| NSUInteger maxQueueLength = self.maxQueueLength; |
| |
| // Get the name of the queue, thread, or machID (whichever we are to use). |
| |
| NSString *queueThreadLabel = nil; |
| |
| BOOL useQueueLabel = YES; |
| BOOL useThreadName = NO; |
| |
| if (logMessage->queueLabel) |
| { |
| // If you manually create a thread, it's dispatch_queue will have one of the thread names below. |
| // Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID. |
| |
| char *names[] = { "com.apple.root.low-priority", |
| "com.apple.root.default-priority", |
| "com.apple.root.high-priority", |
| "com.apple.root.low-overcommit-priority", |
| "com.apple.root.default-overcommit-priority", |
| "com.apple.root.high-overcommit-priority" }; |
| |
| int length = sizeof(names) / sizeof(char *); |
| |
| int i; |
| for (i = 0; i < length; i++) |
| { |
| if (strcmp(logMessage->queueLabel, names[i]) == 0) |
| { |
| useQueueLabel = NO; |
| useThreadName = [logMessage->threadName length] > 0; |
| break; |
| } |
| } |
| } |
| else |
| { |
| useQueueLabel = NO; |
| useThreadName = [logMessage->threadName length] > 0; |
| } |
| |
| if (useQueueLabel || useThreadName) |
| { |
| NSString *fullLabel; |
| NSString *abrvLabel; |
| |
| if (useQueueLabel) |
| fullLabel = @(logMessage->queueLabel); |
| else |
| fullLabel = logMessage->threadName; |
| |
| OSSpinLockLock(&lock); |
| { |
| abrvLabel = _replacements[fullLabel]; |
| } |
| OSSpinLockUnlock(&lock); |
| |
| if (abrvLabel) |
| queueThreadLabel = abrvLabel; |
| else |
| queueThreadLabel = fullLabel; |
| } |
| else |
| { |
| queueThreadLabel = [NSString stringWithFormat:@"%x", logMessage->machThreadID]; |
| } |
| |
| // Now use the thread label in the output |
| |
| NSUInteger labelLength = [queueThreadLabel length]; |
| |
| // labelLength > maxQueueLength : truncate |
| // labelLength < minQueueLength : padding |
| // : exact |
| |
| if ((maxQueueLength > 0) && (labelLength > maxQueueLength)) |
| { |
| // Truncate |
| |
| return [queueThreadLabel substringToIndex:maxQueueLength]; |
| } |
| else if (labelLength < minQueueLength) |
| { |
| // Padding |
| |
| NSUInteger numSpaces = minQueueLength - labelLength; |
| |
| char spaces[numSpaces + 1]; |
| memset(spaces, ' ', numSpaces); |
| spaces[numSpaces] = '\0'; |
| |
| return [NSString stringWithFormat:@"%@%s", queueThreadLabel, spaces]; |
| } |
| else |
| { |
| // Exact |
| |
| return queueThreadLabel; |
| } |
| } |
| |
| - (NSString *)formatLogMessage:(DDLogMessage *)logMessage |
| { |
| NSString *timestamp = [self stringFromDate:(logMessage->timestamp)]; |
| NSString *queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage]; |
| |
| return [NSString stringWithFormat:@"%@ [%@] %@", timestamp, queueThreadLabel, logMessage->logMsg]; |
| } |
| |
| - (void)didAddToLogger:(id <DDLogger>)logger |
| { |
| OSAtomicIncrement32(&atomicLoggerCount); |
| } |
| |
| - (void)willRemoveFromLogger:(id <DDLogger>)logger |
| { |
| OSAtomicDecrement32(&atomicLoggerCount); |
| } |
| |
| @end |