PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/mio/libraries/CocoaLumberjack/DDFileLogger.m

https://gitlab.com/base.io/mio
Objective C | 1387 lines | 853 code | 329 blank | 205 comment | 112 complexity | b61eecaad7a69924aed290958142b84e MD5 | raw file
  1. #import "DDFileLogger.h"
  2. #import <unistd.h>
  3. #import <sys/attr.h>
  4. #import <sys/xattr.h>
  5. #import <libkern/OSAtomic.h>
  6. /**
  7. * Welcome to Cocoa Lumberjack!
  8. *
  9. * The project page has a wealth of documentation if you have any questions.
  10. * https://github.com/robbiehanson/CocoaLumberjack
  11. *
  12. * If you're new to the project you may wish to read the "Getting Started" wiki.
  13. * https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
  14. **/
  15. #if ! __has_feature(objc_arc)
  16. #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  17. #endif
  18. // Does ARC support support GCD objects?
  19. // It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
  20. #if TARGET_OS_IPHONE
  21. // Compiling for iOS
  22. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
  23. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  24. #else // iOS 5.X or earlier
  25. #define NEEDS_DISPATCH_RETAIN_RELEASE 1
  26. #endif
  27. #else
  28. // Compiling for Mac OS X
  29. #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
  30. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  31. #else
  32. #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
  33. #endif
  34. #endif
  35. // We probably shouldn't be using DDLog() statements within the DDLog implementation.
  36. // But we still want to leave our log statements for any future debugging,
  37. // and to allow other developers to trace the implementation (which is a great learning tool).
  38. //
  39. // So we use primitive logging macros around NSLog.
  40. // We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
  41. #define LOG_LEVEL 2
  42. #define NSLogError(frmt, ...) do{ if(LOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
  43. #define NSLogWarn(frmt, ...) do{ if(LOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
  44. #define NSLogInfo(frmt, ...) do{ if(LOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
  45. #define NSLogVerbose(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
  46. @interface DDLogFileManagerDefault (PrivateAPI)
  47. - (void)deleteOldLogFiles;
  48. - (NSString *)defaultLogsDirectory;
  49. @end
  50. @interface DDFileLogger (PrivateAPI)
  51. - (void)rollLogFileNow;
  52. - (void)maybeRollLogFileDueToAge;
  53. - (void)maybeRollLogFileDueToSize;
  54. @end
  55. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  56. #pragma mark -
  57. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  58. @implementation DDLogFileManagerDefault
  59. @synthesize maximumNumberOfLogFiles;
  60. - (id)init
  61. {
  62. return [self initWithLogsDirectory:nil];
  63. }
  64. - (id)initWithLogsDirectory:(NSString *)aLogsDirectory
  65. {
  66. if ((self = [super init]))
  67. {
  68. maximumNumberOfLogFiles = DEFAULT_LOG_MAX_NUM_LOG_FILES;
  69. if (aLogsDirectory)
  70. _logsDirectory = [aLogsDirectory copy];
  71. else
  72. _logsDirectory = [[self defaultLogsDirectory] copy];
  73. NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
  74. [self addObserver:self forKeyPath:@"maximumNumberOfLogFiles" options:kvoOptions context:nil];
  75. NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]);
  76. NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]);
  77. }
  78. return self;
  79. }
  80. - (void)dealloc
  81. {
  82. [self removeObserver:self forKeyPath:@"maximumNumberOfLogFiles"];
  83. }
  84. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  85. #pragma mark Configuration
  86. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  87. - (void)observeValueForKeyPath:(NSString *)keyPath
  88. ofObject:(id)object
  89. change:(NSDictionary *)change
  90. context:(void *)context
  91. {
  92. NSNumber *old = [change objectForKey:NSKeyValueChangeOldKey];
  93. NSNumber *new = [change objectForKey:NSKeyValueChangeNewKey];
  94. if ([old isEqual:new])
  95. {
  96. // No change in value - don't bother with any processing.
  97. return;
  98. }
  99. if ([keyPath isEqualToString:@"maximumNumberOfLogFiles"])
  100. {
  101. NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles");
  102. dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool {
  103. [self deleteOldLogFiles];
  104. }});
  105. }
  106. }
  107. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  108. #pragma mark File Deleting
  109. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  110. /**
  111. * Deletes archived log files that exceed the maximumNumberOfLogFiles configuration value.
  112. **/
  113. - (void)deleteOldLogFiles
  114. {
  115. NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles");
  116. NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles;
  117. if (maxNumLogFiles == 0)
  118. {
  119. // Unlimited - don't delete any log files
  120. return;
  121. }
  122. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  123. // Do we consider the first file?
  124. // We are only supposed to be deleting archived files.
  125. // In most cases, the first file is likely the log file that is currently being written to.
  126. // So in most cases, we do not want to consider this file for deletion.
  127. NSUInteger count = [sortedLogFileInfos count];
  128. BOOL excludeFirstFile = NO;
  129. if (count > 0)
  130. {
  131. DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:0];
  132. if (!logFileInfo.isArchived)
  133. {
  134. excludeFirstFile = YES;
  135. }
  136. }
  137. NSArray *sortedArchivedLogFileInfos;
  138. if (excludeFirstFile)
  139. {
  140. count--;
  141. sortedArchivedLogFileInfos = [sortedLogFileInfos subarrayWithRange:NSMakeRange(1, count)];
  142. }
  143. else
  144. {
  145. sortedArchivedLogFileInfos = sortedLogFileInfos;
  146. }
  147. NSUInteger i;
  148. for (i = maxNumLogFiles; i < count; i++)
  149. {
  150. DDLogFileInfo *logFileInfo = [sortedArchivedLogFileInfos objectAtIndex:i];
  151. NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName);
  152. [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil];
  153. }
  154. }
  155. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  156. #pragma mark Log Files
  157. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  158. /**
  159. * Returns the path to the default logs directory.
  160. * If the logs directory doesn't exist, this method automatically creates it.
  161. **/
  162. - (NSString *)defaultLogsDirectory
  163. {
  164. #if TARGET_OS_IPHONE
  165. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  166. NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
  167. NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
  168. #else
  169. NSString *appName = [[NSProcessInfo processInfo] processName];
  170. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
  171. NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
  172. NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName];
  173. #endif
  174. return logsDirectory;
  175. }
  176. - (NSString *)logsDirectory
  177. {
  178. // We could do this check once, during initalization, and not bother again.
  179. // But this way the code continues to work if the directory gets deleted while the code is running.
  180. if (![[NSFileManager defaultManager] fileExistsAtPath:_logsDirectory])
  181. {
  182. NSError *err = nil;
  183. if (![[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory
  184. withIntermediateDirectories:YES attributes:nil error:&err])
  185. {
  186. NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err);
  187. }
  188. }
  189. return _logsDirectory;
  190. }
  191. - (BOOL)isLogFile:(NSString *)fileName
  192. {
  193. // A log file has a name like "log-<uuid>.txt", where <uuid> is a HEX-string of 6 characters.
  194. //
  195. // For example: log-DFFE99.txt
  196. BOOL hasProperPrefix = [fileName hasPrefix:@"log-"];
  197. BOOL hasProperLength = [fileName length] >= 10;
  198. if (hasProperPrefix && hasProperLength)
  199. {
  200. NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789ABCDEF"];
  201. NSString *hex = [fileName substringWithRange:NSMakeRange(4, 6)];
  202. NSString *nohex = [hex stringByTrimmingCharactersInSet:hexSet];
  203. if ([nohex length] == 0)
  204. {
  205. return YES;
  206. }
  207. }
  208. return NO;
  209. }
  210. /**
  211. * Returns an array of NSString objects,
  212. * each of which is the filePath to an existing log file on disk.
  213. **/
  214. - (NSArray *)unsortedLogFilePaths
  215. {
  216. NSString *logsDirectory = [self logsDirectory];
  217. NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:nil];
  218. NSMutableArray *unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]];
  219. for (NSString *fileName in fileNames)
  220. {
  221. // Filter out any files that aren't log files. (Just for extra safety)
  222. if ([self isLogFile:fileName])
  223. {
  224. NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
  225. [unsortedLogFilePaths addObject:filePath];
  226. }
  227. }
  228. return unsortedLogFilePaths;
  229. }
  230. /**
  231. * Returns an array of NSString objects,
  232. * each of which is the fileName of an existing log file on disk.
  233. **/
  234. - (NSArray *)unsortedLogFileNames
  235. {
  236. NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
  237. NSMutableArray *unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
  238. for (NSString *filePath in unsortedLogFilePaths)
  239. {
  240. [unsortedLogFileNames addObject:[filePath lastPathComponent]];
  241. }
  242. return unsortedLogFileNames;
  243. }
  244. /**
  245. * Returns an array of DDLogFileInfo objects,
  246. * each representing an existing log file on disk,
  247. * and containing important information about the log file such as it's modification date and size.
  248. **/
  249. - (NSArray *)unsortedLogFileInfos
  250. {
  251. NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
  252. NSMutableArray *unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
  253. for (NSString *filePath in unsortedLogFilePaths)
  254. {
  255. DDLogFileInfo *logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath];
  256. [unsortedLogFileInfos addObject:logFileInfo];
  257. }
  258. return unsortedLogFileInfos;
  259. }
  260. /**
  261. * Just like the unsortedLogFilePaths method, but sorts the array.
  262. * The items in the array are sorted by modification date.
  263. * The first item in the array will be the most recently modified log file.
  264. **/
  265. - (NSArray *)sortedLogFilePaths
  266. {
  267. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  268. NSMutableArray *sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
  269. for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
  270. {
  271. [sortedLogFilePaths addObject:[logFileInfo filePath]];
  272. }
  273. return sortedLogFilePaths;
  274. }
  275. /**
  276. * Just like the unsortedLogFileNames method, but sorts the array.
  277. * The items in the array are sorted by modification date.
  278. * The first item in the array will be the most recently modified log file.
  279. **/
  280. - (NSArray *)sortedLogFileNames
  281. {
  282. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  283. NSMutableArray *sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
  284. for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
  285. {
  286. [sortedLogFileNames addObject:[logFileInfo fileName]];
  287. }
  288. return sortedLogFileNames;
  289. }
  290. /**
  291. * Just like the unsortedLogFileInfos method, but sorts the array.
  292. * The items in the array are sorted by modification date.
  293. * The first item in the array will be the most recently modified log file.
  294. **/
  295. - (NSArray *)sortedLogFileInfos
  296. {
  297. return [[self unsortedLogFileInfos] sortedArrayUsingSelector:@selector(reverseCompareByCreationDate:)];
  298. }
  299. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  300. #pragma mark Creation
  301. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  302. /**
  303. * Generates a short UUID suitable for use in the log file's name.
  304. * The result will have six characters, all in the hexadecimal set [0123456789ABCDEF].
  305. **/
  306. - (NSString *)generateShortUUID
  307. {
  308. /*
  309. CFUUIDRef uuid = CFUUIDCreate(NULL);
  310. CFStringRef fullStr = CFUUIDCreateString(NULL, uuid);
  311. NSString *result = (__bridge_transfer NSString *)CFStringCreateWithSubstring(NULL, fullStr, CFRangeMake(0, 6));
  312. CFRelease(fullStr);
  313. CFRelease(uuid);
  314. */
  315. //
  316. NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
  317. [dateFormat setDateFormat:@"yyyyMMdd_HHmmss"];
  318. NSString *result = [dateFormat stringFromDate:[NSDate date]];
  319. NSLog(@"%@", result);
  320. //
  321. return result;
  322. }
  323. /**
  324. * Generates a new unique log file path, and creates the corresponding log file.
  325. **/
  326. - (NSString *)createNewLogFile
  327. {
  328. // Generate a random log file name, and create the file (if there isn't a collision)
  329. NSString *logsDirectory = [self logsDirectory];
  330. do
  331. {
  332. NSString *fileName = [NSString stringWithFormat:@"log-%@.txt", [self generateShortUUID]];
  333. NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
  334. if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
  335. {
  336. NSLogVerbose(@"DDLogFileManagerDefault: Creating new log file: %@", fileName);
  337. [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
  338. // Since we just created a new log file, we may need to delete some old log files
  339. [self deleteOldLogFiles];
  340. return filePath;
  341. }
  342. } while(YES);
  343. }
  344. @end
  345. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  346. #pragma mark -
  347. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  348. @implementation DDLogFileFormatterDefault
  349. - (id)init
  350. {
  351. return [self initWithDateFormatter:nil];
  352. }
  353. - (id)initWithDateFormatter:(NSDateFormatter *)aDateFormatter
  354. {
  355. if ((self = [super init]))
  356. {
  357. if (aDateFormatter)
  358. {
  359. dateFormatter = aDateFormatter;
  360. }
  361. else
  362. {
  363. dateFormatter = [[NSDateFormatter alloc] init];
  364. [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
  365. [dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
  366. }
  367. }
  368. return self;
  369. }
  370. - (NSString *)formatLogMessage:(DDLogMessage *)logMessage
  371. {
  372. NSString *dateAndTime = [dateFormatter stringFromDate:(logMessage->timestamp)];
  373. return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->logMsg];
  374. }
  375. @end
  376. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  377. #pragma mark -
  378. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  379. @implementation DDFileLogger
  380. - (id)init
  381. {
  382. DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];
  383. return [self initWithLogFileManager:defaultLogFileManager];
  384. }
  385. - (id)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager
  386. {
  387. if ((self = [super init]))
  388. {
  389. maximumFileSize = DEFAULT_LOG_MAX_FILE_SIZE;
  390. rollingFrequency = DEFAULT_LOG_ROLLING_FREQUENCY;
  391. logFileManager = aLogFileManager;
  392. formatter = [[DDLogFileFormatterDefault alloc] init];
  393. }
  394. return self;
  395. }
  396. - (void)dealloc
  397. {
  398. [currentLogFileHandle synchronizeFile];
  399. [currentLogFileHandle closeFile];
  400. if (rollingTimer)
  401. {
  402. dispatch_source_cancel(rollingTimer);
  403. rollingTimer = NULL;
  404. }
  405. }
  406. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  407. #pragma mark Properties
  408. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  409. @synthesize logFileManager;
  410. - (unsigned long long)maximumFileSize
  411. {
  412. __block unsigned long long result;
  413. dispatch_block_t block = ^{
  414. result = maximumFileSize;
  415. };
  416. // The design of this method is taken from the DDAbstractLogger implementation.
  417. // For extensive documentation please refer to the DDAbstractLogger implementation.
  418. // Note: The internal implementation MUST access the maximumFileSize variable directly,
  419. // This method is designed explicitly for external access.
  420. //
  421. // Using "self." syntax to go through this method will cause immediate deadlock.
  422. // This is the intended result. Fix it by accessing the ivar directly.
  423. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  424. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  425. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  426. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  427. dispatch_sync(globalLoggingQueue, ^{
  428. dispatch_sync(loggerQueue, block);
  429. });
  430. return result;
  431. }
  432. - (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize
  433. {
  434. dispatch_block_t block = ^{ @autoreleasepool {
  435. maximumFileSize = newMaximumFileSize;
  436. [self maybeRollLogFileDueToSize];
  437. }};
  438. // The design of this method is taken from the DDAbstractLogger implementation.
  439. // For extensive documentation please refer to the DDAbstractLogger implementation.
  440. // Note: The internal implementation MUST access the maximumFileSize variable directly,
  441. // This method is designed explicitly for external access.
  442. //
  443. // Using "self." syntax to go through this method will cause immediate deadlock.
  444. // This is the intended result. Fix it by accessing the ivar directly.
  445. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  446. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  447. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  448. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  449. dispatch_async(globalLoggingQueue, ^{
  450. dispatch_async(loggerQueue, block);
  451. });
  452. }
  453. - (NSTimeInterval)rollingFrequency
  454. {
  455. __block NSTimeInterval result;
  456. dispatch_block_t block = ^{
  457. result = rollingFrequency;
  458. };
  459. // The design of this method is taken from the DDAbstractLogger implementation.
  460. // For extensive documentation please refer to the DDAbstractLogger implementation.
  461. // Note: The internal implementation should access the rollingFrequency variable directly,
  462. // This method is designed explicitly for external access.
  463. //
  464. // Using "self." syntax to go through this method will cause immediate deadlock.
  465. // This is the intended result. Fix it by accessing the ivar directly.
  466. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  467. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  468. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  469. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  470. dispatch_sync(globalLoggingQueue, ^{
  471. dispatch_sync(loggerQueue, block);
  472. });
  473. return result;
  474. }
  475. - (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency
  476. {
  477. dispatch_block_t block = ^{ @autoreleasepool {
  478. rollingFrequency = newRollingFrequency;
  479. [self maybeRollLogFileDueToAge];
  480. }};
  481. // The design of this method is taken from the DDAbstractLogger implementation.
  482. // For extensive documentation please refer to the DDAbstractLogger implementation.
  483. // Note: The internal implementation should access the rollingFrequency variable directly,
  484. // This method is designed explicitly for external access.
  485. //
  486. // Using "self." syntax to go through this method will cause immediate deadlock.
  487. // This is the intended result. Fix it by accessing the ivar directly.
  488. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  489. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  490. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  491. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  492. dispatch_async(globalLoggingQueue, ^{
  493. dispatch_async(loggerQueue, block);
  494. });
  495. }
  496. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  497. #pragma mark File Rolling
  498. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  499. - (void)scheduleTimerToRollLogFileDueToAge
  500. {
  501. if (rollingTimer)
  502. {
  503. dispatch_source_cancel(rollingTimer);
  504. rollingTimer = NULL;
  505. }
  506. if (currentLogFileInfo == nil || rollingFrequency <= 0.0)
  507. {
  508. return;
  509. }
  510. NSDate *logFileCreationDate = [currentLogFileInfo creationDate];
  511. NSTimeInterval ti = [logFileCreationDate timeIntervalSinceReferenceDate];
  512. ti += rollingFrequency;
  513. NSDate *logFileRollingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ti];
  514. NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
  515. NSLogVerbose(@"DDFileLogger: logFileCreationDate: %@", logFileCreationDate);
  516. NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
  517. rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue);
  518. dispatch_source_set_event_handler(rollingTimer, ^{ @autoreleasepool {
  519. [self maybeRollLogFileDueToAge];
  520. }});
  521. #if NEEDS_DISPATCH_RETAIN_RELEASE
  522. dispatch_source_t theRollingTimer = rollingTimer;
  523. dispatch_source_set_cancel_handler(rollingTimer, ^{
  524. dispatch_release(theRollingTimer);
  525. });
  526. #endif
  527. uint64_t delay = [logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC;
  528. dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay);
  529. dispatch_source_set_timer(rollingTimer, fireTime, DISPATCH_TIME_FOREVER, 1.0);
  530. dispatch_resume(rollingTimer);
  531. }
  532. - (void)rollLogFile
  533. {
  534. // This method is public.
  535. // We need to execute the rolling on our logging thread/queue.
  536. dispatch_block_t block = ^{ @autoreleasepool {
  537. [self rollLogFileNow];
  538. }};
  539. // The design of this method is taken from the DDAbstractLogger implementation.
  540. // For extensive documentation please refer to the DDAbstractLogger implementation.
  541. if ([self isOnInternalLoggerQueue])
  542. {
  543. block();
  544. }
  545. else
  546. {
  547. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  548. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  549. dispatch_async(globalLoggingQueue, ^{
  550. dispatch_async(loggerQueue, block);
  551. });
  552. }
  553. }
  554. - (void)rollLogFileNow
  555. {
  556. NSLogVerbose(@"DDFileLogger: rollLogFileNow");
  557. if (currentLogFileHandle == nil) return;
  558. [currentLogFileHandle synchronizeFile];
  559. [currentLogFileHandle closeFile];
  560. currentLogFileHandle = nil;
  561. currentLogFileInfo.isArchived = YES;
  562. if ([logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)])
  563. {
  564. [logFileManager didRollAndArchiveLogFile:(currentLogFileInfo.filePath)];
  565. }
  566. currentLogFileInfo = nil;
  567. if (rollingTimer)
  568. {
  569. dispatch_source_cancel(rollingTimer);
  570. rollingTimer = NULL;
  571. }
  572. }
  573. - (void)maybeRollLogFileDueToAge
  574. {
  575. if (rollingFrequency > 0.0 && currentLogFileInfo.age >= rollingFrequency)
  576. {
  577. NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
  578. [self rollLogFileNow];
  579. }
  580. else
  581. {
  582. [self scheduleTimerToRollLogFileDueToAge];
  583. }
  584. }
  585. - (void)maybeRollLogFileDueToSize
  586. {
  587. // This method is called from logMessage.
  588. // Keep it FAST.
  589. // Note: Use direct access to maximumFileSize variable.
  590. // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons).
  591. if (maximumFileSize > 0)
  592. {
  593. unsigned long long fileSize = [currentLogFileHandle offsetInFile];
  594. if (fileSize >= maximumFileSize)
  595. {
  596. NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize);
  597. [self rollLogFileNow];
  598. }
  599. }
  600. }
  601. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  602. #pragma mark File Logging
  603. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  604. /**
  605. * Returns the log file that should be used.
  606. * If there is an existing log file that is suitable,
  607. * within the constraints of maximumFileSize and rollingFrequency, then it is returned.
  608. *
  609. * Otherwise a new file is created and returned.
  610. **/
  611. - (DDLogFileInfo *)currentLogFileInfo
  612. {
  613. if (currentLogFileInfo == nil)
  614. {
  615. NSArray *sortedLogFileInfos = [logFileManager sortedLogFileInfos];
  616. if ([sortedLogFileInfos count] > 0)
  617. {
  618. DDLogFileInfo *mostRecentLogFileInfo = [sortedLogFileInfos objectAtIndex:0];
  619. BOOL useExistingLogFile = YES;
  620. BOOL shouldArchiveMostRecent = NO;
  621. if (mostRecentLogFileInfo.isArchived)
  622. {
  623. useExistingLogFile = NO;
  624. shouldArchiveMostRecent = NO;
  625. }
  626. else if (maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= maximumFileSize)
  627. {
  628. useExistingLogFile = NO;
  629. shouldArchiveMostRecent = YES;
  630. }
  631. else if (rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= rollingFrequency)
  632. {
  633. useExistingLogFile = NO;
  634. shouldArchiveMostRecent = YES;
  635. }
  636. if (useExistingLogFile)
  637. {
  638. NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", mostRecentLogFileInfo.fileName);
  639. currentLogFileInfo = mostRecentLogFileInfo;
  640. }
  641. else
  642. {
  643. if (shouldArchiveMostRecent)
  644. {
  645. mostRecentLogFileInfo.isArchived = YES;
  646. if ([logFileManager respondsToSelector:@selector(didArchiveLogFile:)])
  647. {
  648. [logFileManager didArchiveLogFile:(mostRecentLogFileInfo.filePath)];
  649. }
  650. }
  651. }
  652. }
  653. if (currentLogFileInfo == nil)
  654. {
  655. NSString *currentLogFilePath = [logFileManager createNewLogFile];
  656. currentLogFileInfo = [[DDLogFileInfo alloc] initWithFilePath:currentLogFilePath];
  657. }
  658. }
  659. return currentLogFileInfo;
  660. }
  661. - (NSFileHandle *)currentLogFileHandle
  662. {
  663. if (currentLogFileHandle == nil)
  664. {
  665. NSString *logFilePath = [[self currentLogFileInfo] filePath];
  666. currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
  667. [currentLogFileHandle seekToEndOfFile];
  668. if (currentLogFileHandle)
  669. {
  670. [self scheduleTimerToRollLogFileDueToAge];
  671. }
  672. }
  673. return currentLogFileHandle;
  674. }
  675. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  676. #pragma mark DDLogger Protocol
  677. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  678. - (void)logMessage:(DDLogMessage *)logMessage
  679. {
  680. NSString *logMsg = logMessage->logMsg;
  681. if (formatter)
  682. {
  683. logMsg = [formatter formatLogMessage:logMessage];
  684. }
  685. if (logMsg)
  686. {
  687. if (![logMsg hasSuffix:@"\n"])
  688. {
  689. logMsg = [logMsg stringByAppendingString:@"\n"];
  690. }
  691. NSData *logData = [logMsg dataUsingEncoding:NSUTF8StringEncoding];
  692. [[self currentLogFileHandle] writeData:logData];
  693. [self maybeRollLogFileDueToSize];
  694. }
  695. }
  696. - (void)willRemoveLogger
  697. {
  698. // If you override me be sure to invoke [super willRemoveLogger];
  699. [self rollLogFileNow];
  700. }
  701. - (NSString *)loggerName
  702. {
  703. return @"cocoa.lumberjack.fileLogger";
  704. }
  705. @end
  706. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  707. #pragma mark -
  708. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  709. #if TARGET_IPHONE_SIMULATOR
  710. #define XATTR_ARCHIVED_NAME @"archived"
  711. #else
  712. #define XATTR_ARCHIVED_NAME @"lumberjack.log.archived"
  713. #endif
  714. @implementation DDLogFileInfo
  715. @synthesize filePath;
  716. @dynamic fileName;
  717. @dynamic fileAttributes;
  718. @dynamic creationDate;
  719. @dynamic modificationDate;
  720. @dynamic fileSize;
  721. @dynamic age;
  722. @dynamic isArchived;
  723. #pragma mark Lifecycle
  724. + (id)logFileWithPath:(NSString *)aFilePath
  725. {
  726. return [[DDLogFileInfo alloc] initWithFilePath:aFilePath];
  727. }
  728. - (id)initWithFilePath:(NSString *)aFilePath
  729. {
  730. if ((self = [super init]))
  731. {
  732. filePath = [aFilePath copy];
  733. }
  734. return self;
  735. }
  736. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  737. #pragma mark Standard Info
  738. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  739. - (NSDictionary *)fileAttributes
  740. {
  741. if (fileAttributes == nil)
  742. {
  743. fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
  744. }
  745. return fileAttributes;
  746. }
  747. - (NSString *)fileName
  748. {
  749. if (fileName == nil)
  750. {
  751. fileName = [filePath lastPathComponent];
  752. }
  753. return fileName;
  754. }
  755. - (NSDate *)modificationDate
  756. {
  757. if (modificationDate == nil)
  758. {
  759. modificationDate = [[self fileAttributes] objectForKey:NSFileModificationDate];
  760. }
  761. return modificationDate;
  762. }
  763. - (NSDate *)creationDate
  764. {
  765. if (creationDate == nil)
  766. {
  767. #if TARGET_OS_IPHONE
  768. const char *path = [filePath UTF8String];
  769. struct attrlist attrList;
  770. memset(&attrList, 0, sizeof(attrList));
  771. attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
  772. attrList.commonattr = ATTR_CMN_CRTIME;
  773. struct {
  774. u_int32_t attrBufferSizeInBytes;
  775. struct timespec crtime;
  776. } attrBuffer;
  777. int result = getattrlist(path, &attrList, &attrBuffer, sizeof(attrBuffer), 0);
  778. if (result == 0)
  779. {
  780. double seconds = (double)(attrBuffer.crtime.tv_sec);
  781. double nanos = (double)(attrBuffer.crtime.tv_nsec);
  782. NSTimeInterval ti = seconds + (nanos / 1000000000.0);
  783. creationDate = [NSDate dateWithTimeIntervalSince1970:ti];
  784. }
  785. else
  786. {
  787. NSLogError(@"DDLogFileInfo: creationDate(%@): getattrlist result = %i", self.fileName, result);
  788. }
  789. #else
  790. creationDate = [[self fileAttributes] objectForKey:NSFileCreationDate];
  791. #endif
  792. }
  793. return creationDate;
  794. }
  795. - (unsigned long long)fileSize
  796. {
  797. if (fileSize == 0)
  798. {
  799. fileSize = [[[self fileAttributes] objectForKey:NSFileSize] unsignedLongLongValue];
  800. }
  801. return fileSize;
  802. }
  803. - (NSTimeInterval)age
  804. {
  805. return [[self creationDate] timeIntervalSinceNow] * -1.0;
  806. }
  807. - (NSString *)description
  808. {
  809. return [@{@"filePath": self.filePath,
  810. @"fileName": self.fileName,
  811. @"fileAttributes": self.fileAttributes,
  812. @"creationDate": self.creationDate,
  813. @"modificationDate": self.modificationDate,
  814. @"fileSize": @(self.fileSize),
  815. @"age": @(self.age),
  816. @"isArchived": @(self.isArchived)} description];
  817. }
  818. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  819. #pragma mark Archiving
  820. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  821. - (BOOL)isArchived
  822. {
  823. #if TARGET_IPHONE_SIMULATOR
  824. // Extended attributes don't work properly on the simulator.
  825. // So we have to use a less attractive alternative.
  826. // See full explanation in the header file.
  827. return [self hasExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
  828. #else
  829. return [self hasExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
  830. #endif
  831. }
  832. - (void)setIsArchived:(BOOL)flag
  833. {
  834. #if TARGET_IPHONE_SIMULATOR
  835. // Extended attributes don't work properly on the simulator.
  836. // So we have to use a less attractive alternative.
  837. // See full explanation in the header file.
  838. if (flag)
  839. [self addExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
  840. else
  841. [self removeExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
  842. #else
  843. if (flag)
  844. [self addExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
  845. else
  846. [self removeExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
  847. #endif
  848. }
  849. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  850. #pragma mark Changes
  851. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  852. - (void)reset
  853. {
  854. fileName = nil;
  855. fileAttributes = nil;
  856. creationDate = nil;
  857. modificationDate = nil;
  858. }
  859. - (void)renameFile:(NSString *)newFileName
  860. {
  861. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  862. // See full explanation in the header file.
  863. if (![newFileName isEqualToString:[self fileName]])
  864. {
  865. NSString *fileDir = [filePath stringByDeletingLastPathComponent];
  866. NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
  867. NSLogVerbose(@"DDLogFileInfo: Renaming file: '%@' -> '%@'", self.fileName, newFileName);
  868. NSError *error = nil;
  869. if (![[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newFilePath error:&error])
  870. {
  871. NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
  872. }
  873. filePath = newFilePath;
  874. [self reset];
  875. }
  876. }
  877. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  878. #pragma mark Attribute Management
  879. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  880. #if TARGET_IPHONE_SIMULATOR
  881. // Extended attributes don't work properly on the simulator.
  882. // So we have to use a less attractive alternative.
  883. // See full explanation in the header file.
  884. - (BOOL)hasExtensionAttributeWithName:(NSString *)attrName
  885. {
  886. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  887. // See full explanation in the header file.
  888. // Split the file name into components.
  889. //
  890. // log-ABC123.archived.uploaded.txt
  891. //
  892. // 0. log-ABC123
  893. // 1. archived
  894. // 2. uploaded
  895. // 3. txt
  896. //
  897. // So we want to search for the attrName in the components (ignoring the first and last array indexes).
  898. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  899. // Watch out for file names without an extension
  900. NSUInteger count = [components count];
  901. NSUInteger max = (count >= 2) ? count-1 : count;
  902. NSUInteger i;
  903. for (i = 1; i < max; i++)
  904. {
  905. NSString *attr = [components objectAtIndex:i];
  906. if ([attrName isEqualToString:attr])
  907. {
  908. return YES;
  909. }
  910. }
  911. return NO;
  912. }
  913. - (void)addExtensionAttributeWithName:(NSString *)attrName
  914. {
  915. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  916. // See full explanation in the header file.
  917. if ([attrName length] == 0) return;
  918. // Example:
  919. // attrName = "archived"
  920. //
  921. // "log-ABC123.txt" -> "log-ABC123.archived.txt"
  922. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  923. NSUInteger count = [components count];
  924. NSUInteger estimatedNewLength = [[self fileName] length] + [attrName length] + 1;
  925. NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
  926. if (count > 0)
  927. {
  928. [newFileName appendString:[components objectAtIndex:0]];
  929. }
  930. NSString *lastExt = @"";
  931. NSUInteger i;
  932. for (i = 1; i < count; i++)
  933. {
  934. NSString *attr = [components objectAtIndex:i];
  935. if ([attr length] == 0)
  936. {
  937. continue;
  938. }
  939. if ([attrName isEqualToString:attr])
  940. {
  941. // Extension attribute already exists in file name
  942. return;
  943. }
  944. if ([lastExt length] > 0)
  945. {
  946. [newFileName appendFormat:@".%@", lastExt];
  947. }
  948. lastExt = attr;
  949. }
  950. [newFileName appendFormat:@".%@", attrName];
  951. if ([lastExt length] > 0)
  952. {
  953. [newFileName appendFormat:@".%@", lastExt];
  954. }
  955. [self renameFile:newFileName];
  956. }
  957. - (void)removeExtensionAttributeWithName:(NSString *)attrName
  958. {
  959. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  960. // See full explanation in the header file.
  961. if ([attrName length] == 0) return;
  962. // Example:
  963. // attrName = "archived"
  964. //
  965. // "log-ABC123.txt" -> "log-ABC123.archived.txt"
  966. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  967. NSUInteger count = [components count];
  968. NSUInteger estimatedNewLength = [[self fileName] length];
  969. NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
  970. if (count > 0)
  971. {
  972. [newFileName appendString:[components objectAtIndex:0]];
  973. }
  974. BOOL found = NO;
  975. NSUInteger i;
  976. for (i = 1; i < count; i++)
  977. {
  978. NSString *attr = [components objectAtIndex:i];
  979. if ([attrName isEqualToString:attr])
  980. {
  981. found = YES;
  982. }
  983. else
  984. {
  985. [newFileName appendFormat:@".%@", attr];
  986. }
  987. }
  988. if (found)
  989. {
  990. [self renameFile:newFileName];
  991. }
  992. }
  993. #else
  994. - (BOOL)hasExtendedAttributeWithName:(NSString *)attrName
  995. {
  996. const char *path = [filePath UTF8String];
  997. const char *name = [attrName UTF8String];
  998. ssize_t result = getxattr(path, name, NULL, 0, 0, 0);
  999. return (result >= 0);
  1000. }
  1001. - (void)addExtendedAttributeWithName:(NSString *)attrName
  1002. {
  1003. const char *path = [filePath UTF8String];
  1004. const char *name = [attrName UTF8String];
  1005. int result = setxattr(path, name, NULL, 0, 0, 0);
  1006. if (result < 0)
  1007. {
  1008. NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %i", attrName, self.fileName, result);
  1009. }
  1010. }
  1011. - (void)removeExtendedAttributeWithName:(NSString *)attrName
  1012. {
  1013. const char *path = [filePath UTF8String];
  1014. const char *name = [attrName UTF8String];
  1015. int result = removexattr(path, name, 0);
  1016. if (result < 0 && errno != ENOATTR)
  1017. {
  1018. NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %i", attrName, self.fileName, result);
  1019. }
  1020. }
  1021. #endif
  1022. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1023. #pragma mark Comparisons
  1024. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1025. - (BOOL)isEqual:(id)object
  1026. {
  1027. if ([object isKindOfClass:[self class]])
  1028. {
  1029. DDLogFileInfo *another = (DDLogFileInfo *)object;
  1030. return [filePath isEqualToString:[another filePath]];
  1031. }
  1032. return NO;
  1033. }
  1034. - (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another
  1035. {
  1036. NSDate *us = [self creationDate];
  1037. NSDate *them = [another creationDate];
  1038. NSComparisonResult result = [us compare:them];
  1039. if (result == NSOrderedAscending)
  1040. return NSOrderedDescending;
  1041. if (result == NSOrderedDescending)
  1042. return NSOrderedAscending;
  1043. return NSOrderedSame;
  1044. }
  1045. - (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another
  1046. {
  1047. NSDate *us = [self modificationDate];
  1048. NSDate *them = [another modificationDate];
  1049. NSComparisonResult result = [us compare:them];
  1050. if (result == NSOrderedAscending)
  1051. return NSOrderedDescending;
  1052. if (result == NSOrderedDescending)
  1053. return NSOrderedAscending;
  1054. return NSOrderedSame;
  1055. }
  1056. @end