PageRenderTime 72ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 1ms

/XMPP_Demo/Vendor/CocoaLumberjack/DDFileLogger.m

https://gitlab.com/praveenvelanati/ios-demo
Objective C | 1380 lines | 883 code | 322 blank | 175 comment | 126 complexity | 9b8ed7a1965c1da01dadfb30fd69d92e 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. CFUUIDRef uuid = CFUUIDCreate(NULL);
  309. CFStringRef fullStr = CFUUIDCreateString(NULL, uuid);
  310. NSString *result = (__bridge_transfer NSString *)CFStringCreateWithSubstring(NULL, fullStr, CFRangeMake(0, 6));
  311. CFRelease(fullStr);
  312. CFRelease(uuid);
  313. return result;
  314. }
  315. /**
  316. * Generates a new unique log file path, and creates the corresponding log file.
  317. **/
  318. - (NSString *)createNewLogFile
  319. {
  320. // Generate a random log file name, and create the file (if there isn't a collision)
  321. NSString *logsDirectory = [self logsDirectory];
  322. do
  323. {
  324. NSString *fileName = [NSString stringWithFormat:@"log-%@.txt", [self generateShortUUID]];
  325. NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
  326. if (![[NSFileManager defaultManager] fileExistsAtPath:filePath])
  327. {
  328. NSLogVerbose(@"DDLogFileManagerDefault: Creating new log file: %@", fileName);
  329. [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
  330. // Since we just created a new log file, we may need to delete some old log files
  331. [self deleteOldLogFiles];
  332. return filePath;
  333. }
  334. } while(YES);
  335. }
  336. @end
  337. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  338. #pragma mark -
  339. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  340. @implementation DDLogFileFormatterDefault
  341. - (id)init
  342. {
  343. return [self initWithDateFormatter:nil];
  344. }
  345. - (id)initWithDateFormatter:(NSDateFormatter *)aDateFormatter
  346. {
  347. if ((self = [super init]))
  348. {
  349. if (aDateFormatter)
  350. {
  351. dateFormatter = aDateFormatter;
  352. }
  353. else
  354. {
  355. dateFormatter = [[NSDateFormatter alloc] init];
  356. [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
  357. [dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
  358. }
  359. }
  360. return self;
  361. }
  362. - (NSString *)formatLogMessage:(DDLogMessage *)logMessage
  363. {
  364. NSString *dateAndTime = [dateFormatter stringFromDate:(logMessage->timestamp)];
  365. return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->logMsg];
  366. }
  367. @end
  368. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  369. #pragma mark -
  370. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  371. @implementation DDFileLogger
  372. - (id)init
  373. {
  374. DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];
  375. return [self initWithLogFileManager:defaultLogFileManager];
  376. }
  377. - (id)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager
  378. {
  379. if ((self = [super init]))
  380. {
  381. maximumFileSize = DEFAULT_LOG_MAX_FILE_SIZE;
  382. rollingFrequency = DEFAULT_LOG_ROLLING_FREQUENCY;
  383. logFileManager = aLogFileManager;
  384. formatter = [[DDLogFileFormatterDefault alloc] init];
  385. }
  386. return self;
  387. }
  388. - (void)dealloc
  389. {
  390. [currentLogFileHandle synchronizeFile];
  391. [currentLogFileHandle closeFile];
  392. if (rollingTimer)
  393. {
  394. dispatch_source_cancel(rollingTimer);
  395. rollingTimer = NULL;
  396. }
  397. }
  398. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  399. #pragma mark Properties
  400. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  401. @synthesize logFileManager;
  402. - (unsigned long long)maximumFileSize
  403. {
  404. // The design of this method is taken from the DDAbstractLogger implementation.
  405. // For extensive documentation please refer to the DDAbstractLogger implementation.
  406. // Note: The internal implementation should access the maximumFileSize variable directly,
  407. // but if we forget to do this, then this method should at least work properly.
  408. dispatch_queue_t currentQueue = dispatch_get_current_queue();
  409. if (currentQueue == loggerQueue)
  410. {
  411. return maximumFileSize;
  412. }
  413. else
  414. {
  415. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  416. NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure");
  417. __block unsigned long long result;
  418. dispatch_sync(globalLoggingQueue, ^{
  419. dispatch_sync(loggerQueue, ^{
  420. result = maximumFileSize;
  421. });
  422. });
  423. return result;
  424. }
  425. }
  426. - (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize
  427. {
  428. // The design of this method is taken from the DDAbstractLogger implementation.
  429. // For documentation please refer to the DDAbstractLogger implementation.
  430. dispatch_block_t block = ^{ @autoreleasepool {
  431. maximumFileSize = newMaximumFileSize;
  432. [self maybeRollLogFileDueToSize];
  433. }};
  434. dispatch_queue_t currentQueue = dispatch_get_current_queue();
  435. if (currentQueue == loggerQueue)
  436. {
  437. block();
  438. }
  439. else
  440. {
  441. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  442. NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure");
  443. dispatch_async(globalLoggingQueue, ^{
  444. dispatch_async(loggerQueue, block);
  445. });
  446. }
  447. }
  448. - (NSTimeInterval)rollingFrequency
  449. {
  450. // The design of this method is taken from the DDAbstractLogger implementation.
  451. // For documentation please refer to the DDAbstractLogger implementation.
  452. // Note: The internal implementation should access the rollingFrequency variable directly,
  453. // but if we forget to do this, then this method should at least work properly.
  454. dispatch_queue_t currentQueue = dispatch_get_current_queue();
  455. if (currentQueue == loggerQueue)
  456. {
  457. return rollingFrequency;
  458. }
  459. else
  460. {
  461. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  462. NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure");
  463. __block NSTimeInterval result;
  464. dispatch_sync(globalLoggingQueue, ^{
  465. dispatch_sync(loggerQueue, ^{
  466. result = rollingFrequency;
  467. });
  468. });
  469. return result;
  470. }
  471. }
  472. - (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency
  473. {
  474. // The design of this method is taken from the DDAbstractLogger implementation.
  475. // For documentation please refer to the DDAbstractLogger implementation.
  476. dispatch_block_t block = ^{ @autoreleasepool {
  477. rollingFrequency = newRollingFrequency;
  478. [self maybeRollLogFileDueToAge];
  479. }};
  480. dispatch_queue_t currentQueue = dispatch_get_current_queue();
  481. if (currentQueue == loggerQueue)
  482. {
  483. block();
  484. }
  485. else
  486. {
  487. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  488. NSAssert(currentQueue != globalLoggingQueue, @"Core architecture requirement failure");
  489. dispatch_async(globalLoggingQueue, ^{
  490. dispatch_async(loggerQueue, block);
  491. });
  492. }
  493. }
  494. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  495. #pragma mark File Rolling
  496. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  497. - (void)scheduleTimerToRollLogFileDueToAge
  498. {
  499. if (rollingTimer)
  500. {
  501. dispatch_source_cancel(rollingTimer);
  502. rollingTimer = NULL;
  503. }
  504. if (currentLogFileInfo == nil || rollingFrequency <= 0.0)
  505. {
  506. return;
  507. }
  508. NSDate *logFileCreationDate = [currentLogFileInfo creationDate];
  509. NSTimeInterval ti = [logFileCreationDate timeIntervalSinceReferenceDate];
  510. ti += rollingFrequency;
  511. NSDate *logFileRollingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ti];
  512. NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
  513. NSLogVerbose(@"DDFileLogger: logFileCreationDate: %@", logFileCreationDate);
  514. NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
  515. rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue);
  516. dispatch_source_set_event_handler(rollingTimer, ^{ @autoreleasepool {
  517. [self maybeRollLogFileDueToAge];
  518. }});
  519. #if NEEDS_DISPATCH_RETAIN_RELEASE
  520. dispatch_source_t theRollingTimer = rollingTimer;
  521. dispatch_source_set_cancel_handler(rollingTimer, ^{
  522. dispatch_release(theRollingTimer);
  523. });
  524. #endif
  525. uint64_t delay = [logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC;
  526. dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay);
  527. dispatch_source_set_timer(rollingTimer, fireTime, DISPATCH_TIME_FOREVER, 1.0);
  528. dispatch_resume(rollingTimer);
  529. }
  530. - (void)rollLogFile
  531. {
  532. // This method is public.
  533. // We need to execute the rolling on our logging thread/queue.
  534. //
  535. // The design of this method is taken from the DDAbstractLogger implementation.
  536. // For documentation please refer to the DDAbstractLogger implementation.
  537. dispatch_block_t block = ^{ @autoreleasepool {
  538. [self rollLogFileNow];
  539. }};
  540. dispatch_queue_t currentQueue = dispatch_get_current_queue();
  541. if (currentQueue == loggerQueue)
  542. {
  543. block();
  544. }
  545. else
  546. {
  547. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  548. NSAssert(currentQueue != globalLoggingQueue, @"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 [[NSDictionary dictionaryWithObjectsAndKeys:
  810. self.filePath, @"filePath",
  811. self.fileName, @"fileName",
  812. self.fileAttributes, @"fileAttributes",
  813. self.creationDate, @"creationDate",
  814. self.modificationDate, @"modificationDate",
  815. [NSNumber numberWithUnsignedLongLong:self.fileSize], @"fileSize",
  816. [NSNumber numberWithDouble:self.age], @"age",
  817. [NSNumber numberWithBool:self.isArchived], @"isArchived",
  818. nil] description];
  819. }
  820. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  821. #pragma mark Archiving
  822. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  823. - (BOOL)isArchived
  824. {
  825. #if TARGET_IPHONE_SIMULATOR
  826. // Extended attributes don't work properly on the simulator.
  827. // So we have to use a less attractive alternative.
  828. // See full explanation in the header file.
  829. return [self hasExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
  830. #else
  831. return [self hasExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
  832. #endif
  833. }
  834. - (void)setIsArchived:(BOOL)flag
  835. {
  836. #if TARGET_IPHONE_SIMULATOR
  837. // Extended attributes don't work properly on the simulator.
  838. // So we have to use a less attractive alternative.
  839. // See full explanation in the header file.
  840. if (flag)
  841. [self addExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
  842. else
  843. [self removeExtensionAttributeWithName:XATTR_ARCHIVED_NAME];
  844. #else
  845. if (flag)
  846. [self addExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
  847. else
  848. [self removeExtendedAttributeWithName:XATTR_ARCHIVED_NAME];
  849. #endif
  850. }
  851. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  852. #pragma mark Changes
  853. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  854. - (void)reset
  855. {
  856. fileName = nil;
  857. fileAttributes = nil;
  858. creationDate = nil;
  859. modificationDate = nil;
  860. }
  861. - (void)renameFile:(NSString *)newFileName
  862. {
  863. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  864. // See full explanation in the header file.
  865. if (![newFileName isEqualToString:[self fileName]])
  866. {
  867. NSString *fileDir = [filePath stringByDeletingLastPathComponent];
  868. NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
  869. NSLogVerbose(@"DDLogFileInfo: Renaming file: '%@' -> '%@'", self.fileName, newFileName);
  870. NSError *error = nil;
  871. if (![[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newFilePath error:&error])
  872. {
  873. NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
  874. }
  875. filePath = newFilePath;
  876. [self reset];
  877. }
  878. }
  879. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  880. #pragma mark Attribute Management
  881. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  882. #if TARGET_IPHONE_SIMULATOR
  883. // Extended attributes don't work properly on the simulator.
  884. // So we have to use a less attractive alternative.
  885. // See full explanation in the header file.
  886. - (BOOL)hasExtensionAttributeWithName:(NSString *)attrName
  887. {
  888. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  889. // See full explanation in the header file.
  890. // Split the file name into components.
  891. //
  892. // log-ABC123.archived.uploaded.txt
  893. //
  894. // 0. log-ABC123
  895. // 1. archived
  896. // 2. uploaded
  897. // 3. txt
  898. //
  899. // So we want to search for the attrName in the components (ignoring the first and last array indexes).
  900. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  901. // Watch out for file names without an extension
  902. NSUInteger count = [components count];
  903. NSUInteger max = (count >= 2) ? count-1 : count;
  904. NSUInteger i;
  905. for (i = 1; i < max; i++)
  906. {
  907. NSString *attr = [components objectAtIndex:i];
  908. if ([attrName isEqualToString:attr])
  909. {
  910. return YES;
  911. }
  912. }
  913. return NO;
  914. }
  915. - (void)addExtensionAttributeWithName:(NSString *)attrName
  916. {
  917. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  918. // See full explanation in the header file.
  919. if ([attrName length] == 0) return;
  920. // Example:
  921. // attrName = "archived"
  922. //
  923. // "log-ABC123.txt" -> "log-ABC123.archived.txt"
  924. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  925. NSUInteger count = [components count];
  926. NSUInteger estimatedNewLength = [[self fileName] length] + [attrName length] + 1;
  927. NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
  928. if (count > 0)
  929. {
  930. [newFileName appendString:[components objectAtIndex:0]];
  931. }
  932. NSString *lastExt = @"";
  933. NSUInteger i;
  934. for (i = 1; i < count; i++)
  935. {
  936. NSString *attr = [components objectAtIndex:i];
  937. if ([attr length] == 0)
  938. {
  939. continue;
  940. }
  941. if ([attrName isEqualToString:attr])
  942. {
  943. // Extension attribute already exists in file name
  944. return;
  945. }
  946. if ([lastExt length] > 0)
  947. {
  948. [newFileName appendFormat:@".%@", lastExt];
  949. }
  950. lastExt = attr;
  951. }
  952. [newFileName appendFormat:@".%@", attrName];
  953. if ([lastExt length] > 0)
  954. {
  955. [newFileName appendFormat:@".%@", lastExt];
  956. }
  957. [self renameFile:newFileName];
  958. }
  959. - (void)removeExtensionAttributeWithName:(NSString *)attrName
  960. {
  961. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  962. // See full explanation in the header file.
  963. if ([attrName length] == 0) return;
  964. // Example:
  965. // attrName = "archived"
  966. //
  967. // "log-ABC123.txt" -> "log-ABC123.archived.txt"
  968. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  969. NSUInteger count = [components count];
  970. NSUInteger estimatedNewLength = [[self fileName] length];
  971. NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
  972. if (count > 0)
  973. {
  974. [newFileName appendString:[components objectAtIndex:0]];
  975. }
  976. BOOL found = NO;
  977. NSUInteger i;
  978. for (i = 1; i < count; i++)
  979. {
  980. NSString *attr = [components objectAtIndex:i];
  981. if ([attrName isEqualToString:attr])
  982. {
  983. found = YES;
  984. }
  985. else
  986. {
  987. [newFileName appendFormat:@".%@", attr];
  988. }
  989. }
  990. if (found)
  991. {
  992. [self renameFile:newFileName];
  993. }
  994. }
  995. #else
  996. - (BOOL)hasExtendedAttributeWithName:(NSString *)attrName
  997. {
  998. const char *path = [filePath UTF8String];
  999. const char *name = [attrName UTF8String];
  1000. ssize_t result = getxattr(path, name, NULL, 0, 0, 0);
  1001. return (result >= 0);
  1002. }
  1003. - (void)addExtendedAttributeWithName:(NSString *)attrName
  1004. {
  1005. const char *path = [filePath UTF8String];
  1006. const char *name = [attrName UTF8String];
  1007. int result = setxattr(path, name, NULL, 0, 0, 0);
  1008. if (result < 0)
  1009. {
  1010. NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %i", attrName, self.fileName, result);
  1011. }
  1012. }
  1013. - (void)removeExtendedAttributeWithName:(NSString *)attrName
  1014. {
  1015. const char *path = [filePath UTF8String];
  1016. const char *name = [attrName UTF8String];
  1017. int result = removexattr(path, name, 0);
  1018. if (result < 0 && errno != ENOATTR)
  1019. {
  1020. NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %i", attrName, self.fileName, result);
  1021. }
  1022. }
  1023. #endif
  1024. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1025. #pragma mark Comparisons
  1026. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1027. - (BOOL)isEqual:(id)object
  1028. {
  1029. if ([object isKindOfClass:[self class]])
  1030. {
  1031. DDLogFileInfo *another = (DDLogFileInfo *)object;
  1032. return [filePath isEqualToString:[another filePath]];
  1033. }
  1034. return NO;
  1035. }
  1036. - (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another
  1037. {
  1038. NSDate *us = [self creationDate];
  1039. NSDate *them = [another creationDate];
  1040. NSComparisonResult result = [us compare:them];
  1041. if (result == NSOrderedAscending)
  1042. return NSOrderedDescending;
  1043. if (result == NSOrderedDescending)
  1044. return NSOrderedAscending;
  1045. return NSOrderedSame;
  1046. }
  1047. - (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another
  1048. {
  1049. NSDate *us = [self modificationDate];
  1050. NSDate *them = [another modificationDate];
  1051. NSComparisonResult result = [us compare:them];
  1052. if (result == NSOrderedAscending)
  1053. return NSOrderedDescending;
  1054. if (result == NSOrderedDescending)
  1055. return NSOrderedAscending;
  1056. return NSOrderedSame;
  1057. }
  1058. @end