Subversion Repositories Mobile Apps.GyroMouse

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 pmbaty 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