#import "DDAbstractDatabaseLogger.h" #import /** * Welcome to Cocoa Lumberjack! * * The project page has a wealth of documentation if you have any questions. * https://github.com/robbiehanson/CocoaLumberjack * * If you're new to the project you may wish to read the "Getting Started" wiki. * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted **/ #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #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]; 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