vendor/Pods/CocoaLumberjack/Lumberjack/DDLog.m in motion-yapper-0.0.1 vs vendor/Pods/CocoaLumberjack/Lumberjack/DDLog.m in motion-yapper-0.0.2

- old
+ new

@@ -3,20 +3,23 @@ #import <pthread.h> #import <objc/runtime.h> #import <mach/mach_host.h> #import <mach/host_info.h> #import <libkern/OSAtomic.h> +#import <Availability.h> +#if TARGET_OS_IPHONE + #import <UIKit/UIDevice.h> +#endif - /** * Welcome to Cocoa Lumberjack! * * The project page has a wealth of documentation if you have any questions. - * https://github.com/robbiehanson/CocoaLumberjack + * https://github.com/CocoaLumberjack/CocoaLumberjack * * If you're new to the project you may wish to read the "Getting Started" wiki. - * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted + * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted * **/ #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). @@ -55,24 +58,28 @@ static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey; @interface DDLoggerNode : NSObject { @public - id <DDLogger> logger; - dispatch_queue_t loggerQueue; + id <DDLogger> logger; + dispatch_queue_t loggerQueue; + int logLevel; } -+ (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue; +@property (nonatomic, assign, readonly) int logLevel; ++ (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue logLevel:(int)logLevel; + @end @interface DDLog (PrivateAPI) -+ (void)lt_addLogger:(id <DDLogger>)logger; ++ (void)lt_addLogger:(id <DDLogger>)logger logLevel:(int)logLevel; + (void)lt_removeLogger:(id <DDLogger>)logger; + (void)lt_removeAllLoggers; ++ (NSArray *)lt_allLoggers; + (void)lt_log:(DDLogMessage *)logMessage; + (void)lt_flush; @end @@ -108,164 +115,195 @@ * * This method may also be called directly (assumably by accident), hence the safety mechanism. **/ + (void)initialize { - static BOOL initialized = NO; - if (!initialized) - { - initialized = YES; - - loggers = [[NSMutableArray alloc] initWithCapacity:4]; - - NSLogDebug(@"DDLog: Using grand central dispatch"); - - loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL); - loggingGroup = dispatch_group_create(); - - void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null - dispatch_queue_set_specific(loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL); - - queueSemaphore = dispatch_semaphore_create(LOG_MAX_QUEUE_SIZE); - - // Figure out how many processors are available. - // This may be used later for an optimization on uniprocessor machines. - - host_basic_info_data_t hostInfo; - mach_msg_type_number_t infoCount; - - infoCount = HOST_BASIC_INFO_COUNT; - host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount); - - unsigned int result = (unsigned int)(hostInfo.max_cpus); - unsigned int one = (unsigned int)(1); - - numProcessors = MAX(result, one); - - NSLogDebug(@"DDLog: numProcessors = %u", numProcessors); - - - #if TARGET_OS_IPHONE - NSString *notificationName = @"UIApplicationWillTerminateNotification"; - #else - NSString *notificationName = @"NSApplicationWillTerminateNotification"; - #endif - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationWillTerminate:) - name:notificationName - object:nil]; - } + static BOOL initialized = NO; + if (!initialized) + { + initialized = YES; + + loggers = [[NSMutableArray alloc] initWithCapacity:4]; + + NSLogDebug(@"DDLog: Using grand central dispatch"); + + loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL); + loggingGroup = dispatch_group_create(); + + void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null + dispatch_queue_set_specific(loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL); + + queueSemaphore = dispatch_semaphore_create(LOG_MAX_QUEUE_SIZE); + + // Figure out how many processors are available. + // This may be used later for an optimization on uniprocessor machines. + + host_basic_info_data_t hostInfo; + mach_msg_type_number_t infoCount; + + infoCount = HOST_BASIC_INFO_COUNT; + host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount); + + unsigned int result = (unsigned int)(hostInfo.max_cpus); + unsigned int one = (unsigned int)(1); + + numProcessors = MAX(result, one); + + NSLogDebug(@"DDLog: numProcessors = %u", numProcessors); + + + #if TARGET_OS_IPHONE + NSString *notificationName = @"UIApplicationWillTerminateNotification"; + #else + NSString *notificationName = nil; + + if (NSClassFromString(@"NSApplication")) + { + notificationName = @"NSApplicationWillTerminateNotification"; + } + else + { + // If there is no NSApp -> we are running Command Line Tool app. + // In this case terminate notification wouldn't be fired, so we use workaround. + atexit_b(^{ + [self applicationWillTerminate:nil]; + }); + } + #endif + + if (notificationName) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillTerminate:) + name:notificationName + object:nil]; + } + } } /** * Provides access to the logging queue. **/ + (dispatch_queue_t)loggingQueue { - return loggingQueue; + return loggingQueue; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Notifications //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + (void)applicationWillTerminate:(NSNotification *)notification { - [self flushLog]; + [self flushLog]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Logger Management //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + (void)addLogger:(id <DDLogger>)logger { - if (logger == nil) return; - - dispatch_async(loggingQueue, ^{ @autoreleasepool { - - [self lt_addLogger:logger]; - }}); + [self addLogger:logger withLogLevel:LOG_LEVEL_VERBOSE]; } ++ (void)addLogger:(id <DDLogger>)logger withLogLevel:(int)logLevel +{ + if (logger == nil) return; + + dispatch_async(loggingQueue, ^{ @autoreleasepool { + + [self lt_addLogger:logger logLevel:logLevel]; + }}); +} + + (void)removeLogger:(id <DDLogger>)logger { - if (logger == nil) return; - - dispatch_async(loggingQueue, ^{ @autoreleasepool { - - [self lt_removeLogger:logger]; - }}); + if (logger == nil) return; + + dispatch_async(loggingQueue, ^{ @autoreleasepool { + + [self lt_removeLogger:logger]; + }}); } + (void)removeAllLoggers { - dispatch_async(loggingQueue, ^{ @autoreleasepool { - - [self lt_removeAllLoggers]; - }}); + dispatch_async(loggingQueue, ^{ @autoreleasepool { + + [self lt_removeAllLoggers]; + }}); } ++ (NSArray *)allLoggers +{ + __block NSArray *theLoggers; + + dispatch_sync(loggingQueue, ^{ @autoreleasepool { + theLoggers = [self lt_allLoggers]; + }}); + + return theLoggers; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Master Logging //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag { - // We have a tricky situation here... - // - // In the common case, when the queueSize is below the maximumQueueSize, - // we want to simply enqueue the logMessage. And we want to do this as fast as possible, - // which means we don't want to block and we don't want to use any locks. - // - // However, if the queueSize gets too big, we want to block. - // But we have very strict requirements as to when we block, and how long we block. - // - // The following example should help illustrate our requirements: - // - // Imagine that the maximum queue size is configured to be 5, - // and that there are already 5 log messages queued. - // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed) - // - // Now if our thread issues a log statement (let us call the log message F), - // it should block before the message is added to the queue. - // Furthermore, it should be unblocked immediately after A has been unqueued. - // - // The requirements are strict in this manner so that we block only as long as necessary, - // and so that blocked threads are unblocked in the order in which they were blocked. - // - // Returning to our previous example, let us assume that log messages A through E are still queued. - // Our aforementioned thread is blocked attempting to queue log message F. - // Now assume we have another separate thread that attempts to issue log message G. - // It should block until log messages A and B have been unqueued. - - - // We are using a counting semaphore provided by GCD. - // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value. - // Everytime we want to queue a log message we decrement this value. - // If the resulting value is less than zero, - // the semaphore function waits in FIFO order for a signal to occur before returning. - // - // A dispatch semaphore is an efficient implementation of a traditional counting semaphore. - // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. - // If the calling semaphore does not need to block, no kernel call is made. - - dispatch_semaphore_wait(queueSemaphore, DISPATCH_TIME_FOREVER); - - // We've now sure we won't overflow the queue. - // It is time to queue our log message. - - dispatch_block_t logBlock = ^{ @autoreleasepool { - - [self lt_log:logMessage]; - }}; - - if (asyncFlag) - dispatch_async(loggingQueue, logBlock); - else - dispatch_sync(loggingQueue, logBlock); + // We have a tricky situation here... + // + // In the common case, when the queueSize is below the maximumQueueSize, + // we want to simply enqueue the logMessage. And we want to do this as fast as possible, + // which means we don't want to block and we don't want to use any locks. + // + // However, if the queueSize gets too big, we want to block. + // But we have very strict requirements as to when we block, and how long we block. + // + // The following example should help illustrate our requirements: + // + // Imagine that the maximum queue size is configured to be 5, + // and that there are already 5 log messages queued. + // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed) + // + // Now if our thread issues a log statement (let us call the log message F), + // it should block before the message is added to the queue. + // Furthermore, it should be unblocked immediately after A has been unqueued. + // + // The requirements are strict in this manner so that we block only as long as necessary, + // and so that blocked threads are unblocked in the order in which they were blocked. + // + // Returning to our previous example, let us assume that log messages A through E are still queued. + // Our aforementioned thread is blocked attempting to queue log message F. + // Now assume we have another separate thread that attempts to issue log message G. + // It should block until log messages A and B have been unqueued. + + + // We are using a counting semaphore provided by GCD. + // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value. + // Everytime we want to queue a log message we decrement this value. + // If the resulting value is less than zero, + // the semaphore function waits in FIFO order for a signal to occur before returning. + // + // A dispatch semaphore is an efficient implementation of a traditional counting semaphore. + // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. + // If the calling semaphore does not need to block, no kernel call is made. + + dispatch_semaphore_wait(queueSemaphore, DISPATCH_TIME_FOREVER); + + // We've now sure we won't overflow the queue. + // It is time to queue our log message. + + dispatch_block_t logBlock = ^{ @autoreleasepool { + + [self lt_log:logMessage]; + }}; + + if (asyncFlag) + dispatch_async(loggingQueue, logBlock); + else + dispatch_sync(loggingQueue, logBlock); } + (void)log:(BOOL)asynchronous level:(int)level flag:(int)flag @@ -274,30 +312,30 @@ function:(const char *)function line:(int)line tag:(id)tag format:(NSString *)format, ... { - va_list args; - if (format) - { - va_start(args, format); - - NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args]; - DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg - level:level - flag:flag - context:context - file:file - function:function - line:line - tag:tag - options:0]; - - [self queueLogMessage:logMessage asynchronously:asynchronous]; - - va_end(args); - } + va_list args; + if (format) + { + va_start(args, format); + + NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args]; + DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:0]; + + [self queueLogMessage:logMessage asynchronously:asynchronous]; + + va_end(args); + } } + (void)log:(BOOL)asynchronous level:(int)level flag:(int)flag @@ -307,499 +345,528 @@ line:(int)line tag:(id)tag format:(NSString *)format args:(va_list)args { - if (format) - { - NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args]; - DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg - level:level - flag:flag - context:context - file:file - function:function - line:line - tag:tag - options:0]; - - [self queueLogMessage:logMessage asynchronously:asynchronous]; - } + if (format) + { + NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args]; + DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg + level:level + flag:flag + context:context + file:file + function:function + line:line + tag:tag + options:0]; + + [self queueLogMessage:logMessage asynchronously:asynchronous]; + } } + (void)flushLog { - dispatch_sync(loggingQueue, ^{ @autoreleasepool { - - [self lt_flush]; - }}); + dispatch_sync(loggingQueue, ^{ @autoreleasepool { + + [self lt_flush]; + }}); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Registered Dynamic Logging //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + (BOOL)isRegisteredClass:(Class)class { - SEL getterSel = @selector(ddLogLevel); - SEL setterSel = @selector(ddSetLogLevel:); - + SEL getterSel = @selector(ddLogLevel); + SEL setterSel = @selector(ddSetLogLevel:); + #if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR - - // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4 - // - // Crash caused by class_getClassMethod(2). - // - // "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until - // users had VoiceOver enabled [...]. I was able to work around it by searching the - // result of class_copyMethodList() instead of calling class_getClassMethod()" - - BOOL result = NO; - - unsigned int methodCount, i; - Method *methodList = class_copyMethodList(object_getClass(class), &methodCount); - - if (methodList != NULL) - { - BOOL getterFound = NO; - BOOL setterFound = NO; - - for (i = 0; i < methodCount; ++i) - { - SEL currentSel = method_getName(methodList[i]); - - if (currentSel == getterSel) - { - getterFound = YES; - } - else if (currentSel == setterSel) - { - setterFound = YES; - } - - if (getterFound && setterFound) - { - result = YES; - break; - } - } - - free(methodList); - } - - return result; - + + // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4 + // + // Crash caused by class_getClassMethod(2). + // + // "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until + // users had VoiceOver enabled [...]. I was able to work around it by searching the + // result of class_copyMethodList() instead of calling class_getClassMethod()" + + BOOL result = NO; + + unsigned int methodCount, i; + Method *methodList = class_copyMethodList(object_getClass(class), &methodCount); + + if (methodList != NULL) + { + BOOL getterFound = NO; + BOOL setterFound = NO; + + for (i = 0; i < methodCount; ++i) + { + SEL currentSel = method_getName(methodList[i]); + + if (currentSel == getterSel) + { + getterFound = YES; + } + else if (currentSel == setterSel) + { + setterFound = YES; + } + + if (getterFound && setterFound) + { + result = YES; + break; + } + } + + free(methodList); + } + + return result; + #else - - // Issue #24 (GitHub) - Crashing in in ARC+Simulator - // - // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator. - // For running in the Simulator, it needs to execute the non-iOS code. - - Method getter = class_getClassMethod(class, getterSel); - Method setter = class_getClassMethod(class, setterSel); - - if ((getter != NULL) && (setter != NULL)) - { - return YES; - } - - return NO; - + + // Issue #24 (GitHub) - Crashing in in ARC+Simulator + // + // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator. + // For running in the Simulator, it needs to execute the non-iOS code. + + Method getter = class_getClassMethod(class, getterSel); + Method setter = class_getClassMethod(class, setterSel); + + if ((getter != NULL) && (setter != NULL)) + { + return YES; + } + + return NO; + #endif } + (NSArray *)registeredClasses { - int numClasses, i; - - // We're going to get the list of all registered classes. - // The Objective-C runtime library automatically registers all the classes defined in your source code. - // - // To do this we use the following method (documented in the Objective-C Runtime Reference): - // - // int objc_getClassList(Class *buffer, int bufferLen) - // - // We can pass (NULL, 0) to obtain the total number of - // registered class definitions without actually retrieving any class definitions. - // This allows us to allocate the minimum amount of memory needed for the application. - - numClasses = objc_getClassList(NULL, 0); - - // The numClasses method now tells us how many classes we have. - // So we can allocate our buffer, and get pointers to all the class definitions. - - Class *classes = (Class *)malloc(sizeof(Class) * numClasses); - - numClasses = objc_getClassList(classes, numClasses); - - // We can now loop through the classes, and test each one to see if it is a DDLogging class. - - NSMutableArray *result = [NSMutableArray arrayWithCapacity:numClasses]; - - for (i = 0; i < numClasses; i++) - { - Class class = classes[i]; - - if ([self isRegisteredClass:class]) - { - [result addObject:class]; - } - } - - free(classes); - - return result; + int numClasses, i; + + // We're going to get the list of all registered classes. + // The Objective-C runtime library automatically registers all the classes defined in your source code. + // + // To do this we use the following method (documented in the Objective-C Runtime Reference): + // + // int objc_getClassList(Class *buffer, int bufferLen) + // + // We can pass (NULL, 0) to obtain the total number of + // registered class definitions without actually retrieving any class definitions. + // This allows us to allocate the minimum amount of memory needed for the application. + + numClasses = objc_getClassList(NULL, 0); + + // The numClasses method now tells us how many classes we have. + // So we can allocate our buffer, and get pointers to all the class definitions. + + Class *classes = (Class *)malloc(sizeof(Class) * numClasses); + if (classes == NULL) return nil; + + numClasses = objc_getClassList(classes, numClasses); + + // We can now loop through the classes, and test each one to see if it is a DDLogging class. + + NSMutableArray *result = [NSMutableArray arrayWithCapacity:numClasses]; + + for (i = 0; i < numClasses; i++) + { + Class class = classes[i]; + + if ([self isRegisteredClass:class]) + { + [result addObject:class]; + } + } + + free(classes); + + return result; } + (NSArray *)registeredClassNames { - NSArray *registeredClasses = [self registeredClasses]; - NSMutableArray *result = [NSMutableArray arrayWithCapacity:[registeredClasses count]]; - - for (Class class in registeredClasses) - { - [result addObject:NSStringFromClass(class)]; - } - - return result; + NSArray *registeredClasses = [self registeredClasses]; + NSMutableArray *result = [NSMutableArray arrayWithCapacity:[registeredClasses count]]; + + for (Class class in registeredClasses) + { + [result addObject:NSStringFromClass(class)]; + } + + return result; } + (int)logLevelForClass:(Class)aClass { - if ([self isRegisteredClass:aClass]) - { - return [aClass ddLogLevel]; - } - - return -1; + if ([self isRegisteredClass:aClass]) + { + return [aClass ddLogLevel]; + } + + return -1; } + (int)logLevelForClassWithName:(NSString *)aClassName { - Class aClass = NSClassFromString(aClassName); - - return [self logLevelForClass:aClass]; + Class aClass = NSClassFromString(aClassName); + + return [self logLevelForClass:aClass]; } + (void)setLogLevel:(int)logLevel forClass:(Class)aClass { - if ([self isRegisteredClass:aClass]) - { - [aClass ddSetLogLevel:logLevel]; - } + if ([self isRegisteredClass:aClass]) + { + [aClass ddSetLogLevel:logLevel]; + } } + (void)setLogLevel:(int)logLevel forClassWithName:(NSString *)aClassName { - Class aClass = NSClassFromString(aClassName); - - [self setLogLevel:logLevel forClass:aClass]; + Class aClass = NSClassFromString(aClassName); + + [self setLogLevel:logLevel forClass:aClass]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Logging Thread //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * This method should only be run on the logging thread/queue. -**/ -+ (void)lt_addLogger:(id <DDLogger>)logger ++ (void)lt_addLogger:(id <DDLogger>)logger logLevel:(int)logLevel { - // Add to loggers array. - // Need to create loggerQueue if loggerNode doesn't provide one. - - dispatch_queue_t loggerQueue = NULL; - - if ([logger respondsToSelector:@selector(loggerQueue)]) - { - // Logger may be providing its own queue - - loggerQueue = [logger loggerQueue]; - } - - if (loggerQueue == nil) - { - // Automatically create queue for the logger. - // Use the logger name as the queue name if possible. - - const char *loggerQueueName = NULL; - if ([logger respondsToSelector:@selector(loggerName)]) - { - loggerQueueName = [[logger loggerName] UTF8String]; - } - - loggerQueue = dispatch_queue_create(loggerQueueName, NULL); - } - - DDLoggerNode *loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue]; - [loggers addObject:loggerNode]; - - if ([logger respondsToSelector:@selector(didAddLogger)]) - { - dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { - - [logger didAddLogger]; - }}); - } + // Add to loggers array. + // Need to create loggerQueue if loggerNode doesn't provide one. + + NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), + @"This method should only be run on the logging thread/queue"); + + dispatch_queue_t loggerQueue = NULL; + + if ([logger respondsToSelector:@selector(loggerQueue)]) + { + // Logger may be providing its own queue + + loggerQueue = [logger loggerQueue]; + } + + if (loggerQueue == nil) + { + // Automatically create queue for the logger. + // Use the logger name as the queue name if possible. + + const char *loggerQueueName = NULL; + if ([logger respondsToSelector:@selector(loggerName)]) + { + loggerQueueName = [[logger loggerName] UTF8String]; + } + + loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + } + + DDLoggerNode *loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue logLevel:logLevel]; + [loggers addObject:loggerNode]; + + if ([logger respondsToSelector:@selector(didAddLogger)]) + { + dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [logger didAddLogger]; + }}); + } } -/** - * This method should only be run on the logging thread/queue. -**/ + (void)lt_removeLogger:(id <DDLogger>)logger { - // Find associated loggerNode in list of added loggers - - DDLoggerNode *loggerNode = nil; - - for (DDLoggerNode *node in loggers) - { - if (node->logger == logger) - { - loggerNode = node; - break; - } - } - - if (loggerNode == nil) - { - NSLogDebug(@"DDLog: Request to remove logger which wasn't added"); - return; - } - - // Notify logger - - if ([logger respondsToSelector:@selector(willRemoveLogger)]) - { - dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { - - [logger willRemoveLogger]; - }}); - } - - // Remove from loggers array - - [loggers removeObject:loggerNode]; + // Find associated loggerNode in list of added loggers + + NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), + @"This method should only be run on the logging thread/queue"); + + DDLoggerNode *loggerNode = nil; + + for (DDLoggerNode *node in loggers) + { + if (node->logger == logger) + { + loggerNode = node; + break; + } + } + + if (loggerNode == nil) + { + NSLogDebug(@"DDLog: Request to remove logger which wasn't added"); + return; + } + + // Notify logger + + if ([logger respondsToSelector:@selector(willRemoveLogger)]) + { + dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [logger willRemoveLogger]; + }}); + } + + // Remove from loggers array + + [loggers removeObject:loggerNode]; } -/** - * This method should only be run on the logging thread/queue. -**/ + (void)lt_removeAllLoggers { - // Notify all loggers - - for (DDLoggerNode *loggerNode in loggers) - { - if ([loggerNode->logger respondsToSelector:@selector(willRemoveLogger)]) - { - dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { - - [loggerNode->logger willRemoveLogger]; - }}); - } - } - - // Remove all loggers from array - - [loggers removeAllObjects]; + // Notify all loggers + + NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), + @"This method should only be run on the logging thread/queue"); + + for (DDLoggerNode *loggerNode in loggers) + { + if ([loggerNode->logger respondsToSelector:@selector(willRemoveLogger)]) + { + dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger willRemoveLogger]; + }}); + } + } + + // Remove all loggers from array + + [loggers removeAllObjects]; } -/** - * This method should only be run on the logging thread/queue. -**/ ++ (NSArray *)lt_allLoggers +{ + NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), + @"This method should only be run on the logging thread/queue"); + + NSMutableArray *theLoggers = [NSMutableArray new]; + + for (DDLoggerNode *loggerNode in loggers) + { + [theLoggers addObject:loggerNode->logger]; + } + + return [theLoggers copy]; +} + + (void)lt_log:(DDLogMessage *)logMessage { - // Execute the given log message on each of our loggers. - - if (numProcessors > 1) - { - // Execute each logger concurrently, each within its own queue. - // All blocks are added to same group. - // After each block has been queued, wait on group. - // - // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages. - // This would defeat the purpose of the efforts we made earlier to restrict the max queue size. - - for (DDLoggerNode *loggerNode in loggers) - { - dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool { - - [loggerNode->logger logMessage:logMessage]; - - }}); - } - - dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER); - } - else - { - // Execute each logger serialy, each within its own queue. - - for (DDLoggerNode *loggerNode in loggers) - { - dispatch_sync(loggerNode->loggerQueue, ^{ @autoreleasepool { - - [loggerNode->logger logMessage:logMessage]; - - }}); - } - } - - // If our queue got too big, there may be blocked threads waiting to add log messages to the queue. - // Since we've now dequeued an item from the log, we may need to unblock the next thread. - - // We are using a counting semaphore provided by GCD. - // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value. - // When a log message is queued this value is decremented. - // When a log message is dequeued this value is incremented. - // If the value ever drops below zero, - // the queueing thread blocks and waits in FIFO order for us to signal it. - // - // A dispatch semaphore is an efficient implementation of a traditional counting semaphore. - // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. - // If the calling semaphore does not need to block, no kernel call is made. - - dispatch_semaphore_signal(queueSemaphore); + // Execute the given log message on each of our loggers. + + NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), + @"This method should only be run on the logging thread/queue"); + + if (numProcessors > 1) + { + // Execute each logger concurrently, each within its own queue. + // All blocks are added to same group. + // After each block has been queued, wait on group. + // + // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages. + // This would defeat the purpose of the efforts we made earlier to restrict the max queue size. + + for (DDLoggerNode *loggerNode in loggers) + { + // skip the loggers that shouldn't write this message based on the logLevel + + if (!(logMessage->logFlag & loggerNode.logLevel)) + continue; + + dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger logMessage:logMessage]; + + }}); + } + + dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER); + } + else + { + // Execute each logger serialy, each within its own queue. + + for (DDLoggerNode *loggerNode in loggers) + { + // skip the loggers that shouldn't write this message based on the logLevel + + if (!(logMessage->logFlag & loggerNode.logLevel)) + continue; + + dispatch_sync(loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger logMessage:logMessage]; + + }}); + } + } + + // If our queue got too big, there may be blocked threads waiting to add log messages to the queue. + // Since we've now dequeued an item from the log, we may need to unblock the next thread. + + // We are using a counting semaphore provided by GCD. + // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value. + // When a log message is queued this value is decremented. + // When a log message is dequeued this value is incremented. + // If the value ever drops below zero, + // the queueing thread blocks and waits in FIFO order for us to signal it. + // + // A dispatch semaphore is an efficient implementation of a traditional counting semaphore. + // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. + // If the calling semaphore does not need to block, no kernel call is made. + + dispatch_semaphore_signal(queueSemaphore); } -/** - * This method should only be run on the background logging thread. -**/ + (void)lt_flush { - // All log statements issued before the flush method was invoked have now been executed. - // - // Now we need to propogate the flush request to any loggers that implement the flush method. - // This is designed for loggers that buffer IO. - - for (DDLoggerNode *loggerNode in loggers) - { - if ([loggerNode->logger respondsToSelector:@selector(flush)]) - { - dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool { - - [loggerNode->logger flush]; - - }}); - } - } - - dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER); + // All log statements issued before the flush method was invoked have now been executed. + // + // Now we need to propogate the flush request to any loggers that implement the flush method. + // This is designed for loggers that buffer IO. + + NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), + @"This method should only be run on the logging thread/queue"); + + for (DDLoggerNode *loggerNode in loggers) + { + if ([loggerNode->logger respondsToSelector:@selector(flush)]) + { + dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool { + + [loggerNode->logger flush]; + + }}); + } + } + + dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy) { - if (filePath == NULL) return nil; - - char *lastSlash = NULL; - char *lastDot = NULL; - - char *p = (char *)filePath; - - while (*p != '\0') - { - if (*p == '/') - lastSlash = p; - else if (*p == '.') - lastDot = p; - - p++; - } - - char *subStr; - NSUInteger subLen; - - if (lastSlash) - { - if (lastDot) - { - // lastSlash -> lastDot - subStr = lastSlash + 1; - subLen = lastDot - subStr; - } - else - { - // lastSlash -> endOfString - subStr = lastSlash + 1; - subLen = p - subStr; - } - } - else - { - if (lastDot) - { - // startOfString -> lastDot - subStr = (char *)filePath; - subLen = lastDot - subStr; - } - else - { - // startOfString -> endOfString - subStr = (char *)filePath; - subLen = p - subStr; - } - } - - if (copy) - { - return [[NSString alloc] initWithBytes:subStr - length:subLen - encoding:NSUTF8StringEncoding]; - } - else - { - // We can take advantage of the fact that __FILE__ is a string literal. - // Specifically, we don't need to waste time copying the string. - // We can just tell NSString to point to a range within the string literal. - - return [[NSString alloc] initWithBytesNoCopy:subStr - length:subLen - encoding:NSUTF8StringEncoding - freeWhenDone:NO]; - } + if (filePath == NULL) return nil; + + char *lastSlash = NULL; + char *lastDot = NULL; + + char *p = (char *)filePath; + + while (*p != '\0') + { + if (*p == '/') + lastSlash = p; + else if (*p == '.') + lastDot = p; + + p++; + } + + char *subStr; + NSUInteger subLen; + + if (lastSlash) + { + if (lastDot) + { + // lastSlash -> lastDot + subStr = lastSlash + 1; + subLen = lastDot - subStr; + } + else + { + // lastSlash -> endOfString + subStr = lastSlash + 1; + subLen = p - subStr; + } + } + else + { + if (lastDot) + { + // startOfString -> lastDot + subStr = (char *)filePath; + subLen = lastDot - subStr; + } + else + { + // startOfString -> endOfString + subStr = (char *)filePath; + subLen = p - subStr; + } + } + + if (copy) + { + return [[NSString alloc] initWithBytes:subStr + length:subLen + encoding:NSUTF8StringEncoding]; + } + else + { + // We can take advantage of the fact that __FILE__ is a string literal. + // Specifically, we don't need to waste time copying the string. + // We can just tell NSString to point to a range within the string literal. + + return [[NSString alloc] initWithBytesNoCopy:subStr + length:subLen + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + } } @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation DDLoggerNode -- (id)initWithLogger:(id <DDLogger>)aLogger loggerQueue:(dispatch_queue_t)aLoggerQueue +@synthesize logLevel; + +- (instancetype)initWithLogger:(id <DDLogger>)aLogger loggerQueue:(dispatch_queue_t)aLoggerQueue logLevel:(int)aLogLevel { - if ((self = [super init])) - { - logger = aLogger; - - if (aLoggerQueue) { - loggerQueue = aLoggerQueue; - #if !OS_OBJECT_USE_OBJC - dispatch_retain(loggerQueue); - #endif - } - } - return self; + if ((self = [super init])) + { + logger = aLogger; + + if (aLoggerQueue) { + loggerQueue = aLoggerQueue; + #if !OS_OBJECT_USE_OBJC + dispatch_retain(loggerQueue); + #endif + } + logLevel = aLogLevel; + } + return self; } -+ (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue ++ (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue logLevel:(int)logLevel { - return [[DDLoggerNode alloc] initWithLogger:logger loggerQueue:loggerQueue]; + return [[DDLoggerNode alloc] initWithLogger:logger loggerQueue:loggerQueue logLevel:logLevel]; } - (void)dealloc { - #if !OS_OBJECT_USE_OBJC - if (loggerQueue) dispatch_release(loggerQueue); - #endif + #if !OS_OBJECT_USE_OBJC + if (loggerQueue) dispatch_release(loggerQueue); + #endif } @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -808,276 +875,334 @@ @implementation DDLogMessage static char *dd_str_copy(const char *str) { - if (str == NULL) return NULL; - - size_t length = strlen(str); - char * result = malloc(length + 1); - strncpy(result, str, length); - result[length] = 0; - - return result; + if (str == NULL) return NULL; + + size_t length = strlen(str); + char * result = malloc(length + 1); + if (result == NULL) return NULL; + strncpy(result, str, length); + result[length] = 0; + + return result; } -- (id)initWithLogMsg:(NSString *)msg - level:(int)level - flag:(int)flag - context:(int)context - file:(const char *)aFile - function:(const char *)aFunction - line:(int)line - tag:(id)aTag - options:(DDLogMessageOptions)optionsMask +- (instancetype)initWithLogMsg:(NSString *)msg + level:(int)level + flag:(int)flag + context:(int)context + file:(const char *)aFile + function:(const char *)aFunction + line:(int)line + tag:(id)aTag + options:(DDLogMessageOptions)optionsMask { - if ((self = [super init])) - { - logMsg = msg; - logLevel = level; - logFlag = flag; - logContext = context; - lineNumber = line; - tag = aTag; - options = optionsMask; - - if (options & DDLogMessageCopyFile) - file = dd_str_copy(aFile); - else - file = (char *)aFile; - - if (options & DDLogMessageCopyFunction) - function = dd_str_copy(aFunction); - else - function = (char *)aFunction; - - timestamp = [[NSDate alloc] init]; - - machThreadID = pthread_mach_thread_np(pthread_self()); - - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - // The documentation for dispatch_get_current_queue() states: - // - // > [This method is] "recommended for debugging and logging purposes only"... - // - // Well that's exactly how we're using it here. Literally for logging purposes only. - // However, Apple has decided to deprecate this method anyway. - // However they have not given us an alternate version of dispatch_queue_get_label() that - // automatically uses the current queue, thus dispatch_get_current_queue() is still required. - // - // If dispatch_get_current_queue() disappears, without a dispatch_queue_get_label() alternative, - // Apple will have effectively taken away our ability to properly log the name of executing dispatch queue. - - dispatch_queue_t currentQueue = dispatch_get_current_queue(); - #pragma clang diagnostic pop - - queueLabel = dd_str_copy(dispatch_queue_get_label(currentQueue)); - - threadName = [[NSThread currentThread] name]; - } - return self; + if ((self = [super init])) + { + logMsg = msg; + logLevel = level; + logFlag = flag; + logContext = context; + lineNumber = line; + tag = aTag; + options = optionsMask; + + if (options & DDLogMessageCopyFile) + file = dd_str_copy(aFile); + else + file = (char *)aFile; + + if (options & DDLogMessageCopyFunction) + function = dd_str_copy(aFunction); + else + function = (char *)aFunction; + + timestamp = [[NSDate alloc] init]; + + machThreadID = pthread_mach_thread_np(pthread_self()); + + // Try to get the current queue's label + + // a) Compiling against newer SDK's (iOS 7+/OS X 10.9+) where DISPATCH_CURRENT_QUEUE_LABEL is defined + // on a (iOS 7.0+/OS X 10.9+) runtime version + BOOL gotLabel = NO; + #ifdef DISPATCH_CURRENT_QUEUE_LABEL + if ( + #if TARGET_OS_IPHONE + #ifndef NSFoundationVersionNumber_iOS_6_1 + #define NSFoundationVersionNumber_iOS_6_1 993.00 + #endif + floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1 // iOS 7+ (> iOS 6.1) + #else + [NSTimer instancesRespondToSelector:@selector(tolerance)] // OS X 10.9+ + #endif + ) { + queueLabel = dd_str_copy(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)); + gotLabel = YES; + } + #endif + + // b) Systems where dispatch_get_current_queue is not yet deprecated and won't crash (< iOS 6.0/OS X 10.9) + // dispatch_get_current_queue(void); __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_6,__MAC_10_9,__IPHONE_4_0,__IPHONE_6_0) + if (!gotLabel && + #if TARGET_OS_IPHONE + #ifndef NSFoundationVersionNumber_iOS_6_0 + #define NSFoundationVersionNumber_iOS_6_0 993.00 + #endif + floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_6_0 // < iOS 6.0 + #else + ![NSTimer instancesRespondToSelector:@selector(tolerance)] // < OS X 10.9 + #endif + ) { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + dispatch_queue_t currentQueue = dispatch_get_current_queue(); + #pragma clang diagnostic pop + + queueLabel = dd_str_copy(dispatch_queue_get_label(currentQueue)); + gotLabel = YES; + } + + // c) Give up + if (!gotLabel) { + queueLabel = dd_str_copy(""); // iOS 6.x only + } + + threadName = [[NSThread currentThread] name]; + } + return self; } - (NSString *)threadID { - return [[NSString alloc] initWithFormat:@"%x", machThreadID]; + return [[NSString alloc] initWithFormat:@"%x", machThreadID]; } - (NSString *)fileName { - return DDExtractFileNameWithoutExtension(file, NO); + return DDExtractFileNameWithoutExtension(file, NO); } - (NSString *)methodName { - if (function == NULL) - return nil; - else - return [[NSString alloc] initWithUTF8String:function]; + if (function == NULL) + return nil; + else + return [[NSString alloc] initWithUTF8String:function]; } - (void)dealloc { - if (file && (options & DDLogMessageCopyFile)) - free(file); - - if (function && (options & DDLogMessageCopyFunction)) - free(function); - - if (queueLabel) - free(queueLabel); + if (file && (options & DDLogMessageCopyFile)) + free(file); + + if (function && (options & DDLogMessageCopyFunction)) + free(function); + + if (queueLabel) + free(queueLabel); } + +- (id)copyWithZone:(NSZone *)zone { + DDLogMessage *newMessage = [[DDLogMessage alloc] init]; + + newMessage->logLevel = self->logLevel; + newMessage->logFlag = self->logFlag; + newMessage->logContext = self->logContext; + newMessage->logMsg = self->logMsg; + newMessage->timestamp = self->timestamp; + + if (self->options & DDLogMessageCopyFile) { + newMessage->file = dd_str_copy(self->file); + newMessage->function = dd_str_copy(self->function); + } else { + newMessage->file = self->file; + newMessage->function = self->function; + } + + newMessage->lineNumber = self->lineNumber; + + newMessage->machThreadID = self->machThreadID; + newMessage->queueLabel = dd_str_copy(self->queueLabel); + newMessage->threadName = self->threadName; + newMessage->tag = self->tag; + newMessage->options = self->options; + + return newMessage; +} + @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation DDAbstractLogger - (id)init { - if ((self = [super init])) - { - const char *loggerQueueName = NULL; - if ([self respondsToSelector:@selector(loggerName)]) - { - loggerQueueName = [[self loggerName] UTF8String]; - } - - loggerQueue = dispatch_queue_create(loggerQueueName, NULL); - - // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue. - // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue. - // The documentation states: - // - // > Keys are only compared as pointers and are never dereferenced. - // > Thus, you can use a pointer to a static variable for a specific subsystem or - // > any other value that allows you to identify the value uniquely. - // > Specifying a pointer to a string constant is not recommended. - // - // So we're going to use the very convenient key of "self", - // which also works when multiple logger classes extend this class, as each will have a different "self" key. - // - // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below). - - void *key = (__bridge void *)self; - void *nonNullValue = (__bridge void *)self; - - dispatch_queue_set_specific(loggerQueue, key, nonNullValue, NULL); - } - return self; + if ((self = [super init])) + { + const char *loggerQueueName = NULL; + if ([self respondsToSelector:@selector(loggerName)]) + { + loggerQueueName = [[self loggerName] UTF8String]; + } + + loggerQueue = dispatch_queue_create(loggerQueueName, NULL); + + // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue. + // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue. + // The documentation states: + // + // > Keys are only compared as pointers and are never dereferenced. + // > Thus, you can use a pointer to a static variable for a specific subsystem or + // > any other value that allows you to identify the value uniquely. + // > Specifying a pointer to a string constant is not recommended. + // + // So we're going to use the very convenient key of "self", + // which also works when multiple logger classes extend this class, as each will have a different "self" key. + // + // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below). + + void *key = (__bridge void *)self; + void *nonNullValue = (__bridge void *)self; + + dispatch_queue_set_specific(loggerQueue, key, nonNullValue, NULL); + } + return self; } - (void)dealloc { - #if !OS_OBJECT_USE_OBJC - if (loggerQueue) dispatch_release(loggerQueue); - #endif + #if !OS_OBJECT_USE_OBJC + if (loggerQueue) dispatch_release(loggerQueue); + #endif } - (void)logMessage:(DDLogMessage *)logMessage { - // Override me + // Override me } - (id <DDLogFormatter>)logFormatter { - // This method must be thread safe and intuitive. - // Therefore if somebody executes the following code: - // - // [logger setLogFormatter:myFormatter]; - // formatter = [logger logFormatter]; - // - // They would expect formatter to equal myFormatter. - // This functionality must be ensured by the getter and setter method. - // - // The thread safety must not come at a cost to the performance of the logMessage method. - // This method is likely called sporadically, while the logMessage method is called repeatedly. - // This means, the implementation of this method: - // - Must NOT require the logMessage method to acquire a lock. - // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts). - // - // Thread safety is ensured by executing access to the formatter variable on the loggerQueue. - // This is the same queue that the logMessage method operates on. - // - // Note: The last time I benchmarked the performance of direct access vs atomic property access, - // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone. - // - // Furthermore, consider the following code: - // - // DDLogVerbose(@"log msg 1"); - // DDLogVerbose(@"log msg 2"); - // [logger setFormatter:myFormatter]; - // DDLogVerbose(@"log msg 3"); - // - // Our intuitive requirement means that the new formatter will only apply to the 3rd log message. - // This must remain true even when using asynchronous logging. - // We must keep in mind the various queue's that are in play here: - // - // loggerQueue : Our own private internal queue that the logMessage method runs on. - // Operations are added to this queue from the global loggingQueue. - // - // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue. - // - // All log statements go through the serial gloabalLoggingQueue before they arrive at our loggerQueue. - // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation. - - // IMPORTANT NOTE: - // - // Methods within the DDLogger implementation MUST access the formatter ivar directly. - // This method is designed explicitly for external access. - // - // Using "self." syntax to go through this method will cause immediate deadlock. - // This is the intended result. Fix it by accessing the ivar directly. - // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. - - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); - - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - - __block id <DDLogFormatter> result; - - dispatch_sync(globalLoggingQueue, ^{ - dispatch_sync(loggerQueue, ^{ - result = formatter; - }); - }); - - return result; + // This method must be thread safe and intuitive. + // Therefore if somebody executes the following code: + // + // [logger setLogFormatter:myFormatter]; + // formatter = [logger logFormatter]; + // + // They would expect formatter to equal myFormatter. + // This functionality must be ensured by the getter and setter method. + // + // The thread safety must not come at a cost to the performance of the logMessage method. + // This method is likely called sporadically, while the logMessage method is called repeatedly. + // This means, the implementation of this method: + // - Must NOT require the logMessage method to acquire a lock. + // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts). + // + // Thread safety is ensured by executing access to the formatter variable on the loggerQueue. + // This is the same queue that the logMessage method operates on. + // + // Note: The last time I benchmarked the performance of direct access vs atomic property access, + // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone. + // + // Furthermore, consider the following code: + // + // DDLogVerbose(@"log msg 1"); + // DDLogVerbose(@"log msg 2"); + // [logger setFormatter:myFormatter]; + // DDLogVerbose(@"log msg 3"); + // + // Our intuitive requirement means that the new formatter will only apply to the 3rd log message. + // This must remain true even when using asynchronous logging. + // We must keep in mind the various queue's that are in play here: + // + // loggerQueue : Our own private internal queue that the logMessage method runs on. + // Operations are added to this queue from the global loggingQueue. + // + // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue. + // + // All log statements go through the serial gloabalLoggingQueue before they arrive at our loggerQueue. + // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation. + + // IMPORTANT NOTE: + // + // Methods within the DDLogger implementation MUST access the formatter ivar directly. + // This method is designed explicitly for external access. + // + // Using "self." syntax to go through this method will cause immediate deadlock. + // This is the intended result. Fix it by accessing the ivar directly. + // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + __block id <DDLogFormatter> result; + + dispatch_sync(globalLoggingQueue, ^{ + dispatch_sync(loggerQueue, ^{ + result = formatter; + }); + }); + + return result; } - (void)setLogFormatter:(id <DDLogFormatter>)logFormatter { - // The design of this method is documented extensively in the logFormatter message (above in code). - - NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); - NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); - - dispatch_block_t block = ^{ @autoreleasepool { - - if (formatter != logFormatter) - { - if ([formatter respondsToSelector:@selector(willRemoveFromLogger:)]) { - [formatter willRemoveFromLogger:self]; - } - - formatter = logFormatter; - - if ([formatter respondsToSelector:@selector(didAddToLogger:)]) { - [formatter didAddToLogger:self]; - } - } - }}; - - dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; - - dispatch_async(globalLoggingQueue, ^{ - dispatch_async(loggerQueue, block); - }); + // The design of this method is documented extensively in the logFormatter message (above in code). + + NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); + NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); + + dispatch_block_t block = ^{ @autoreleasepool { + + if (formatter != logFormatter) + { + if ([formatter respondsToSelector:@selector(willRemoveFromLogger:)]) { + [formatter willRemoveFromLogger:self]; + } + + formatter = logFormatter; + + if ([formatter respondsToSelector:@selector(didAddToLogger:)]) { + [formatter didAddToLogger:self]; + } + } + }}; + + dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; + + dispatch_async(globalLoggingQueue, ^{ + dispatch_async(loggerQueue, block); + }); } - (dispatch_queue_t)loggerQueue { - return loggerQueue; + return loggerQueue; } - (NSString *)loggerName { - return NSStringFromClass([self class]); + return NSStringFromClass([self class]); } - (BOOL)isOnGlobalLoggingQueue { - return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL); + return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL); } - (BOOL)isOnInternalLoggerQueue { - void *key = (__bridge void *)self; - return (dispatch_get_specific(key) != NULL); + void *key = (__bridge void *)self; + return (dispatch_get_specific(key) != NULL); } @end