Subversion Repositories Mobile Apps.GyroMouse

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. #import "DDLog.h"
  2.  
  3. #import <pthread.h>
  4. #import <objc/runtime.h>
  5. #import <mach/mach_host.h>
  6. #import <mach/host_info.h>
  7. #import <libkern/OSAtomic.h>
  8. #import <Availability.h>
  9. #if TARGET_OS_IPHONE
  10.     #import <UIKit/UIDevice.h>
  11. #endif
  12.  
  13. /**
  14.  * Welcome to Cocoa Lumberjack!
  15.  *
  16.  * The project page has a wealth of documentation if you have any questions.
  17.  * https://github.com/CocoaLumberjack/CocoaLumberjack
  18.  *
  19.  * If you're new to the project you may wish to read the "Getting Started" wiki.
  20.  * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted
  21.  *
  22. **/
  23.  
  24. #if ! __has_feature(objc_arc)
  25. #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  26. #endif
  27.  
  28. // We probably shouldn't be using DDLog() statements within the DDLog implementation.
  29. // But we still want to leave our log statements for any future debugging,
  30. // and to allow other developers to trace the implementation (which is a great learning tool).
  31. //
  32. // So we use a primitive logging macro around NSLog.
  33. // We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
  34.  
  35. #define DD_DEBUG NO
  36.  
  37. #define NSLogDebug(frmt, ...) do{ if(DD_DEBUG) NSLog((frmt), ##__VA_ARGS__); } while(0)
  38.  
  39. // Specifies the maximum queue size of the logging thread.
  40. //
  41. // Since most logging is asynchronous, its possible for rogue threads to flood the logging queue.
  42. // That is, to issue an abundance of log statements faster than the logging thread can keepup.
  43. // Typically such a scenario occurs when log statements are added haphazardly within large loops,
  44. // but may also be possible if relatively slow loggers are being used.
  45. //
  46. // This property caps the queue size at a given number of outstanding log statements.
  47. // If a thread attempts to issue a log statement when the queue is already maxed out,
  48. // the issuing thread will block until the queue size drops below the max again.
  49.  
  50. #define LOG_MAX_QUEUE_SIZE 1000 // Should not exceed INT32_MAX
  51.  
  52. // The "global logging queue" refers to [DDLog loggingQueue].
  53. // It is the queue that all log statements go through.
  54. //
  55. // The logging queue sets a flag via dispatch_queue_set_specific using this key.
  56. // We can check for this key via dispatch_get_specific() to see if we're on the "global logging queue".
  57.  
  58. static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey;
  59.  
  60.  
  61. @interface DDLoggerNode : NSObject {
  62. @public
  63.     id <DDLogger> logger;  
  64.     dispatch_queue_t loggerQueue;
  65.     int logLevel;
  66. }
  67.  
  68. @property (nonatomic, assign, readonly) int logLevel;
  69.  
  70. + (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue logLevel:(int)logLevel;
  71.  
  72. @end
  73.  
  74.  
  75. @interface DDLog (PrivateAPI)
  76.  
  77. + (void)lt_addLogger:(id <DDLogger>)logger logLevel:(int)logLevel;
  78. + (void)lt_removeLogger:(id <DDLogger>)logger;
  79. + (void)lt_removeAllLoggers;
  80. + (void)lt_log:(DDLogMessage *)logMessage;
  81. + (void)lt_flush;
  82.  
  83. @end
  84.  
  85. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  86. #pragma mark -
  87. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  88.  
  89. @implementation DDLog
  90.  
  91. // An array used to manage all the individual loggers.
  92. // The array is only modified on the loggingQueue/loggingThread.
  93. static NSMutableArray *loggers;
  94.  
  95. // All logging statements are added to the same queue to ensure FIFO operation.
  96. static dispatch_queue_t loggingQueue;
  97.  
  98. // Individual loggers are executed concurrently per log statement.
  99. // Each logger has it's own associated queue, and a dispatch group is used for synchrnoization.
  100. static dispatch_group_t loggingGroup;
  101.  
  102. // In order to prevent to queue from growing infinitely large,
  103. // a maximum size is enforced (LOG_MAX_QUEUE_SIZE).
  104. static dispatch_semaphore_t queueSemaphore;
  105.  
  106. // Minor optimization for uniprocessor machines
  107. static unsigned int numProcessors;
  108.  
  109. /**
  110.  * The runtime sends initialize to each class in a program exactly one time just before the class,
  111.  * or any class that inherits from it, is sent its first message from within the program. (Thus the
  112.  * method may never be invoked if the class is not used.) The runtime sends the initialize message to
  113.  * classes in a thread-safe manner. Superclasses receive this message before their subclasses.
  114.  *
  115.  * This method may also be called directly (assumably by accident), hence the safety mechanism.
  116. **/
  117. + (void)initialize
  118. {
  119.     static BOOL initialized = NO;
  120.     if (!initialized)
  121.     {
  122.         initialized = YES;
  123.        
  124.         loggers = [[NSMutableArray alloc] initWithCapacity:4];
  125.        
  126.         NSLogDebug(@"DDLog: Using grand central dispatch");
  127.        
  128.         loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL);
  129.         loggingGroup = dispatch_group_create();
  130.        
  131.         void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null
  132.         dispatch_queue_set_specific(loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL);
  133.        
  134.         queueSemaphore = dispatch_semaphore_create(LOG_MAX_QUEUE_SIZE);
  135.        
  136.         // Figure out how many processors are available.
  137.         // This may be used later for an optimization on uniprocessor machines.
  138.        
  139.         host_basic_info_data_t hostInfo;
  140.         mach_msg_type_number_t infoCount;
  141.        
  142.         infoCount = HOST_BASIC_INFO_COUNT;
  143.         host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&hostInfo, &infoCount);
  144.        
  145.         unsigned int result = (unsigned int)(hostInfo.max_cpus);
  146.         unsigned int one    = (unsigned int)(1);
  147.        
  148.         numProcessors = MAX(result, one);
  149.        
  150.         NSLogDebug(@"DDLog: numProcessors = %u", numProcessors);
  151.            
  152.        
  153.     #if TARGET_OS_IPHONE
  154.         NSString *notificationName = @"UIApplicationWillTerminateNotification";
  155.     #else
  156.         NSString *notificationName = nil;
  157.  
  158.         if (NSApp)
  159.         {
  160.             notificationName = @"NSApplicationWillTerminateNotification";
  161.         }
  162.         else
  163.         {
  164.             // If there is no NSApp -> we are running Command Line Tool app.
  165.             // In this case terminate notification wouldn't be fired, so we use workaround.
  166.             atexit_b(^{
  167.                 [self applicationWillTerminate:nil];
  168.             });
  169.         }
  170.     #endif
  171.        
  172.         if (notificationName) {
  173.             [[NSNotificationCenter defaultCenter] addObserver:self
  174.                                                      selector:@selector(applicationWillTerminate:)
  175.                                                          name:notificationName
  176.                                                        object:nil];
  177.         }
  178.     }
  179. }
  180.  
  181. /**
  182.  * Provides access to the logging queue.
  183. **/
  184. + (dispatch_queue_t)loggingQueue
  185. {
  186.     return loggingQueue;
  187. }
  188.  
  189. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  190. #pragma mark Notifications
  191. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  192.  
  193. + (void)applicationWillTerminate:(NSNotification *)notification
  194. {
  195.     [self flushLog];
  196. }
  197.  
  198. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  199. #pragma mark Logger Management
  200. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  201.  
  202. + (void)addLogger:(id <DDLogger>)logger
  203. {
  204.     [self addLogger:logger withLogLevel:LOG_LEVEL_VERBOSE];
  205. }
  206.  
  207. + (void)addLogger:(id <DDLogger>)logger withLogLevel:(int)logLevel
  208. {
  209.     if (logger == nil) return;
  210.    
  211.     dispatch_async(loggingQueue, ^{ @autoreleasepool {
  212.        
  213.         [self lt_addLogger:logger logLevel:logLevel];
  214.     }});
  215. }
  216.  
  217. + (void)removeLogger:(id <DDLogger>)logger
  218. {
  219.     if (logger == nil) return;
  220.    
  221.     dispatch_async(loggingQueue, ^{ @autoreleasepool {
  222.        
  223.         [self lt_removeLogger:logger];
  224.     }});
  225. }
  226.  
  227. + (void)removeAllLoggers
  228. {
  229.     dispatch_async(loggingQueue, ^{ @autoreleasepool {
  230.        
  231.         [self lt_removeAllLoggers];
  232.     }});
  233. }
  234.  
  235. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  236. #pragma mark Master Logging
  237. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  238.  
  239. + (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag
  240. {
  241.     // We have a tricky situation here...
  242.     //
  243.     // In the common case, when the queueSize is below the maximumQueueSize,
  244.     // we want to simply enqueue the logMessage. And we want to do this as fast as possible,
  245.     // which means we don't want to block and we don't want to use any locks.
  246.     //
  247.     // However, if the queueSize gets too big, we want to block.
  248.     // But we have very strict requirements as to when we block, and how long we block.
  249.     //
  250.     // The following example should help illustrate our requirements:
  251.     //
  252.     // Imagine that the maximum queue size is configured to be 5,
  253.     // and that there are already 5 log messages queued.
  254.     // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed)
  255.     //
  256.     // Now if our thread issues a log statement (let us call the log message F),
  257.     // it should block before the message is added to the queue.
  258.     // Furthermore, it should be unblocked immediately after A has been unqueued.
  259.     //
  260.     // The requirements are strict in this manner so that we block only as long as necessary,
  261.     // and so that blocked threads are unblocked in the order in which they were blocked.
  262.     //
  263.     // Returning to our previous example, let us assume that log messages A through E are still queued.
  264.     // Our aforementioned thread is blocked attempting to queue log message F.
  265.     // Now assume we have another separate thread that attempts to issue log message G.
  266.     // It should block until log messages A and B have been unqueued.
  267.    
  268.    
  269.     // We are using a counting semaphore provided by GCD.
  270.     // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value.
  271.     // Everytime we want to queue a log message we decrement this value.
  272.     // If the resulting value is less than zero,
  273.     // the semaphore function waits in FIFO order for a signal to occur before returning.
  274.     //
  275.     // A dispatch semaphore is an efficient implementation of a traditional counting semaphore.
  276.     // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked.
  277.     // If the calling semaphore does not need to block, no kernel call is made.
  278.    
  279.     dispatch_semaphore_wait(queueSemaphore, DISPATCH_TIME_FOREVER);
  280.    
  281.     // We've now sure we won't overflow the queue.
  282.     // It is time to queue our log message.
  283.    
  284.     dispatch_block_t logBlock = ^{ @autoreleasepool {
  285.        
  286.         [self lt_log:logMessage];
  287.     }};
  288.    
  289.     if (asyncFlag)
  290.         dispatch_async(loggingQueue, logBlock);
  291.     else
  292.         dispatch_sync(loggingQueue, logBlock);
  293. }
  294.  
  295. + (void)log:(BOOL)asynchronous
  296.       level:(int)level
  297.        flag:(int)flag
  298.     context:(int)context
  299.        file:(const char *)file
  300.    function:(const char *)function
  301.        line:(int)line
  302.         tag:(id)tag
  303.      format:(NSString *)format, ...
  304. {
  305.     va_list args;
  306.     if (format)
  307.     {
  308.         va_start(args, format);
  309.        
  310.         NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args];
  311.         DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg
  312.                                                                   level:level
  313.                                                                    flag:flag
  314.                                                                 context:context
  315.                                                                    file:file
  316.                                                                function:function
  317.                                                                    line:line
  318.                                                                     tag:tag
  319.                                                                 options:0];
  320.        
  321.         [self queueLogMessage:logMessage asynchronously:asynchronous];
  322.        
  323.         va_end(args);
  324.     }
  325. }
  326.  
  327. + (void)log:(BOOL)asynchronous
  328.       level:(int)level
  329.        flag:(int)flag
  330.     context:(int)context
  331.        file:(const char *)file
  332.    function:(const char *)function
  333.        line:(int)line
  334.         tag:(id)tag
  335.      format:(NSString *)format
  336.        args:(va_list)args
  337. {
  338.     if (format)
  339.     {
  340.         NSString *logMsg = [[NSString alloc] initWithFormat:format arguments:args];
  341.         DDLogMessage *logMessage = [[DDLogMessage alloc] initWithLogMsg:logMsg
  342.                                                                   level:level
  343.                                                                    flag:flag
  344.                                                                 context:context
  345.                                                                    file:file
  346.                                                                function:function
  347.                                                                    line:line
  348.                                                                     tag:tag
  349.                                                                 options:0];
  350.        
  351.         [self queueLogMessage:logMessage asynchronously:asynchronous];
  352.     }
  353. }
  354.  
  355. + (void)flushLog
  356. {
  357.     dispatch_sync(loggingQueue, ^{ @autoreleasepool {
  358.        
  359.         [self lt_flush];
  360.     }});
  361. }
  362.  
  363. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  364. #pragma mark Registered Dynamic Logging
  365. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  366.  
  367. + (BOOL)isRegisteredClass:(Class)class
  368. {
  369.     SEL getterSel = @selector(ddLogLevel);
  370.     SEL setterSel = @selector(ddSetLogLevel:);
  371.    
  372. #if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
  373.    
  374.     // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4
  375.     //
  376.     // Crash caused by class_getClassMethod(2).
  377.     //
  378.     //     "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until
  379.     //      users had VoiceOver enabled [...]. I was able to work around it by searching the
  380.     //      result of class_copyMethodList() instead of calling class_getClassMethod()"
  381.    
  382.     BOOL result = NO;
  383.    
  384.     unsigned int methodCount, i;
  385.     Method *methodList = class_copyMethodList(object_getClass(class), &methodCount);
  386.    
  387.     if (methodList != NULL)
  388.     {
  389.         BOOL getterFound = NO;
  390.         BOOL setterFound = NO;
  391.        
  392.         for (i = 0; i < methodCount; ++i)
  393.         {
  394.             SEL currentSel = method_getName(methodList[i]);
  395.            
  396.             if (currentSel == getterSel)
  397.             {
  398.                 getterFound = YES;
  399.             }
  400.             else if (currentSel == setterSel)
  401.             {
  402.                 setterFound = YES;
  403.             }
  404.            
  405.             if (getterFound && setterFound)
  406.             {
  407.                 result = YES;
  408.                 break;
  409.             }
  410.         }
  411.        
  412.         free(methodList);
  413.     }
  414.    
  415.     return result;
  416.    
  417. #else
  418.    
  419.     // Issue #24 (GitHub) - Crashing in in ARC+Simulator
  420.     //
  421.     // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator.
  422.     // For running in the Simulator, it needs to execute the non-iOS code.
  423.    
  424.     Method getter = class_getClassMethod(class, getterSel);
  425.     Method setter = class_getClassMethod(class, setterSel);
  426.    
  427.     if ((getter != NULL) && (setter != NULL))
  428.     {
  429.         return YES;
  430.     }
  431.    
  432.     return NO;
  433.    
  434. #endif
  435. }
  436.  
  437. + (NSArray *)registeredClasses
  438. {
  439.     int numClasses, i;
  440.    
  441.     // We're going to get the list of all registered classes.
  442.     // The Objective-C runtime library automatically registers all the classes defined in your source code.
  443.     //
  444.     // To do this we use the following method (documented in the Objective-C Runtime Reference):
  445.     //
  446.     // int objc_getClassList(Class *buffer, int bufferLen)
  447.     //
  448.     // We can pass (NULL, 0) to obtain the total number of
  449.     // registered class definitions without actually retrieving any class definitions.
  450.     // This allows us to allocate the minimum amount of memory needed for the application.
  451.    
  452.     numClasses = objc_getClassList(NULL, 0);
  453.    
  454.     // The numClasses method now tells us how many classes we have.
  455.     // So we can allocate our buffer, and get pointers to all the class definitions.
  456.    
  457.     Class *classes = (Class *)malloc(sizeof(Class) * numClasses);
  458.     if (classes == NULL) return nil;
  459.    
  460.     numClasses = objc_getClassList(classes, numClasses);
  461.    
  462.     // We can now loop through the classes, and test each one to see if it is a DDLogging class.
  463.    
  464.     NSMutableArray *result = [NSMutableArray arrayWithCapacity:numClasses];
  465.    
  466.     for (i = 0; i < numClasses; i++)
  467.     {
  468.         Class class = classes[i];
  469.        
  470.         if ([self isRegisteredClass:class])
  471.         {
  472.             [result addObject:class];
  473.         }
  474.     }
  475.    
  476.     free(classes);
  477.    
  478.     return result;
  479. }
  480.  
  481. + (NSArray *)registeredClassNames
  482. {
  483.     NSArray *registeredClasses = [self registeredClasses];
  484.     NSMutableArray *result = [NSMutableArray arrayWithCapacity:[registeredClasses count]];
  485.    
  486.     for (Class class in registeredClasses)
  487.     {
  488.         [result addObject:NSStringFromClass(class)];
  489.     }
  490.    
  491.     return result;
  492. }
  493.  
  494. + (int)logLevelForClass:(Class)aClass
  495. {
  496.     if ([self isRegisteredClass:aClass])
  497.     {
  498.         return [aClass ddLogLevel];
  499.     }
  500.    
  501.     return -1;
  502. }
  503.  
  504. + (int)logLevelForClassWithName:(NSString *)aClassName
  505. {
  506.     Class aClass = NSClassFromString(aClassName);
  507.    
  508.     return [self logLevelForClass:aClass];
  509. }
  510.  
  511. + (void)setLogLevel:(int)logLevel forClass:(Class)aClass
  512. {
  513.     if ([self isRegisteredClass:aClass])
  514.     {
  515.         [aClass ddSetLogLevel:logLevel];
  516.     }
  517. }
  518.  
  519. + (void)setLogLevel:(int)logLevel forClassWithName:(NSString *)aClassName
  520. {
  521.     Class aClass = NSClassFromString(aClassName);
  522.    
  523.     [self setLogLevel:logLevel forClass:aClass];
  524. }
  525.  
  526. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  527. #pragma mark Logging Thread
  528. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  529.  
  530. /**
  531.  * This method should only be run on the logging thread/queue.
  532. **/
  533. + (void)lt_addLogger:(id <DDLogger>)logger logLevel:(int)logLevel
  534. {
  535.     // Add to loggers array.
  536.     // Need to create loggerQueue if loggerNode doesn't provide one.
  537.    
  538.     dispatch_queue_t loggerQueue = NULL;
  539.    
  540.     if ([logger respondsToSelector:@selector(loggerQueue)])
  541.     {
  542.         // Logger may be providing its own queue
  543.        
  544.         loggerQueue = [logger loggerQueue];
  545.     }
  546.    
  547.     if (loggerQueue == nil)
  548.     {
  549.         // Automatically create queue for the logger.
  550.         // Use the logger name as the queue name if possible.
  551.        
  552.         const char *loggerQueueName = NULL;
  553.         if ([logger respondsToSelector:@selector(loggerName)])
  554.         {
  555.             loggerQueueName = [[logger loggerName] UTF8String];
  556.         }
  557.        
  558.         loggerQueue = dispatch_queue_create(loggerQueueName, NULL);
  559.     }
  560.    
  561.     DDLoggerNode *loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue logLevel:logLevel];
  562.     [loggers addObject:loggerNode];
  563.    
  564.     if ([logger respondsToSelector:@selector(didAddLogger)])
  565.     {
  566.         dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool {
  567.            
  568.             [logger didAddLogger];
  569.         }});
  570.     }
  571. }
  572.  
  573. /**
  574.  * This method should only be run on the logging thread/queue.
  575. **/
  576. + (void)lt_removeLogger:(id <DDLogger>)logger
  577. {
  578.     // Find associated loggerNode in list of added loggers
  579.    
  580.     DDLoggerNode *loggerNode = nil;
  581.    
  582.     for (DDLoggerNode *node in loggers)
  583.     {
  584.         if (node->logger == logger)
  585.         {
  586.             loggerNode = node;
  587.             break;
  588.         }
  589.     }
  590.    
  591.     if (loggerNode == nil)
  592.     {
  593.         NSLogDebug(@"DDLog: Request to remove logger which wasn't added");
  594.         return;
  595.     }
  596.    
  597.     // Notify logger
  598.    
  599.     if ([logger respondsToSelector:@selector(willRemoveLogger)])
  600.     {
  601.         dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool {
  602.            
  603.             [logger willRemoveLogger];
  604.         }});
  605.     }
  606.    
  607.     // Remove from loggers array
  608.    
  609.     [loggers removeObject:loggerNode];
  610. }
  611.  
  612. /**
  613.  * This method should only be run on the logging thread/queue.
  614. **/
  615. + (void)lt_removeAllLoggers
  616. {
  617.     // Notify all loggers
  618.    
  619.     for (DDLoggerNode *loggerNode in loggers)
  620.     {
  621.         if ([loggerNode->logger respondsToSelector:@selector(willRemoveLogger)])
  622.         {
  623.             dispatch_async(loggerNode->loggerQueue, ^{ @autoreleasepool {
  624.                
  625.                 [loggerNode->logger willRemoveLogger];
  626.             }});
  627.         }
  628.     }
  629.    
  630.     // Remove all loggers from array
  631.    
  632.     [loggers removeAllObjects];
  633. }
  634.  
  635. /**
  636.  * This method should only be run on the logging thread/queue.
  637. **/
  638. + (void)lt_log:(DDLogMessage *)logMessage
  639. {
  640.     // Execute the given log message on each of our loggers.
  641.    
  642.     if (numProcessors > 1)
  643.     {
  644.         // Execute each logger concurrently, each within its own queue.
  645.         // All blocks are added to same group.
  646.         // After each block has been queued, wait on group.
  647.         //
  648.         // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages.
  649.         // This would defeat the purpose of the efforts we made earlier to restrict the max queue size.
  650.        
  651.         for (DDLoggerNode *loggerNode in loggers)
  652.         {
  653.             // skip the loggers that shouldn't write this message based on the logLevel
  654.  
  655.             if (logMessage->logFlag > loggerNode.logLevel)
  656.                 continue;
  657.  
  658.             dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool {
  659.                
  660.                 [loggerNode->logger logMessage:logMessage];
  661.            
  662.             }});
  663.         }
  664.        
  665.         dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER);
  666.     }
  667.     else
  668.     {
  669.         // Execute each logger serialy, each within its own queue.
  670.        
  671.         for (DDLoggerNode *loggerNode in loggers)
  672.         {
  673.             // skip the loggers that shouldn't write this message based on the logLevel
  674.            
  675.             if (logMessage->logFlag > loggerNode.logLevel)
  676.                 continue;
  677.  
  678.             dispatch_sync(loggerNode->loggerQueue, ^{ @autoreleasepool {
  679.                
  680.                 [loggerNode->logger logMessage:logMessage];
  681.                
  682.             }});
  683.         }
  684.     }
  685.    
  686.     // If our queue got too big, there may be blocked threads waiting to add log messages to the queue.
  687.     // Since we've now dequeued an item from the log, we may need to unblock the next thread.
  688.    
  689.     // We are using a counting semaphore provided by GCD.
  690.     // The semaphore is initialized with our LOG_MAX_QUEUE_SIZE value.
  691.     // When a log message is queued this value is decremented.
  692.     // When a log message is dequeued this value is incremented.
  693.     // If the value ever drops below zero,
  694.     // the queueing thread blocks and waits in FIFO order for us to signal it.
  695.     //
  696.     // A dispatch semaphore is an efficient implementation of a traditional counting semaphore.
  697.     // Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked.
  698.     // If the calling semaphore does not need to block, no kernel call is made.
  699.    
  700.     dispatch_semaphore_signal(queueSemaphore);
  701. }
  702.  
  703. /**
  704.  * This method should only be run on the background logging thread.
  705. **/
  706. + (void)lt_flush
  707. {
  708.     // All log statements issued before the flush method was invoked have now been executed.
  709.     //
  710.     // Now we need to propogate the flush request to any loggers that implement the flush method.
  711.     // This is designed for loggers that buffer IO.
  712.        
  713.     for (DDLoggerNode *loggerNode in loggers)
  714.     {
  715.         if ([loggerNode->logger respondsToSelector:@selector(flush)])
  716.         {
  717.             dispatch_group_async(loggingGroup, loggerNode->loggerQueue, ^{ @autoreleasepool {
  718.                
  719.                 [loggerNode->logger flush];
  720.                
  721.             }});
  722.         }
  723.     }
  724.    
  725.     dispatch_group_wait(loggingGroup, DISPATCH_TIME_FOREVER);
  726. }
  727.  
  728. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  729. #pragma mark Utilities
  730. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  731.  
  732. NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy)
  733. {
  734.     if (filePath == NULL) return nil;
  735.    
  736.     char *lastSlash = NULL;
  737.     char *lastDot = NULL;
  738.    
  739.     char *p = (char *)filePath;
  740.    
  741.     while (*p != '\0')
  742.     {
  743.         if (*p == '/')
  744.             lastSlash = p;
  745.         else if (*p == '.')
  746.             lastDot = p;
  747.        
  748.         p++;
  749.     }
  750.    
  751.     char *subStr;
  752.     NSUInteger subLen;
  753.    
  754.     if (lastSlash)
  755.     {
  756.         if (lastDot)
  757.         {
  758.             // lastSlash -> lastDot
  759.             subStr = lastSlash + 1;
  760.             subLen = lastDot - subStr;
  761.         }
  762.         else
  763.         {
  764.             // lastSlash -> endOfString
  765.             subStr = lastSlash + 1;
  766.             subLen = p - subStr;
  767.         }
  768.     }
  769.     else
  770.     {
  771.         if (lastDot)
  772.         {
  773.             // startOfString -> lastDot
  774.             subStr = (char *)filePath;
  775.             subLen = lastDot - subStr;
  776.         }
  777.         else
  778.         {
  779.             // startOfString -> endOfString
  780.             subStr = (char *)filePath;
  781.             subLen = p - subStr;
  782.         }
  783.     }
  784.    
  785.     if (copy)
  786.     {
  787.         return [[NSString alloc] initWithBytes:subStr
  788.                                         length:subLen
  789.                                       encoding:NSUTF8StringEncoding];
  790.     }
  791.     else
  792.     {
  793.         // We can take advantage of the fact that __FILE__ is a string literal.
  794.         // Specifically, we don't need to waste time copying the string.
  795.         // We can just tell NSString to point to a range within the string literal.
  796.        
  797.         return [[NSString alloc] initWithBytesNoCopy:subStr
  798.                                               length:subLen
  799.                                             encoding:NSUTF8StringEncoding
  800.                                         freeWhenDone:NO];
  801.     }
  802. }
  803.  
  804. @end
  805.  
  806. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  807. #pragma mark -
  808. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  809.  
  810. @implementation DDLoggerNode
  811.  
  812. @synthesize logLevel;
  813.  
  814. - (instancetype)initWithLogger:(id <DDLogger>)aLogger loggerQueue:(dispatch_queue_t)aLoggerQueue logLevel:(int)aLogLevel
  815. {
  816.     if ((self = [super init]))
  817.     {
  818.         logger = aLogger;
  819.        
  820.         if (aLoggerQueue) {
  821.             loggerQueue = aLoggerQueue;
  822.             #if !OS_OBJECT_USE_OBJC
  823.             dispatch_retain(loggerQueue);
  824.             #endif
  825.         }
  826.         logLevel = aLogLevel;
  827.     }
  828.     return self;
  829. }
  830.  
  831. + (DDLoggerNode *)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue logLevel:(int)logLevel
  832. {
  833.     return [[DDLoggerNode alloc] initWithLogger:logger loggerQueue:loggerQueue logLevel:logLevel];
  834. }
  835.  
  836. - (void)dealloc
  837. {
  838.     #if !OS_OBJECT_USE_OBJC
  839.     if (loggerQueue) dispatch_release(loggerQueue);
  840.     #endif
  841. }
  842.  
  843. @end
  844.  
  845. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  846. #pragma mark -
  847. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  848.  
  849. @implementation DDLogMessage
  850.  
  851. static char *dd_str_copy(const char *str)
  852. {
  853.     if (str == NULL) return NULL;
  854.    
  855.     size_t length = strlen(str);
  856.     char * result = malloc(length + 1);
  857.     if (result == NULL) return NULL;
  858.     strncpy(result, str, length);
  859.     result[length] = 0;
  860.    
  861.     return result;
  862. }
  863.  
  864. - (instancetype)initWithLogMsg:(NSString *)msg
  865.                          level:(int)level
  866.                           flag:(int)flag
  867.                        context:(int)context
  868.                           file:(const char *)aFile
  869.                       function:(const char *)aFunction
  870.                           line:(int)line
  871.                            tag:(id)aTag
  872.                        options:(DDLogMessageOptions)optionsMask
  873. {
  874.     if ((self = [super init]))
  875.     {
  876.         logMsg     = msg;
  877.         logLevel   = level;
  878.         logFlag    = flag;
  879.         logContext = context;
  880.         lineNumber = line;
  881.         tag        = aTag;
  882.         options    = optionsMask;
  883.        
  884.         if (options & DDLogMessageCopyFile)
  885.             file = dd_str_copy(aFile);
  886.         else
  887.             file = (char *)aFile;
  888.        
  889.         if (options & DDLogMessageCopyFunction)
  890.             function = dd_str_copy(aFunction);
  891.         else
  892.             function = (char *)aFunction;
  893.        
  894.         timestamp = [[NSDate alloc] init];
  895.        
  896.         machThreadID = pthread_mach_thread_np(pthread_self());
  897.  
  898.         // Try to get the current queue's label
  899.        
  900.         // a) Compiling against newer SDK's (iOS 7+/OS X 10.9+) where DISPATCH_CURRENT_QUEUE_LABEL is defined
  901.         //    on a (iOS 7.0+/OS X 10.9+) runtime version
  902.         BOOL gotLabel = NO;
  903.         #ifdef DISPATCH_CURRENT_QUEUE_LABEL
  904.         if (
  905.             #if TARGET_OS_IPHONE
  906.                 #ifndef NSFoundationVersionNumber_iOS_6_1
  907.                 #define NSFoundationVersionNumber_iOS_6_1 993.00
  908.                 #endif
  909.                 floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1 // iOS 7+ (> iOS 6.1)
  910.             #else
  911.                 [[NSApplication sharedApplication] respondsToSelector:@selector(occlusionState)] // OS X 10.9+
  912.             #endif
  913.             ) {
  914.             queueLabel = dd_str_copy(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
  915.             gotLabel = YES;
  916.         }
  917.         #endif
  918.        
  919.         // b) Systems where dispatch_get_current_queue is not yet deprecated and won't crash (< iOS 6.0/OS X 10.9)
  920.         //    dispatch_get_current_queue(void); __OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_6,__MAC_10_9,__IPHONE_4_0,__IPHONE_6_0)
  921.         if (!gotLabel &&
  922.         #if TARGET_OS_IPHONE
  923.             #ifndef NSFoundationVersionNumber_iOS_6_0
  924.             #define NSFoundationVersionNumber_iOS_6_0 993.00
  925.             #endif
  926.             floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_6_0 // < iOS 6.0
  927.         #else
  928.             ![[NSApplication sharedApplication] respondsToSelector:@selector(occlusionState)] // < OS X 10.9
  929.         #endif
  930.             ) {
  931.             #pragma clang diagnostic push
  932.             #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  933.             dispatch_queue_t currentQueue = dispatch_get_current_queue();
  934.             #pragma clang diagnostic pop
  935.            
  936.             queueLabel = dd_str_copy(dispatch_queue_get_label(currentQueue));
  937.             gotLabel = YES;
  938.         }
  939.        
  940.         // c) Give up
  941.         if (!gotLabel) {
  942.             queueLabel = dd_str_copy(""); // iOS 6.x only
  943.         }
  944.        
  945.         threadName = [[NSThread currentThread] name];
  946.     }
  947.     return self;
  948. }
  949.  
  950. - (NSString *)threadID
  951. {
  952.     return [[NSString alloc] initWithFormat:@"%x", machThreadID];
  953. }
  954.  
  955. - (NSString *)fileName
  956. {
  957.     return DDExtractFileNameWithoutExtension(file, NO);
  958. }
  959.  
  960. - (NSString *)methodName
  961. {
  962.     if (function == NULL)
  963.         return nil;
  964.     else
  965.         return [[NSString alloc] initWithUTF8String:function];
  966. }
  967.  
  968. - (void)dealloc
  969. {
  970.     if (file && (options & DDLogMessageCopyFile))
  971.         free(file);
  972.    
  973.     if (function && (options & DDLogMessageCopyFunction))
  974.         free(function);
  975.    
  976.     if (queueLabel)
  977.         free(queueLabel);
  978. }
  979.  
  980.  
  981. - (id)copyWithZone:(NSZone *)zone {
  982.     DDLogMessage *newMessage = [[DDLogMessage alloc] init];
  983.    
  984.     newMessage->logLevel = self->logLevel;
  985.     newMessage->logFlag = self->logFlag;
  986.     newMessage->logContext = self->logContext;
  987.     newMessage->logMsg = self->logMsg;
  988.     newMessage->timestamp = self->timestamp;
  989.    
  990.     if (self->options & DDLogMessageCopyFile) {
  991.         newMessage->file = dd_str_copy(self->file);
  992.         newMessage->function = dd_str_copy(self->function);
  993.     } else {
  994.         newMessage->file = self->file;
  995.         newMessage->function = self->function;
  996.     }
  997.    
  998.     newMessage->lineNumber = self->lineNumber;
  999.    
  1000.     newMessage->machThreadID = self->machThreadID;
  1001.     newMessage->queueLabel = dd_str_copy(self->queueLabel);
  1002.     newMessage->threadName = self->threadName;
  1003.     newMessage->tag = self->tag;
  1004.     newMessage->options = self->options;
  1005.    
  1006.     return newMessage;
  1007. }
  1008.  
  1009. @end
  1010.  
  1011. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1012. #pragma mark -
  1013. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1014.  
  1015. @implementation DDAbstractLogger
  1016.  
  1017. - (id)init
  1018. {
  1019.     if ((self = [super init]))
  1020.     {
  1021.         const char *loggerQueueName = NULL;
  1022.         if ([self respondsToSelector:@selector(loggerName)])
  1023.         {
  1024.             loggerQueueName = [[self loggerName] UTF8String];
  1025.         }
  1026.        
  1027.         loggerQueue = dispatch_queue_create(loggerQueueName, NULL);
  1028.        
  1029.         // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue.
  1030.         // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue.
  1031.         // The documentation states:
  1032.         //
  1033.         // > Keys are only compared as pointers and are never dereferenced.
  1034.         // > Thus, you can use a pointer to a static variable for a specific subsystem or
  1035.         // > any other value that allows you to identify the value uniquely.
  1036.         // > Specifying a pointer to a string constant is not recommended.
  1037.         //
  1038.         // So we're going to use the very convenient key of "self",
  1039.         // which also works when multiple logger classes extend this class, as each will have a different "self" key.
  1040.         //
  1041.         // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below).
  1042.        
  1043.         void *key = (__bridge void *)self;
  1044.         void *nonNullValue = (__bridge void *)self;
  1045.        
  1046.         dispatch_queue_set_specific(loggerQueue, key, nonNullValue, NULL);
  1047.     }
  1048.     return self;
  1049. }
  1050.  
  1051. - (void)dealloc
  1052. {
  1053.     #if !OS_OBJECT_USE_OBJC
  1054.     if (loggerQueue) dispatch_release(loggerQueue);
  1055.     #endif
  1056. }
  1057.  
  1058. - (void)logMessage:(DDLogMessage *)logMessage
  1059. {
  1060.     // Override me
  1061. }
  1062.  
  1063. - (id <DDLogFormatter>)logFormatter
  1064. {
  1065.     // This method must be thread safe and intuitive.
  1066.     // Therefore if somebody executes the following code:
  1067.     //
  1068.     // [logger setLogFormatter:myFormatter];
  1069.     // formatter = [logger logFormatter];
  1070.     //
  1071.     // They would expect formatter to equal myFormatter.
  1072.     // This functionality must be ensured by the getter and setter method.
  1073.     //
  1074.     // The thread safety must not come at a cost to the performance of the logMessage method.
  1075.     // This method is likely called sporadically, while the logMessage method is called repeatedly.
  1076.     // This means, the implementation of this method:
  1077.     // - Must NOT require the logMessage method to acquire a lock.
  1078.     // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts).
  1079.     //
  1080.     // Thread safety is ensured by executing access to the formatter variable on the loggerQueue.
  1081.     // This is the same queue that the logMessage method operates on.
  1082.     //
  1083.     // Note: The last time I benchmarked the performance of direct access vs atomic property access,
  1084.     // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone.
  1085.     //
  1086.     // Furthermore, consider the following code:
  1087.     //
  1088.     // DDLogVerbose(@"log msg 1");
  1089.     // DDLogVerbose(@"log msg 2");
  1090.     // [logger setFormatter:myFormatter];
  1091.     // DDLogVerbose(@"log msg 3");
  1092.     //
  1093.     // Our intuitive requirement means that the new formatter will only apply to the 3rd log message.
  1094.     // This must remain true even when using asynchronous logging.
  1095.     // We must keep in mind the various queue's that are in play here:
  1096.     //
  1097.     // loggerQueue : Our own private internal queue that the logMessage method runs on.
  1098.     //               Operations are added to this queue from the global loggingQueue.
  1099.     //
  1100.     // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue.
  1101.     //
  1102.     // All log statements go through the serial gloabalLoggingQueue before they arrive at our loggerQueue.
  1103.     // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation.
  1104.    
  1105.     // IMPORTANT NOTE:
  1106.     //
  1107.     // Methods within the DDLogger implementation MUST access the formatter ivar directly.
  1108.     // This method is designed explicitly for external access.
  1109.     //
  1110.     // Using "self." syntax to go through this method will cause immediate deadlock.
  1111.     // This is the intended result. Fix it by accessing the ivar directly.
  1112.     // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  1113.    
  1114.     NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  1115.     NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  1116.    
  1117.     dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  1118.    
  1119.     __block id <DDLogFormatter> result;
  1120.    
  1121.     dispatch_sync(globalLoggingQueue, ^{
  1122.         dispatch_sync(loggerQueue, ^{
  1123.             result = formatter;
  1124.         });
  1125.     });
  1126.    
  1127.     return result;
  1128. }
  1129.  
  1130. - (void)setLogFormatter:(id <DDLogFormatter>)logFormatter
  1131. {
  1132.     // The design of this method is documented extensively in the logFormatter message (above in code).
  1133.    
  1134.     NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  1135.     NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  1136.    
  1137.     dispatch_block_t block = ^{ @autoreleasepool {
  1138.        
  1139.         if (formatter != logFormatter)
  1140.         {
  1141.             if ([formatter respondsToSelector:@selector(willRemoveFromLogger:)]) {
  1142.                 [formatter willRemoveFromLogger:self];
  1143.             }
  1144.            
  1145.             formatter = logFormatter;
  1146.            
  1147.             if ([formatter respondsToSelector:@selector(didAddToLogger:)]) {
  1148.                 [formatter didAddToLogger:self];
  1149.             }
  1150.         }
  1151.     }};
  1152.    
  1153.     dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  1154.    
  1155.     dispatch_async(globalLoggingQueue, ^{
  1156.         dispatch_async(loggerQueue, block);
  1157.     });
  1158. }
  1159.  
  1160. - (dispatch_queue_t)loggerQueue
  1161. {
  1162.     return loggerQueue;
  1163. }
  1164.  
  1165. - (NSString *)loggerName
  1166. {
  1167.     return NSStringFromClass([self class]);
  1168. }
  1169.  
  1170. - (BOOL)isOnGlobalLoggingQueue
  1171. {
  1172.     return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL);
  1173. }
  1174.  
  1175. - (BOOL)isOnInternalLoggerQueue
  1176. {
  1177.     void *key = (__bridge void *)self;
  1178.     return (dispatch_get_specific(key) != NULL);
  1179. }
  1180.  
  1181. @end
  1182.