Subversion Repositories Mobile Apps.GyroMouse

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 pmbaty 1
#import "DDAbstractDatabaseLogger.h"
2
#import <math.h>
3
 
4
/**
5
 * Welcome to Cocoa Lumberjack!
6
 *
7
 * The project page has a wealth of documentation if you have any questions.
8
 * https://github.com/CocoaLumberjack/CocoaLumberjack
9
 *
10
 * If you're new to the project you may wish to read the "Getting Started" wiki.
11
 * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted
12
**/
13
 
14
#if ! __has_feature(objc_arc)
15
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
16
#endif
17
 
18
@interface DDAbstractDatabaseLogger ()
19
- (void)destroySaveTimer;
20
- (void)destroyDeleteTimer;
21
@end
22
 
23
#pragma mark -
24
 
25
@implementation DDAbstractDatabaseLogger
26
 
27
- (id)init
28
{
29
    if ((self = [super init]))
30
    {
31
        saveThreshold = 500;
32
        saveInterval = 60;           // 60 seconds
33
        maxAge = (60 * 60 * 24 * 7); //  7 days
34
        deleteInterval = (60 * 5);   //  5 minutes
35
    }
36
    return self;
37
}
38
 
39
- (void)dealloc
40
{
41
    [self destroySaveTimer];
42
    [self destroyDeleteTimer];
43
 
44
}
45
 
46
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
47
#pragma mark Override Me
48
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
49
 
50
- (BOOL)db_log:(DDLogMessage *)logMessage
51
{
52
    // Override me and add your implementation.
53
    //
54
    // Return YES if an item was added to the buffer.
55
    // Return NO if the logMessage was ignored.
56
 
57
    return NO;
58
}
59
 
60
- (void)db_save
61
{
62
    // Override me and add your implementation.
63
}
64
 
65
- (void)db_delete
66
{
67
    // Override me and add your implementation.
68
}
69
 
70
- (void)db_saveAndDelete
71
{
72
    // Override me and add your implementation.
73
}
74
 
75
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
76
#pragma mark Private API
77
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
78
 
79
- (void)performSaveAndSuspendSaveTimer
80
{
81
    if (unsavedCount > 0)
82
    {
83
        if (deleteOnEverySave)
84
            [self db_saveAndDelete];
85
        else
86
            [self db_save];
87
    }
88
 
89
    unsavedCount = 0;
90
    unsavedTime = 0;
91
 
92
    if (saveTimer && !saveTimerSuspended)
93
    {
94
        dispatch_suspend(saveTimer);
95
        saveTimerSuspended = YES;
96
    }
97
}
98
 
99
- (void)performDelete
100
{
101
    if (maxAge > 0.0)
102
    {
103
        [self db_delete];
104
 
105
        lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0);
106
    }
107
}
108
 
109
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
110
#pragma mark Timers
111
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
112
 
113
- (void)destroySaveTimer
114
{
115
    if (saveTimer)
116
    {
117
        dispatch_source_cancel(saveTimer);
118
        if (saveTimerSuspended)
119
        {
120
            // Must resume a timer before releasing it (or it will crash)
121
            dispatch_resume(saveTimer);
122
            saveTimerSuspended = NO;
123
        }
124
        #if !OS_OBJECT_USE_OBJC
125
        dispatch_release(saveTimer);
126
        #endif
127
        saveTimer = NULL;
128
    }
129
}
130
 
131
- (void)updateAndResumeSaveTimer
132
{
133
    if ((saveTimer != NULL) && (saveInterval > 0.0) && (unsavedTime > 0.0))
134
    {
135
        uint64_t interval = (uint64_t)(saveInterval * NSEC_PER_SEC);
136
        dispatch_time_t startTime = dispatch_time(unsavedTime, interval);
137
 
138
        dispatch_source_set_timer(saveTimer, startTime, interval, 1.0);
139
 
140
        if (saveTimerSuspended)
141
        {
142
            dispatch_resume(saveTimer);
143
            saveTimerSuspended = NO;
144
        }
145
    }
146
}
147
 
148
- (void)createSuspendedSaveTimer
149
{
150
    if ((saveTimer == NULL) && (saveInterval > 0.0))
151
    {
152
        saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue);
153
 
154
        dispatch_source_set_event_handler(saveTimer, ^{ @autoreleasepool {
155
 
156
            [self performSaveAndSuspendSaveTimer];
157
 
158
        }});
159
 
160
        saveTimerSuspended = YES;
161
    }
162
}
163
 
164
- (void)destroyDeleteTimer
165
{
166
    if (deleteTimer)
167
    {
168
        dispatch_source_cancel(deleteTimer);
169
        #if !OS_OBJECT_USE_OBJC
170
        dispatch_release(deleteTimer);
171
        #endif
172
        deleteTimer = NULL;
173
    }
174
}
175
 
176
- (void)updateDeleteTimer
177
{
178
    if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxAge > 0.0))
179
    {
180
        uint64_t interval = (uint64_t)(deleteInterval * NSEC_PER_SEC);
181
        dispatch_time_t startTime;
182
 
183
        if (lastDeleteTime > 0)
184
            startTime = dispatch_time(lastDeleteTime, interval);
185
        else
186
            startTime = dispatch_time(DISPATCH_TIME_NOW, interval);
187
 
188
        dispatch_source_set_timer(deleteTimer, startTime, interval, 1.0);
189
    }
190
}
191
 
192
- (void)createAndStartDeleteTimer
193
{
194
    if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxAge > 0.0))
195
    {
196
        deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue);
197
 
198
        if (deleteTimer != NULL) {
199
            dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool {
200
 
201
                [self performDelete];
202
 
203
            }});
204
 
205
            [self updateDeleteTimer];
206
 
207
            if (deleteTimer != NULL) dispatch_resume(deleteTimer);
208
        }
209
    }
210
}
211
 
212
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
213
#pragma mark Configuration
214
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
215
 
216
- (NSUInteger)saveThreshold
217
{
218
    // The design of this method is taken from the DDAbstractLogger implementation.
219
    // For extensive documentation please refer to the DDAbstractLogger implementation.
220
 
221
    // Note: The internal implementation MUST access the colorsEnabled variable directly,
222
    // This method is designed explicitly for external access.
223
    //
224
    // Using "self." syntax to go through this method will cause immediate deadlock.
225
    // This is the intended result. Fix it by accessing the ivar directly.
226
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
227
 
228
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
229
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
230
 
231
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
232
 
233
    __block NSUInteger result;
234
 
235
    dispatch_sync(globalLoggingQueue, ^{
236
        dispatch_sync(loggerQueue, ^{
237
            result = saveThreshold;
238
        });
239
    });
240
 
241
    return result;
242
}
243
 
244
- (void)setSaveThreshold:(NSUInteger)threshold
245
{
246
    dispatch_block_t block = ^{ @autoreleasepool {
247
 
248
        if (saveThreshold != threshold)
249
        {
250
            saveThreshold = threshold;
251
 
252
            // Since the saveThreshold has changed,
253
            // we check to see if the current unsavedCount has surpassed the new threshold.
254
            //
255
            // If it has, we immediately save the log.
256
 
257
            if ((unsavedCount >= saveThreshold) && (saveThreshold > 0))
258
            {
259
                [self performSaveAndSuspendSaveTimer];
260
            }
261
        }
262
    }};
263
 
264
    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
265
    // For documentation please refer to the DDAbstractLogger implementation.
266
 
267
    if ([self isOnInternalLoggerQueue])
268
    {
269
        block();
270
    }
271
    else
272
    {
273
        dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
274
        NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
275
 
276
        dispatch_async(globalLoggingQueue, ^{
277
            dispatch_async(loggerQueue, block);
278
        });
279
    }
280
}
281
 
282
- (NSTimeInterval)saveInterval
283
{
284
    // The design of this method is taken from the DDAbstractLogger implementation.
285
    // For extensive documentation please refer to the DDAbstractLogger implementation.
286
 
287
    // Note: The internal implementation MUST access the colorsEnabled variable directly,
288
    // This method is designed explicitly for external access.
289
    //
290
    // Using "self." syntax to go through this method will cause immediate deadlock.
291
    // This is the intended result. Fix it by accessing the ivar directly.
292
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
293
 
294
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
295
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
296
 
297
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
298
 
299
    __block NSTimeInterval result;
300
 
301
    dispatch_sync(globalLoggingQueue, ^{
302
        dispatch_sync(loggerQueue, ^{
303
            result = saveInterval;
304
        });
305
    });
306
 
307
    return result;
308
}
309
 
310
- (void)setSaveInterval:(NSTimeInterval)interval
311
{
312
    dispatch_block_t block = ^{ @autoreleasepool {
313
 
314
        // C99 recommended floating point comparison macro
315
        // Read: isLessThanOrGreaterThan(floatA, floatB)
316
 
317
        if (/* saveInterval != interval */ islessgreater(saveInterval, interval))
318
        {
319
            saveInterval = interval;
320
 
321
            // There are several cases we need to handle here.
322
            //
323
            // 1. If the saveInterval was previously enabled and it just got disabled,
324
            //    then we need to stop the saveTimer. (And we might as well release it.)
325
            //
326
            // 2. If the saveInterval was previously disabled and it just got enabled,
327
            //    then we need to setup the saveTimer. (Plus we might need to do an immediate save.)
328
            //
329
            // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date.
330
            //
331
            // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date.
332
            //    (Plus we might need to do an immediate save.)
333
 
334
            if (saveInterval > 0.0)
335
            {
336
                if (saveTimer == NULL)
337
                {
338
                    // Handles #2
339
                    //
340
                    // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
341
                    // if a save is needed the timer will fire immediately.
342
 
343
                    [self createSuspendedSaveTimer];
344
                    [self updateAndResumeSaveTimer];
345
                }
346
                else
347
                {
348
                    // Handles #3
349
                    // Handles #4
350
                    //
351
                    // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
352
                    // if a save is needed the timer will fire immediately.
353
 
354
                    [self updateAndResumeSaveTimer];
355
                }
356
            }
357
            else if (saveTimer)
358
            {
359
                // Handles #1
360
 
361
                [self destroySaveTimer];
362
            }
363
        }
364
    }};
365
 
366
    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
367
    // For documentation please refer to the DDAbstractLogger implementation.
368
 
369
    if ([self isOnInternalLoggerQueue])
370
    {
371
        block();
372
    }
373
    else
374
    {
375
        dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
376
        NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
377
 
378
        dispatch_async(globalLoggingQueue, ^{
379
            dispatch_async(loggerQueue, block);
380
        });
381
    }
382
}
383
 
384
- (NSTimeInterval)maxAge
385
{
386
    // The design of this method is taken from the DDAbstractLogger implementation.
387
    // For extensive documentation please refer to the DDAbstractLogger implementation.
388
 
389
    // Note: The internal implementation MUST access the colorsEnabled variable directly,
390
    // This method is designed explicitly for external access.
391
    //
392
    // Using "self." syntax to go through this method will cause immediate deadlock.
393
    // This is the intended result. Fix it by accessing the ivar directly.
394
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
395
 
396
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
397
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
398
 
399
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
400
 
401
    __block NSTimeInterval result;
402
 
403
    dispatch_sync(globalLoggingQueue, ^{
404
        dispatch_sync(loggerQueue, ^{
405
            result = maxAge;
406
        });
407
    });
408
 
409
    return result;
410
}
411
 
412
- (void)setMaxAge:(NSTimeInterval)interval
413
{
414
    dispatch_block_t block = ^{ @autoreleasepool {
415
 
416
        // C99 recommended floating point comparison macro
417
        // Read: isLessThanOrGreaterThan(floatA, floatB)
418
 
419
        if (/* maxAge != interval */ islessgreater(maxAge, interval))
420
        {
421
            NSTimeInterval oldMaxAge = maxAge;
422
            NSTimeInterval newMaxAge = interval;
423
 
424
            maxAge = interval;
425
 
426
            // There are several cases we need to handle here.
427
            //
428
            // 1. If the maxAge was previously enabled and it just got disabled,
429
            //    then we need to stop the deleteTimer. (And we might as well release it.)
430
            //
431
            // 2. If the maxAge was previously disabled and it just got enabled,
432
            //    then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
433
            //
434
            // 3. If the maxAge was increased,
435
            //    then we don't need to do anything.
436
            //
437
            // 4. If the maxAge was decreased,
438
            //    then we should do an immediate delete.
439
 
440
            BOOL shouldDeleteNow = NO;
441
 
442
            if (oldMaxAge > 0.0)
443
            {
444
                if (newMaxAge <= 0.0)
445
                {
446
                    // Handles #1
447
 
448
                    [self destroyDeleteTimer];
449
                }
450
                else if (oldMaxAge > newMaxAge)
451
                {
452
                    // Handles #4
453
                    shouldDeleteNow = YES;
454
                }
455
            }
456
            else if (newMaxAge > 0.0)
457
            {
458
                // Handles #2
459
                shouldDeleteNow = YES;
460
            }
461
 
462
            if (shouldDeleteNow)
463
            {
464
                [self performDelete];
465
 
466
                if (deleteTimer)
467
                    [self updateDeleteTimer];
468
                else
469
                    [self createAndStartDeleteTimer];
470
            }
471
        }
472
    }};
473
 
474
    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
475
    // For documentation please refer to the DDAbstractLogger implementation.
476
 
477
    if ([self isOnInternalLoggerQueue])
478
    {
479
        block();
480
    }
481
    else
482
    {
483
        dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
484
        NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
485
 
486
        dispatch_async(globalLoggingQueue, ^{
487
            dispatch_async(loggerQueue, block);
488
        });
489
    }
490
}
491
 
492
- (NSTimeInterval)deleteInterval
493
{
494
    // The design of this method is taken from the DDAbstractLogger implementation.
495
    // For extensive documentation please refer to the DDAbstractLogger implementation.
496
 
497
    // Note: The internal implementation MUST access the colorsEnabled variable directly,
498
    // This method is designed explicitly for external access.
499
    //
500
    // Using "self." syntax to go through this method will cause immediate deadlock.
501
    // This is the intended result. Fix it by accessing the ivar directly.
502
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
503
 
504
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
505
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
506
 
507
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
508
 
509
    __block NSTimeInterval result;
510
 
511
    dispatch_sync(globalLoggingQueue, ^{
512
        dispatch_sync(loggerQueue, ^{
513
            result = deleteInterval;
514
        });
515
    });
516
 
517
    return result;
518
}
519
 
520
- (void)setDeleteInterval:(NSTimeInterval)interval
521
{
522
    dispatch_block_t block = ^{ @autoreleasepool {
523
 
524
        // C99 recommended floating point comparison macro
525
        // Read: isLessThanOrGreaterThan(floatA, floatB)
526
 
527
        if (/* deleteInterval != interval */ islessgreater(deleteInterval, interval))
528
        {
529
            deleteInterval = interval;
530
 
531
            // There are several cases we need to handle here.
532
            //
533
            // 1. If the deleteInterval was previously enabled and it just got disabled,
534
            //    then we need to stop the deleteTimer. (And we might as well release it.)
535
            //
536
            // 2. If the deleteInterval was previously disabled and it just got enabled,
537
            //    then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
538
            //
539
            // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date.
540
            //
541
            // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date.
542
            //    (Plus we might need to do an immediate delete.)
543
 
544
            if (deleteInterval > 0.0)
545
            {
546
                if (deleteTimer == NULL)
547
                {
548
                    // Handles #2
549
                    //
550
                    // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
551
                    // if a delete is needed the timer will fire immediately.
552
 
553
                    [self createAndStartDeleteTimer];
554
                }
555
                else
556
                {
557
                    // Handles #3
558
                    // Handles #4
559
                    //
560
                    // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
561
                    // if a save is needed the timer will fire immediately.
562
 
563
                    [self updateDeleteTimer];
564
                }
565
            }
566
            else if (deleteTimer)
567
            {
568
                // Handles #1
569
 
570
                [self destroyDeleteTimer];
571
            }
572
        }
573
    }};
574
 
575
    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
576
    // For documentation please refer to the DDAbstractLogger implementation.
577
 
578
    if ([self isOnInternalLoggerQueue])
579
    {
580
        block();
581
    }
582
    else
583
    {
584
        dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
585
        NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
586
 
587
        dispatch_async(globalLoggingQueue, ^{
588
            dispatch_async(loggerQueue, block);
589
        });
590
    }
591
}
592
 
593
- (BOOL)deleteOnEverySave
594
{
595
    // The design of this method is taken from the DDAbstractLogger implementation.
596
    // For extensive documentation please refer to the DDAbstractLogger implementation.
597
 
598
    // Note: The internal implementation MUST access the colorsEnabled variable directly,
599
    // This method is designed explicitly for external access.
600
    //
601
    // Using "self." syntax to go through this method will cause immediate deadlock.
602
    // This is the intended result. Fix it by accessing the ivar directly.
603
    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
604
 
605
    NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
606
    NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
607
 
608
    dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
609
 
610
    __block BOOL result;
611
 
612
    dispatch_sync(globalLoggingQueue, ^{
613
        dispatch_sync(loggerQueue, ^{
614
            result = deleteOnEverySave;
615
        });
616
    });
617
 
618
    return result;
619
}
620
 
621
- (void)setDeleteOnEverySave:(BOOL)flag
622
{
623
    dispatch_block_t block = ^{
624
 
625
        deleteOnEverySave = flag;
626
    };
627
 
628
    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
629
    // For documentation please refer to the DDAbstractLogger implementation.
630
 
631
    if ([self isOnInternalLoggerQueue])
632
    {
633
        block();
634
    }
635
    else
636
    {
637
        dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
638
        NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
639
 
640
        dispatch_async(globalLoggingQueue, ^{
641
            dispatch_async(loggerQueue, block);
642
        });
643
    }
644
}
645
 
646
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
647
#pragma mark Public API
648
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
649
 
650
- (void)savePendingLogEntries
651
{
652
    dispatch_block_t block = ^{ @autoreleasepool {
653
 
654
        [self performSaveAndSuspendSaveTimer];
655
    }};
656
 
657
    if ([self isOnInternalLoggerQueue])
658
        block();
659
    else
660
        dispatch_async(loggerQueue, block);
661
}
662
 
663
- (void)deleteOldLogEntries
664
{
665
    dispatch_block_t block = ^{ @autoreleasepool {
666
 
667
        [self performDelete];
668
    }};
669
 
670
    if ([self isOnInternalLoggerQueue])
671
        block();
672
    else
673
        dispatch_async(loggerQueue, block);
674
}
675
 
676
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
677
#pragma mark DDLogger
678
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
679
 
680
- (void)didAddLogger
681
{
682
    // If you override me be sure to invoke [super didAddLogger];
683
 
684
    [self createSuspendedSaveTimer];
685
 
686
    [self createAndStartDeleteTimer];
687
}
688
 
689
- (void)willRemoveLogger
690
{
691
    // If you override me be sure to invoke [super willRemoveLogger];
692
 
693
    [self performSaveAndSuspendSaveTimer];
694
 
695
    [self destroySaveTimer];
696
    [self destroyDeleteTimer];
697
}
698
 
699
- (void)logMessage:(DDLogMessage *)logMessage
700
{
701
    if ([self db_log:logMessage])
702
    {
703
        BOOL firstUnsavedEntry = (++unsavedCount == 1);
704
 
705
        if ((unsavedCount >= saveThreshold) && (saveThreshold > 0))
706
        {
707
            [self performSaveAndSuspendSaveTimer];
708
        }
709
        else if (firstUnsavedEntry)
710
        {
711
            unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0);
712
            [self updateAndResumeSaveTimer];
713
        }
714
    }
715
}
716
 
717
- (void)flush
718
{
719
    // This method is invoked by DDLog's flushLog method.
720
    //
721
    // It is called automatically when the application quits,
722
    // or if the developer invokes DDLog's flushLog method prior to crashing or something.
723
 
724
    [self performSaveAndSuspendSaveTimer];
725
}
726
 
727
@end