#import "DDFileLogger.h" #import #import #import #import /** * 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) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // We probably shouldn't be using DDLog() statements within the DDLog implementation. // But we still want to leave our log statements for any future debugging, // and to allow other developers to trace the implementation (which is a great learning tool). // // So we use primitive logging macros around NSLog. // We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog. #define LOG_LEVEL 2 #define NSLogError(frmt, ...) do{ if(LOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0) #define NSLogWarn(frmt, ...) do{ if(LOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0) #define NSLogInfo(frmt, ...) do{ if(LOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0) #define NSLogDebug(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0) #define NSLogVerbose(frmt, ...) do{ if(LOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0) @interface DDLogFileManagerDefault (PrivateAPI) - (void)deleteOldLogFiles; - (NSString *)defaultLogsDirectory; @end @interface DDFileLogger (PrivateAPI) - (void)rollLogFileNow; - (void)maybeRollLogFileDueToAge; - (void)maybeRollLogFileDueToSize; @end #if TARGET_OS_IPHONE BOOL doesAppRunInBackground(void); #endif //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation DDLogFileManagerDefault @synthesize maximumNumberOfLogFiles; - (id)init { return [self initWithLogsDirectory:nil]; } - (instancetype)initWithLogsDirectory:(NSString *)aLogsDirectory { if ((self = [super init])) { maximumNumberOfLogFiles = DEFAULT_LOG_MAX_NUM_LOG_FILES; if (aLogsDirectory) _logsDirectory = [aLogsDirectory copy]; else _logsDirectory = [[self defaultLogsDirectory] copy]; NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew; [self addObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles)) options:kvoOptions context:nil]; NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]); NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]); } return self; } - (void)dealloc { // try-catch because the observer might be removed or never added. In this case, removeObserver throws and exception @try { [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles))]; } @catch (NSException *exception) { } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Configuration //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSNumber *old = [change objectForKey:NSKeyValueChangeOldKey]; NSNumber *new = [change objectForKey:NSKeyValueChangeNewKey]; if ([old isEqual:new]) { // No change in value - don't bother with any processing. return; } if ([keyPath isEqualToString:NSStringFromSelector(@selector(maximumNumberOfLogFiles))]) { NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles"); dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool { [self deleteOldLogFiles]; }}); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark File Deleting //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Deletes archived log files that exceed the maximumNumberOfLogFiles configuration value. **/ - (void)deleteOldLogFiles { NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles"); NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles; if (maxNumLogFiles == 0) { // Unlimited - don't delete any log files return; } NSArray *sortedLogFileInfos = [self sortedLogFileInfos]; // Do we consider the first file? // We are only supposed to be deleting archived files. // In most cases, the first file is likely the log file that is currently being written to. // So in most cases, we do not want to consider this file for deletion. NSUInteger count = [sortedLogFileInfos count]; BOOL excludeFirstFile = NO; if (count > 0) { DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:0]; if (!logFileInfo.isArchived) { excludeFirstFile = YES; } } NSArray *sortedArchivedLogFileInfos; if (excludeFirstFile) { count--; sortedArchivedLogFileInfos = [sortedLogFileInfos subarrayWithRange:NSMakeRange(1, count)]; } else { sortedArchivedLogFileInfos = sortedLogFileInfos; } NSUInteger i; for (i = maxNumLogFiles; i < count; i++) { DDLogFileInfo *logFileInfo = [sortedArchivedLogFileInfos objectAtIndex:i]; NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName); [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil]; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Log Files //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns the path to the default logs directory. * If the logs directory doesn't exist, this method automatically creates it. **/ - (NSString *)defaultLogsDirectory { #if TARGET_OS_IPHONE NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil; NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"]; #else NSString *appName = [[NSProcessInfo processInfo] processName]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory(); NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName]; #endif return logsDirectory; } - (NSString *)logsDirectory { // We could do this check once, during initalization, and not bother again. // But this way the code continues to work if the directory gets deleted while the code is running. if (![[NSFileManager defaultManager] fileExistsAtPath:_logsDirectory]) { NSError *err = nil; if (![[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory withIntermediateDirectories:YES attributes:nil error:&err]) { NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err); } } return _logsDirectory; } /** * Default log file name is "