| #import "DDAbstractDatabaseLogger.h" |
| #import <math.h> |
| |
| /** |
| * Welcome to Cocoa Lumberjack! |
| * |
| * The project page has a wealth of documentation if you have any questions. |
| * https://github.com/CocoaLumberjack/CocoaLumberjack |
| * |
| * If you're new to the project you may wish to read the "Getting Started" wiki. |
| * https://github.com/CocoaLumberjack/CocoaLumberjack/wiki/GettingStarted |
| **/ |
| |
| #if ! __has_feature(objc_arc) |
| #error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). |
| #endif |
| |
| @interface DDAbstractDatabaseLogger () |
| - (void)destroySaveTimer; |
| - (void)destroyDeleteTimer; |
| @end |
| |
| #pragma mark - |
| |
| @implementation DDAbstractDatabaseLogger |
| |
| - (id)init |
| { |
| if ((self = [super init])) |
| { |
| saveThreshold = 500; |
| saveInterval = 60; // 60 seconds |
| maxAge = (60 * 60 * 24 * 7); // 7 days |
| deleteInterval = (60 * 5); // 5 minutes |
| } |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| [self destroySaveTimer]; |
| [self destroyDeleteTimer]; |
| |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark Override Me |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| - (BOOL)db_log:(DDLogMessage *)logMessage |
| { |
| // Override me and add your implementation. |
| // |
| // Return YES if an item was added to the buffer. |
| // Return NO if the logMessage was ignored. |
| |
| return NO; |
| } |
| |
| - (void)db_save |
| { |
| // Override me and add your implementation. |
| } |
| |
| - (void)db_delete |
| { |
| // Override me and add your implementation. |
| } |
| |
| - (void)db_saveAndDelete |
| { |
| // Override me and add your implementation. |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark Private API |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| - (void)performSaveAndSuspendSaveTimer |
| { |
| if (unsavedCount > 0) |
| { |
| if (deleteOnEverySave) |
| [self db_saveAndDelete]; |
| else |
| [self db_save]; |
| } |
| |
| unsavedCount = 0; |
| unsavedTime = 0; |
| |
| if (saveTimer && !saveTimerSuspended) |
| { |
| dispatch_suspend(saveTimer); |
| saveTimerSuspended = YES; |
| } |
| } |
| |
| - (void)performDelete |
| { |
| if (maxAge > 0.0) |
| { |
| [self db_delete]; |
| |
| lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark Timers |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| - (void)destroySaveTimer |
| { |
| if (saveTimer) |
| { |
| dispatch_source_cancel(saveTimer); |
| if (saveTimerSuspended) |
| { |
| // Must resume a timer before releasing it (or it will crash) |
| dispatch_resume(saveTimer); |
| saveTimerSuspended = NO; |
| } |
| #if !OS_OBJECT_USE_OBJC |
| dispatch_release(saveTimer); |
| #endif |
| saveTimer = NULL; |
| } |
| } |
| |
| - (void)updateAndResumeSaveTimer |
| { |
| if ((saveTimer != NULL) && (saveInterval > 0.0) && (unsavedTime > 0.0)) |
| { |
| uint64_t interval = (uint64_t)(saveInterval * NSEC_PER_SEC); |
| dispatch_time_t startTime = dispatch_time(unsavedTime, interval); |
| |
| dispatch_source_set_timer(saveTimer, startTime, interval, 1.0); |
| |
| if (saveTimerSuspended) |
| { |
| dispatch_resume(saveTimer); |
| saveTimerSuspended = NO; |
| } |
| } |
| } |
| |
| - (void)createSuspendedSaveTimer |
| { |
| if ((saveTimer == NULL) && (saveInterval > 0.0)) |
| { |
| saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); |
| |
| dispatch_source_set_event_handler(saveTimer, ^{ @autoreleasepool { |
| |
| [self performSaveAndSuspendSaveTimer]; |
| |
| }}); |
| |
| saveTimerSuspended = YES; |
| } |
| } |
| |
| - (void)destroyDeleteTimer |
| { |
| if (deleteTimer) |
| { |
| dispatch_source_cancel(deleteTimer); |
| #if !OS_OBJECT_USE_OBJC |
| dispatch_release(deleteTimer); |
| #endif |
| deleteTimer = NULL; |
| } |
| } |
| |
| - (void)updateDeleteTimer |
| { |
| if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) |
| { |
| uint64_t interval = (uint64_t)(deleteInterval * NSEC_PER_SEC); |
| dispatch_time_t startTime; |
| |
| if (lastDeleteTime > 0) |
| startTime = dispatch_time(lastDeleteTime, interval); |
| else |
| startTime = dispatch_time(DISPATCH_TIME_NOW, interval); |
| |
| dispatch_source_set_timer(deleteTimer, startTime, interval, 1.0); |
| } |
| } |
| |
| - (void)createAndStartDeleteTimer |
| { |
| if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxAge > 0.0)) |
| { |
| deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue); |
| |
| if (deleteTimer != NULL) { |
| dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool { |
| |
| [self performDelete]; |
| |
| }}); |
| |
| [self updateDeleteTimer]; |
| |
| if (deleteTimer != NULL) dispatch_resume(deleteTimer); |
| } |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark Configuration |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| - (NSUInteger)saveThreshold |
| { |
| // The design of this method is taken from the DDAbstractLogger implementation. |
| // For extensive documentation please refer to the DDAbstractLogger implementation. |
| |
| // Note: The internal implementation MUST access the colorsEnabled variable directly, |
| // This method is designed explicitly for external access. |
| // |
| // Using "self." syntax to go through this method will cause immediate deadlock. |
| // This is the intended result. Fix it by accessing the ivar directly. |
| // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. |
| |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); |
| |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| |
| __block NSUInteger result; |
| |
| dispatch_sync(globalLoggingQueue, ^{ |
| dispatch_sync(loggerQueue, ^{ |
| result = saveThreshold; |
| }); |
| }); |
| |
| return result; |
| } |
| |
| - (void)setSaveThreshold:(NSUInteger)threshold |
| { |
| dispatch_block_t block = ^{ @autoreleasepool { |
| |
| if (saveThreshold != threshold) |
| { |
| saveThreshold = threshold; |
| |
| // Since the saveThreshold has changed, |
| // we check to see if the current unsavedCount has surpassed the new threshold. |
| // |
| // If it has, we immediately save the log. |
| |
| if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) |
| { |
| [self performSaveAndSuspendSaveTimer]; |
| } |
| } |
| }}; |
| |
| // The design of the setter logic below is taken from the DDAbstractLogger implementation. |
| // For documentation please refer to the DDAbstractLogger implementation. |
| |
| if ([self isOnInternalLoggerQueue]) |
| { |
| block(); |
| } |
| else |
| { |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| |
| dispatch_async(globalLoggingQueue, ^{ |
| dispatch_async(loggerQueue, block); |
| }); |
| } |
| } |
| |
| - (NSTimeInterval)saveInterval |
| { |
| // The design of this method is taken from the DDAbstractLogger implementation. |
| // For extensive documentation please refer to the DDAbstractLogger implementation. |
| |
| // Note: The internal implementation MUST access the colorsEnabled variable directly, |
| // This method is designed explicitly for external access. |
| // |
| // Using "self." syntax to go through this method will cause immediate deadlock. |
| // This is the intended result. Fix it by accessing the ivar directly. |
| // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. |
| |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); |
| |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| |
| __block NSTimeInterval result; |
| |
| dispatch_sync(globalLoggingQueue, ^{ |
| dispatch_sync(loggerQueue, ^{ |
| result = saveInterval; |
| }); |
| }); |
| |
| return result; |
| } |
| |
| - (void)setSaveInterval:(NSTimeInterval)interval |
| { |
| dispatch_block_t block = ^{ @autoreleasepool { |
| |
| // C99 recommended floating point comparison macro |
| // Read: isLessThanOrGreaterThan(floatA, floatB) |
| |
| if (/* saveInterval != interval */ islessgreater(saveInterval, interval)) |
| { |
| saveInterval = interval; |
| |
| // There are several cases we need to handle here. |
| // |
| // 1. If the saveInterval was previously enabled and it just got disabled, |
| // then we need to stop the saveTimer. (And we might as well release it.) |
| // |
| // 2. If the saveInterval was previously disabled and it just got enabled, |
| // then we need to setup the saveTimer. (Plus we might need to do an immediate save.) |
| // |
| // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date. |
| // |
| // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date. |
| // (Plus we might need to do an immediate save.) |
| |
| if (saveInterval > 0.0) |
| { |
| if (saveTimer == NULL) |
| { |
| // Handles #2 |
| // |
| // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, |
| // if a save is needed the timer will fire immediately. |
| |
| [self createSuspendedSaveTimer]; |
| [self updateAndResumeSaveTimer]; |
| } |
| else |
| { |
| // Handles #3 |
| // Handles #4 |
| // |
| // Since the saveTimer uses the unsavedTime to calculate it's first fireDate, |
| // if a save is needed the timer will fire immediately. |
| |
| [self updateAndResumeSaveTimer]; |
| } |
| } |
| else if (saveTimer) |
| { |
| // Handles #1 |
| |
| [self destroySaveTimer]; |
| } |
| } |
| }}; |
| |
| // The design of the setter logic below is taken from the DDAbstractLogger implementation. |
| // For documentation please refer to the DDAbstractLogger implementation. |
| |
| if ([self isOnInternalLoggerQueue]) |
| { |
| block(); |
| } |
| else |
| { |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| |
| dispatch_async(globalLoggingQueue, ^{ |
| dispatch_async(loggerQueue, block); |
| }); |
| } |
| } |
| |
| - (NSTimeInterval)maxAge |
| { |
| // The design of this method is taken from the DDAbstractLogger implementation. |
| // For extensive documentation please refer to the DDAbstractLogger implementation. |
| |
| // Note: The internal implementation MUST access the colorsEnabled variable directly, |
| // This method is designed explicitly for external access. |
| // |
| // Using "self." syntax to go through this method will cause immediate deadlock. |
| // This is the intended result. Fix it by accessing the ivar directly. |
| // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. |
| |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); |
| |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| |
| __block NSTimeInterval result; |
| |
| dispatch_sync(globalLoggingQueue, ^{ |
| dispatch_sync(loggerQueue, ^{ |
| result = maxAge; |
| }); |
| }); |
| |
| return result; |
| } |
| |
| - (void)setMaxAge:(NSTimeInterval)interval |
| { |
| dispatch_block_t block = ^{ @autoreleasepool { |
| |
| // C99 recommended floating point comparison macro |
| // Read: isLessThanOrGreaterThan(floatA, floatB) |
| |
| if (/* maxAge != interval */ islessgreater(maxAge, interval)) |
| { |
| NSTimeInterval oldMaxAge = maxAge; |
| NSTimeInterval newMaxAge = interval; |
| |
| maxAge = interval; |
| |
| // There are several cases we need to handle here. |
| // |
| // 1. If the maxAge was previously enabled and it just got disabled, |
| // then we need to stop the deleteTimer. (And we might as well release it.) |
| // |
| // 2. If the maxAge was previously disabled and it just got enabled, |
| // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) |
| // |
| // 3. If the maxAge was increased, |
| // then we don't need to do anything. |
| // |
| // 4. If the maxAge was decreased, |
| // then we should do an immediate delete. |
| |
| BOOL shouldDeleteNow = NO; |
| |
| if (oldMaxAge > 0.0) |
| { |
| if (newMaxAge <= 0.0) |
| { |
| // Handles #1 |
| |
| [self destroyDeleteTimer]; |
| } |
| else if (oldMaxAge > newMaxAge) |
| { |
| // Handles #4 |
| shouldDeleteNow = YES; |
| } |
| } |
| else if (newMaxAge > 0.0) |
| { |
| // Handles #2 |
| shouldDeleteNow = YES; |
| } |
| |
| if (shouldDeleteNow) |
| { |
| [self performDelete]; |
| |
| if (deleteTimer) |
| [self updateDeleteTimer]; |
| else |
| [self createAndStartDeleteTimer]; |
| } |
| } |
| }}; |
| |
| // The design of the setter logic below is taken from the DDAbstractLogger implementation. |
| // For documentation please refer to the DDAbstractLogger implementation. |
| |
| if ([self isOnInternalLoggerQueue]) |
| { |
| block(); |
| } |
| else |
| { |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| |
| dispatch_async(globalLoggingQueue, ^{ |
| dispatch_async(loggerQueue, block); |
| }); |
| } |
| } |
| |
| - (NSTimeInterval)deleteInterval |
| { |
| // The design of this method is taken from the DDAbstractLogger implementation. |
| // For extensive documentation please refer to the DDAbstractLogger implementation. |
| |
| // Note: The internal implementation MUST access the colorsEnabled variable directly, |
| // This method is designed explicitly for external access. |
| // |
| // Using "self." syntax to go through this method will cause immediate deadlock. |
| // This is the intended result. Fix it by accessing the ivar directly. |
| // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. |
| |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); |
| |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| |
| __block NSTimeInterval result; |
| |
| dispatch_sync(globalLoggingQueue, ^{ |
| dispatch_sync(loggerQueue, ^{ |
| result = deleteInterval; |
| }); |
| }); |
| |
| return result; |
| } |
| |
| - (void)setDeleteInterval:(NSTimeInterval)interval |
| { |
| dispatch_block_t block = ^{ @autoreleasepool { |
| |
| // C99 recommended floating point comparison macro |
| // Read: isLessThanOrGreaterThan(floatA, floatB) |
| |
| if (/* deleteInterval != interval */ islessgreater(deleteInterval, interval)) |
| { |
| deleteInterval = interval; |
| |
| // There are several cases we need to handle here. |
| // |
| // 1. If the deleteInterval was previously enabled and it just got disabled, |
| // then we need to stop the deleteTimer. (And we might as well release it.) |
| // |
| // 2. If the deleteInterval was previously disabled and it just got enabled, |
| // then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.) |
| // |
| // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date. |
| // |
| // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date. |
| // (Plus we might need to do an immediate delete.) |
| |
| if (deleteInterval > 0.0) |
| { |
| if (deleteTimer == NULL) |
| { |
| // Handles #2 |
| // |
| // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, |
| // if a delete is needed the timer will fire immediately. |
| |
| [self createAndStartDeleteTimer]; |
| } |
| else |
| { |
| // Handles #3 |
| // Handles #4 |
| // |
| // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate, |
| // if a save is needed the timer will fire immediately. |
| |
| [self updateDeleteTimer]; |
| } |
| } |
| else if (deleteTimer) |
| { |
| // Handles #1 |
| |
| [self destroyDeleteTimer]; |
| } |
| } |
| }}; |
| |
| // The design of the setter logic below is taken from the DDAbstractLogger implementation. |
| // For documentation please refer to the DDAbstractLogger implementation. |
| |
| if ([self isOnInternalLoggerQueue]) |
| { |
| block(); |
| } |
| else |
| { |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| |
| dispatch_async(globalLoggingQueue, ^{ |
| dispatch_async(loggerQueue, block); |
| }); |
| } |
| } |
| |
| - (BOOL)deleteOnEverySave |
| { |
| // The design of this method is taken from the DDAbstractLogger implementation. |
| // For extensive documentation please refer to the DDAbstractLogger implementation. |
| |
| // Note: The internal implementation MUST access the colorsEnabled variable directly, |
| // This method is designed explicitly for external access. |
| // |
| // Using "self." syntax to go through this method will cause immediate deadlock. |
| // This is the intended result. Fix it by accessing the ivar directly. |
| // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster. |
| |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax."); |
| |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| |
| __block BOOL result; |
| |
| dispatch_sync(globalLoggingQueue, ^{ |
| dispatch_sync(loggerQueue, ^{ |
| result = deleteOnEverySave; |
| }); |
| }); |
| |
| return result; |
| } |
| |
| - (void)setDeleteOnEverySave:(BOOL)flag |
| { |
| dispatch_block_t block = ^{ |
| |
| deleteOnEverySave = flag; |
| }; |
| |
| // The design of the setter logic below is taken from the DDAbstractLogger implementation. |
| // For documentation please refer to the DDAbstractLogger implementation. |
| |
| if ([self isOnInternalLoggerQueue]) |
| { |
| block(); |
| } |
| else |
| { |
| dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue]; |
| NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure"); |
| |
| dispatch_async(globalLoggingQueue, ^{ |
| dispatch_async(loggerQueue, block); |
| }); |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark Public API |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| - (void)savePendingLogEntries |
| { |
| dispatch_block_t block = ^{ @autoreleasepool { |
| |
| [self performSaveAndSuspendSaveTimer]; |
| }}; |
| |
| if ([self isOnInternalLoggerQueue]) |
| block(); |
| else |
| dispatch_async(loggerQueue, block); |
| } |
| |
| - (void)deleteOldLogEntries |
| { |
| dispatch_block_t block = ^{ @autoreleasepool { |
| |
| [self performDelete]; |
| }}; |
| |
| if ([self isOnInternalLoggerQueue]) |
| block(); |
| else |
| dispatch_async(loggerQueue, block); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| #pragma mark DDLogger |
| //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
| |
| - (void)didAddLogger |
| { |
| // If you override me be sure to invoke [super didAddLogger]; |
| |
| [self createSuspendedSaveTimer]; |
| |
| [self createAndStartDeleteTimer]; |
| } |
| |
| - (void)willRemoveLogger |
| { |
| // If you override me be sure to invoke [super willRemoveLogger]; |
| |
| [self performSaveAndSuspendSaveTimer]; |
| |
| [self destroySaveTimer]; |
| [self destroyDeleteTimer]; |
| } |
| |
| - (void)logMessage:(DDLogMessage *)logMessage |
| { |
| if ([self db_log:logMessage]) |
| { |
| BOOL firstUnsavedEntry = (++unsavedCount == 1); |
| |
| if ((unsavedCount >= saveThreshold) && (saveThreshold > 0)) |
| { |
| [self performSaveAndSuspendSaveTimer]; |
| } |
| else if (firstUnsavedEntry) |
| { |
| unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0); |
| [self updateAndResumeSaveTimer]; |
| } |
| } |
| } |
| |
| - (void)flush |
| { |
| // This method is invoked by DDLog's flushLog method. |
| // |
| // It is called automatically when the application quits, |
| // or if the developer invokes DDLog's flushLog method prior to crashing or something. |
| |
| [self performSaveAndSuspendSaveTimer]; |
| } |
| |
| @end |