Nenhuma Descrição

DDFileLogger.m 51KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2015, Deusty, LLC
  4. // All rights reserved.
  5. //
  6. // Redistribution and use of this software in source and binary forms,
  7. // with or without modification, are permitted provided that the following conditions are met:
  8. //
  9. // * Redistributions of source code must retain the above copyright notice,
  10. // this list of conditions and the following disclaimer.
  11. //
  12. // * Neither the name of Deusty nor the names of its contributors may be used
  13. // to endorse or promote products derived from this software without specific
  14. // prior written permission of Deusty, LLC.
  15. #import "DDFileLogger.h"
  16. #import <unistd.h>
  17. #import <sys/attr.h>
  18. #import <sys/xattr.h>
  19. #import <libkern/OSAtomic.h>
  20. #if !__has_feature(objc_arc)
  21. #error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  22. #endif
  23. // We probably shouldn't be using DDLog() statements within the DDLog implementation.
  24. // But we still want to leave our log statements for any future debugging,
  25. // and to allow other developers to trace the implementation (which is a great learning tool).
  26. //
  27. // So we use primitive logging macros around NSLog.
  28. // We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
  29. #define LOG_LEVEL 2
  30. #define NSLogError(frmt, ...) do{ if(LOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
  31. #define NSLogWarn(frmt, ...) do{ if(LOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
  32. #define NSLogInfo(frmt, ...) do{ if(LOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
  33. #define NSLogDebug(frmt, ...) do{ if(LOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
  34. #define NSLogVerbose(frmt, ...) do{ if(LOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
  35. #if TARGET_OS_IPHONE
  36. BOOL doesAppRunInBackground(void);
  37. #endif
  38. unsigned long long const kDDDefaultLogMaxFileSize = 1024 * 1024; // 1 MB
  39. NSTimeInterval const kDDDefaultLogRollingFrequency = 60 * 60 * 24; // 24 Hours
  40. NSUInteger const kDDDefaultLogMaxNumLogFiles = 5; // 5 Files
  41. unsigned long long const kDDDefaultLogFilesDiskQuota = 20 * 1024 * 1024; // 20 MB
  42. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  43. #pragma mark -
  44. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  45. @interface DDLogFileManagerDefault () {
  46. NSUInteger _maximumNumberOfLogFiles;
  47. unsigned long long _logFilesDiskQuota;
  48. NSString *_logsDirectory;
  49. #if TARGET_OS_IPHONE
  50. NSString *_defaultFileProtectionLevel;
  51. #endif
  52. }
  53. - (void)deleteOldLogFiles;
  54. - (NSString *)defaultLogsDirectory;
  55. @end
  56. @implementation DDLogFileManagerDefault
  57. @synthesize maximumNumberOfLogFiles = _maximumNumberOfLogFiles;
  58. @synthesize logFilesDiskQuota = _logFilesDiskQuota;
  59. - (instancetype)init {
  60. return [self initWithLogsDirectory:nil];
  61. }
  62. - (instancetype)initWithLogsDirectory:(NSString *)aLogsDirectory {
  63. if ((self = [super init])) {
  64. _maximumNumberOfLogFiles = kDDDefaultLogMaxNumLogFiles;
  65. _logFilesDiskQuota = kDDDefaultLogFilesDiskQuota;
  66. if (aLogsDirectory) {
  67. _logsDirectory = [aLogsDirectory copy];
  68. } else {
  69. _logsDirectory = [[self defaultLogsDirectory] copy];
  70. }
  71. NSKeyValueObservingOptions kvoOptions = NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew;
  72. [self addObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles)) options:kvoOptions context:nil];
  73. [self addObserver:self forKeyPath:NSStringFromSelector(@selector(logFilesDiskQuota)) options:kvoOptions context:nil];
  74. NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]);
  75. NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]);
  76. }
  77. return self;
  78. }
  79. + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
  80. {
  81. BOOL automatic = NO;
  82. if ([theKey isEqualToString:@"maximumNumberOfLogFiles"] || [theKey isEqualToString:@"logFilesDiskQuota"]) {
  83. automatic = NO;
  84. } else {
  85. automatic = [super automaticallyNotifiesObserversForKey:theKey];
  86. }
  87. return automatic;
  88. }
  89. #if TARGET_OS_IPHONE
  90. - (instancetype)initWithLogsDirectory:(NSString *)logsDirectory defaultFileProtectionLevel:(NSString *)fileProtectionLevel {
  91. if ((self = [self initWithLogsDirectory:logsDirectory])) {
  92. if ([fileProtectionLevel isEqualToString:NSFileProtectionNone] ||
  93. [fileProtectionLevel isEqualToString:NSFileProtectionComplete] ||
  94. [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUnlessOpen] ||
  95. [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) {
  96. _defaultFileProtectionLevel = fileProtectionLevel;
  97. }
  98. }
  99. return self;
  100. }
  101. #endif
  102. - (void)dealloc {
  103. // try-catch because the observer might be removed or never added. In this case, removeObserver throws and exception
  104. @try {
  105. [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(maximumNumberOfLogFiles))];
  106. [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(logFilesDiskQuota))];
  107. } @catch (NSException *exception) {
  108. }
  109. }
  110. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  111. #pragma mark Configuration
  112. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  113. - (void)observeValueForKeyPath:(NSString *)keyPath
  114. ofObject:(id)object
  115. change:(NSDictionary *)change
  116. context:(void *)context {
  117. NSNumber *old = change[NSKeyValueChangeOldKey];
  118. NSNumber *new = change[NSKeyValueChangeNewKey];
  119. if ([old isEqual:new]) {
  120. // No change in value - don't bother with any processing.
  121. return;
  122. }
  123. if ([keyPath isEqualToString:NSStringFromSelector(@selector(maximumNumberOfLogFiles))] ||
  124. [keyPath isEqualToString:NSStringFromSelector(@selector(logFilesDiskQuota))]) {
  125. NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: %@", keyPath);
  126. dispatch_async([DDLog loggingQueue], ^{ @autoreleasepool {
  127. [self deleteOldLogFiles];
  128. } });
  129. }
  130. }
  131. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  132. #pragma mark File Deleting
  133. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  134. /**
  135. * Deletes archived log files that exceed the maximumNumberOfLogFiles or logFilesDiskQuota configuration values.
  136. **/
  137. - (void)deleteOldLogFiles {
  138. NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles");
  139. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  140. NSUInteger firstIndexToDelete = NSNotFound;
  141. const unsigned long long diskQuota = self.logFilesDiskQuota;
  142. const NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles;
  143. if (diskQuota) {
  144. unsigned long long used = 0;
  145. for (NSUInteger i = 0; i < sortedLogFileInfos.count; i++) {
  146. DDLogFileInfo *info = sortedLogFileInfos[i];
  147. used += info.fileSize;
  148. if (used > diskQuota) {
  149. firstIndexToDelete = i;
  150. break;
  151. }
  152. }
  153. }
  154. if (maxNumLogFiles) {
  155. if (firstIndexToDelete == NSNotFound) {
  156. firstIndexToDelete = maxNumLogFiles;
  157. } else {
  158. firstIndexToDelete = MIN(firstIndexToDelete, maxNumLogFiles);
  159. }
  160. }
  161. if (firstIndexToDelete == 0) {
  162. // Do we consider the first file?
  163. // We are only supposed to be deleting archived files.
  164. // In most cases, the first file is likely the log file that is currently being written to.
  165. // So in most cases, we do not want to consider this file for deletion.
  166. if (sortedLogFileInfos.count > 0) {
  167. DDLogFileInfo *logFileInfo = sortedLogFileInfos[0];
  168. if (!logFileInfo.isArchived) {
  169. // Don't delete active file.
  170. ++firstIndexToDelete;
  171. }
  172. }
  173. }
  174. if (firstIndexToDelete != NSNotFound) {
  175. // removing all logfiles starting with firstIndexToDelete
  176. for (NSUInteger i = firstIndexToDelete; i < sortedLogFileInfos.count; i++) {
  177. DDLogFileInfo *logFileInfo = sortedLogFileInfos[i];
  178. NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName);
  179. [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:nil];
  180. }
  181. }
  182. }
  183. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  184. #pragma mark Log Files
  185. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  186. /**
  187. * Returns the path to the default logs directory.
  188. * If the logs directory doesn't exist, this method automatically creates it.
  189. **/
  190. - (NSString *)defaultLogsDirectory {
  191. #if TARGET_OS_IPHONE
  192. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  193. NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
  194. NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
  195. #else
  196. NSString *appName = [[NSProcessInfo processInfo] processName];
  197. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
  198. NSString *basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory();
  199. NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName];
  200. #endif
  201. return logsDirectory;
  202. }
  203. - (NSString *)logsDirectory {
  204. // We could do this check once, during initalization, and not bother again.
  205. // But this way the code continues to work if the directory gets deleted while the code is running.
  206. if (![[NSFileManager defaultManager] fileExistsAtPath:_logsDirectory]) {
  207. NSError *err = nil;
  208. if (![[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory
  209. withIntermediateDirectories:YES
  210. attributes:nil
  211. error:&err]) {
  212. NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err);
  213. }
  214. }
  215. return _logsDirectory;
  216. }
  217. /**
  218. * Default log file name is "<bundle identifier> <date> <time>.log".
  219. * Example: MobileSafari 2013-12-03 17-14.log
  220. *
  221. * You can change it by overriding newLogFileName and isLogFile: methods.
  222. **/
  223. - (BOOL)isLogFile:(NSString *)fileName {
  224. NSString *appName = [self applicationName];
  225. BOOL hasProperPrefix = [fileName hasPrefix:appName];
  226. BOOL hasProperSuffix = [fileName hasSuffix:@".log"];
  227. BOOL hasProperDate = NO;
  228. if (hasProperPrefix && hasProperSuffix) {
  229. NSUInteger lengthOfMiddle = fileName.length - appName.length - @".log".length;
  230. // Date string should have at least 16 characters - " 2013-12-03 17-14"
  231. if (lengthOfMiddle >= 17) {
  232. NSRange range = NSMakeRange(appName.length, lengthOfMiddle);
  233. NSString *middle = [fileName substringWithRange:range];
  234. NSArray *components = [middle componentsSeparatedByString:@" "];
  235. // When creating logfile if there is existing file with the same name, we append attemp number at the end.
  236. // Thats why here we can have three or four components. For details see createNewLogFile method.
  237. //
  238. // Components:
  239. // "", "2013-12-03", "17-14"
  240. // or
  241. // "", "2013-12-03", "17-14", "1"
  242. if (components.count == 3 || components.count == 4) {
  243. NSString *dateString = [NSString stringWithFormat:@"%@ %@", components[1], components[2]];
  244. NSDateFormatter *dateFormatter = [self logFileDateFormatter];
  245. NSDate *date = [dateFormatter dateFromString:dateString];
  246. if (date) {
  247. hasProperDate = YES;
  248. }
  249. }
  250. }
  251. }
  252. return (hasProperPrefix && hasProperDate && hasProperSuffix);
  253. }
  254. - (NSDateFormatter *)logFileDateFormatter {
  255. NSMutableDictionary *dictionary = [[NSThread currentThread]
  256. threadDictionary];
  257. NSString *dateFormat = @"yyyy'-'MM'-'dd' 'HH'-'mm'";
  258. NSString *key = [NSString stringWithFormat:@"logFileDateFormatter.%@", dateFormat];
  259. NSDateFormatter *dateFormatter = dictionary[key];
  260. if (dateFormatter == nil) {
  261. dateFormatter = [[NSDateFormatter alloc] init];
  262. [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
  263. [dateFormatter setDateFormat:dateFormat];
  264. [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
  265. dictionary[key] = dateFormatter;
  266. }
  267. return dateFormatter;
  268. }
  269. /**
  270. * Returns an array of NSString objects,
  271. * each of which is the filePath to an existing log file on disk.
  272. **/
  273. - (NSArray *)unsortedLogFilePaths {
  274. NSString *logsDirectory = [self logsDirectory];
  275. NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:nil];
  276. NSMutableArray *unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]];
  277. for (NSString *fileName in fileNames) {
  278. // Filter out any files that aren't log files. (Just for extra safety)
  279. #if TARGET_IPHONE_SIMULATOR
  280. // In case of iPhone simulator there can be 'archived' extension. isLogFile:
  281. // method knows nothing about it. Thus removing it for this method.
  282. //
  283. // See full explanation in the header file.
  284. NSString *theFileName = [fileName stringByReplacingOccurrencesOfString:@".archived"
  285. withString:@""];
  286. if ([self isLogFile:theFileName])
  287. #else
  288. if ([self isLogFile:fileName])
  289. #endif
  290. {
  291. NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
  292. [unsortedLogFilePaths addObject:filePath];
  293. }
  294. }
  295. return unsortedLogFilePaths;
  296. }
  297. /**
  298. * Returns an array of NSString objects,
  299. * each of which is the fileName of an existing log file on disk.
  300. **/
  301. - (NSArray *)unsortedLogFileNames {
  302. NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
  303. NSMutableArray *unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
  304. for (NSString *filePath in unsortedLogFilePaths) {
  305. [unsortedLogFileNames addObject:[filePath lastPathComponent]];
  306. }
  307. return unsortedLogFileNames;
  308. }
  309. /**
  310. * Returns an array of DDLogFileInfo objects,
  311. * each representing an existing log file on disk,
  312. * and containing important information about the log file such as it's modification date and size.
  313. **/
  314. - (NSArray *)unsortedLogFileInfos {
  315. NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
  316. NSMutableArray *unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
  317. for (NSString *filePath in unsortedLogFilePaths) {
  318. DDLogFileInfo *logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath];
  319. [unsortedLogFileInfos addObject:logFileInfo];
  320. }
  321. return unsortedLogFileInfos;
  322. }
  323. /**
  324. * Just like the unsortedLogFilePaths method, but sorts the array.
  325. * The items in the array are sorted by creation date.
  326. * The first item in the array will be the most recently created log file.
  327. **/
  328. - (NSArray *)sortedLogFilePaths {
  329. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  330. NSMutableArray *sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
  331. for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
  332. [sortedLogFilePaths addObject:[logFileInfo filePath]];
  333. }
  334. return sortedLogFilePaths;
  335. }
  336. /**
  337. * Just like the unsortedLogFileNames method, but sorts the array.
  338. * The items in the array are sorted by creation date.
  339. * The first item in the array will be the most recently created log file.
  340. **/
  341. - (NSArray *)sortedLogFileNames {
  342. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  343. NSMutableArray *sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
  344. for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
  345. [sortedLogFileNames addObject:[logFileInfo fileName]];
  346. }
  347. return sortedLogFileNames;
  348. }
  349. /**
  350. * Just like the unsortedLogFileInfos method, but sorts the array.
  351. * The items in the array are sorted by creation date.
  352. * The first item in the array will be the most recently created log file.
  353. **/
  354. - (NSArray *)sortedLogFileInfos {
  355. return [[self unsortedLogFileInfos] sortedArrayUsingSelector:@selector(reverseCompareByCreationDate:)];
  356. }
  357. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  358. #pragma mark Creation
  359. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  360. /**
  361. * Generates log file name with default format "<bundle identifier> <date> <time>.log"
  362. * Example: MobileSafari 2013-12-03 17-14.log
  363. *
  364. * You can change it by overriding newLogFileName and isLogFile: methods.
  365. **/
  366. - (NSString *)newLogFileName {
  367. NSString *appName = [self applicationName];
  368. NSDateFormatter *dateFormatter = [self logFileDateFormatter];
  369. NSString *formattedDate = [dateFormatter stringFromDate:[NSDate date]];
  370. return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate];
  371. }
  372. /**
  373. * Generates a new unique log file path, and creates the corresponding log file.
  374. **/
  375. - (NSString *)createNewLogFile {
  376. NSString *fileName = [self newLogFileName];
  377. NSString *logsDirectory = [self logsDirectory];
  378. NSUInteger attempt = 1;
  379. do {
  380. NSString *actualFileName = fileName;
  381. if (attempt > 1) {
  382. NSString *extension = [actualFileName pathExtension];
  383. actualFileName = [actualFileName stringByDeletingPathExtension];
  384. actualFileName = [actualFileName stringByAppendingFormat:@" %lu", (unsigned long)attempt];
  385. if (extension.length) {
  386. actualFileName = [actualFileName stringByAppendingPathExtension:extension];
  387. }
  388. }
  389. NSString *filePath = [logsDirectory stringByAppendingPathComponent:actualFileName];
  390. if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  391. NSLogVerbose(@"DDLogFileManagerDefault: Creating new log file: %@", actualFileName);
  392. NSDictionary *attributes = nil;
  393. #if TARGET_OS_IPHONE
  394. // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
  395. //
  396. // But in case if app is able to launch from background we need to have an ability to open log file any time we
  397. // want (even if device is locked). Thats why that attribute have to be changed to
  398. // NSFileProtectionCompleteUntilFirstUserAuthentication.
  399. NSString *key = _defaultFileProtectionLevel ? :
  400. (doesAppRunInBackground() ? NSFileProtectionCompleteUntilFirstUserAuthentication : NSFileProtectionCompleteUnlessOpen);
  401. attributes = @{
  402. NSFileProtectionKey: key
  403. };
  404. #endif
  405. [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:attributes];
  406. // Since we just created a new log file, we may need to delete some old log files
  407. [self deleteOldLogFiles];
  408. return filePath;
  409. } else {
  410. attempt++;
  411. }
  412. } while (YES);
  413. }
  414. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  415. #pragma mark Utility
  416. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  417. - (NSString *)applicationName {
  418. static NSString *_appName;
  419. static dispatch_once_t onceToken;
  420. dispatch_once(&onceToken, ^{
  421. _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
  422. if (!_appName) {
  423. _appName = [[NSProcessInfo processInfo] processName];
  424. }
  425. if (!_appName) {
  426. _appName = @"";
  427. }
  428. });
  429. return _appName;
  430. }
  431. @end
  432. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  433. #pragma mark -
  434. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  435. @interface DDLogFileFormatterDefault () {
  436. NSDateFormatter *_dateFormatter;
  437. }
  438. @end
  439. @implementation DDLogFileFormatterDefault
  440. - (instancetype)init {
  441. return [self initWithDateFormatter:nil];
  442. }
  443. - (instancetype)initWithDateFormatter:(NSDateFormatter *)aDateFormatter {
  444. if ((self = [super init])) {
  445. if (aDateFormatter) {
  446. _dateFormatter = aDateFormatter;
  447. } else {
  448. _dateFormatter = [[NSDateFormatter alloc] init];
  449. [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
  450. [_dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
  451. }
  452. }
  453. return self;
  454. }
  455. - (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
  456. NSString *dateAndTime = [_dateFormatter stringFromDate:(logMessage->_timestamp)];
  457. return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->_message];
  458. }
  459. @end
  460. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  461. #pragma mark -
  462. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  463. @interface DDFileLogger () {
  464. __strong id <DDLogFileManager> _logFileManager;
  465. DDLogFileInfo *_currentLogFileInfo;
  466. NSFileHandle *_currentLogFileHandle;
  467. dispatch_source_t _currentLogFileVnode;
  468. dispatch_source_t _rollingTimer;
  469. unsigned long long _maximumFileSize;
  470. NSTimeInterval _rollingFrequency;
  471. }
  472. - (void)rollLogFileNow;
  473. - (void)maybeRollLogFileDueToAge;
  474. - (void)maybeRollLogFileDueToSize;
  475. @end
  476. @implementation DDFileLogger
  477. - (instancetype)init {
  478. DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];
  479. return [self initWithLogFileManager:defaultLogFileManager];
  480. }
  481. - (instancetype)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager {
  482. if ((self = [super init])) {
  483. _maximumFileSize = kDDDefaultLogMaxFileSize;
  484. _rollingFrequency = kDDDefaultLogRollingFrequency;
  485. _automaticallyAppendNewlineForCustomFormatters = YES;
  486. logFileManager = aLogFileManager;
  487. self.logFormatter = [DDLogFileFormatterDefault new];
  488. }
  489. return self;
  490. }
  491. - (void)dealloc {
  492. [_currentLogFileHandle synchronizeFile];
  493. [_currentLogFileHandle closeFile];
  494. if (_currentLogFileVnode) {
  495. dispatch_source_cancel(_currentLogFileVnode);
  496. _currentLogFileVnode = NULL;
  497. }
  498. if (_rollingTimer) {
  499. dispatch_source_cancel(_rollingTimer);
  500. _rollingTimer = NULL;
  501. }
  502. }
  503. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  504. #pragma mark Properties
  505. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  506. @synthesize logFileManager;
  507. - (unsigned long long)maximumFileSize {
  508. __block unsigned long long result;
  509. dispatch_block_t block = ^{
  510. result = _maximumFileSize;
  511. };
  512. // The design of this method is taken from the DDAbstractLogger implementation.
  513. // For extensive documentation please refer to the DDAbstractLogger implementation.
  514. // Note: The internal implementation MUST access the maximumFileSize variable directly,
  515. // This method is designed explicitly for external access.
  516. //
  517. // Using "self." syntax to go through this method will cause immediate deadlock.
  518. // This is the intended result. Fix it by accessing the ivar directly.
  519. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  520. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  521. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  522. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  523. dispatch_sync(globalLoggingQueue, ^{
  524. dispatch_sync(self.loggerQueue, block);
  525. });
  526. return result;
  527. }
  528. - (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize {
  529. dispatch_block_t block = ^{
  530. @autoreleasepool {
  531. _maximumFileSize = newMaximumFileSize;
  532. [self maybeRollLogFileDueToSize];
  533. }
  534. };
  535. // The design of this method is taken from the DDAbstractLogger implementation.
  536. // For extensive documentation please refer to the DDAbstractLogger implementation.
  537. // Note: The internal implementation MUST access the maximumFileSize variable directly,
  538. // This method is designed explicitly for external access.
  539. //
  540. // Using "self." syntax to go through this method will cause immediate deadlock.
  541. // This is the intended result. Fix it by accessing the ivar directly.
  542. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  543. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  544. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  545. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  546. dispatch_async(globalLoggingQueue, ^{
  547. dispatch_async(self.loggerQueue, block);
  548. });
  549. }
  550. - (NSTimeInterval)rollingFrequency {
  551. __block NSTimeInterval result;
  552. dispatch_block_t block = ^{
  553. result = _rollingFrequency;
  554. };
  555. // The design of this method is taken from the DDAbstractLogger implementation.
  556. // For extensive documentation please refer to the DDAbstractLogger implementation.
  557. // Note: The internal implementation should access the rollingFrequency variable directly,
  558. // This method is designed explicitly for external access.
  559. //
  560. // Using "self." syntax to go through this method will cause immediate deadlock.
  561. // This is the intended result. Fix it by accessing the ivar directly.
  562. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  563. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  564. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  565. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  566. dispatch_sync(globalLoggingQueue, ^{
  567. dispatch_sync(self.loggerQueue, block);
  568. });
  569. return result;
  570. }
  571. - (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency {
  572. dispatch_block_t block = ^{
  573. @autoreleasepool {
  574. _rollingFrequency = newRollingFrequency;
  575. [self maybeRollLogFileDueToAge];
  576. }
  577. };
  578. // The design of this method is taken from the DDAbstractLogger implementation.
  579. // For extensive documentation please refer to the DDAbstractLogger implementation.
  580. // Note: The internal implementation should access the rollingFrequency variable directly,
  581. // This method is designed explicitly for external access.
  582. //
  583. // Using "self." syntax to go through this method will cause immediate deadlock.
  584. // This is the intended result. Fix it by accessing the ivar directly.
  585. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  586. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  587. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  588. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  589. dispatch_async(globalLoggingQueue, ^{
  590. dispatch_async(self.loggerQueue, block);
  591. });
  592. }
  593. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  594. #pragma mark File Rolling
  595. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  596. - (void)scheduleTimerToRollLogFileDueToAge {
  597. if (_rollingTimer) {
  598. dispatch_source_cancel(_rollingTimer);
  599. _rollingTimer = NULL;
  600. }
  601. if (_currentLogFileInfo == nil || _rollingFrequency <= 0.0) {
  602. return;
  603. }
  604. NSDate *logFileCreationDate = [_currentLogFileInfo creationDate];
  605. NSTimeInterval ti = [logFileCreationDate timeIntervalSinceReferenceDate];
  606. ti += _rollingFrequency;
  607. NSDate *logFileRollingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ti];
  608. NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
  609. NSLogVerbose(@"DDFileLogger: logFileCreationDate: %@", logFileCreationDate);
  610. NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
  611. _rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
  612. dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool {
  613. [self maybeRollLogFileDueToAge];
  614. } });
  615. #if !OS_OBJECT_USE_OBJC
  616. dispatch_source_t theRollingTimer = _rollingTimer;
  617. dispatch_source_set_cancel_handler(_rollingTimer, ^{
  618. dispatch_release(theRollingTimer);
  619. });
  620. #endif
  621. uint64_t delay = (uint64_t)([logFileRollingDate timeIntervalSinceNow] * NSEC_PER_SEC);
  622. dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay);
  623. dispatch_source_set_timer(_rollingTimer, fireTime, DISPATCH_TIME_FOREVER, 1.0);
  624. dispatch_resume(_rollingTimer);
  625. }
  626. - (void)rollLogFile {
  627. [self rollLogFileWithCompletionBlock:nil];
  628. }
  629. - (void)rollLogFileWithCompletionBlock:(void (^)())completionBlock {
  630. // This method is public.
  631. // We need to execute the rolling on our logging thread/queue.
  632. dispatch_block_t block = ^{
  633. @autoreleasepool {
  634. [self rollLogFileNow];
  635. if (completionBlock) {
  636. dispatch_async(dispatch_get_main_queue(), ^{
  637. completionBlock();
  638. });
  639. }
  640. }
  641. };
  642. // The design of this method is taken from the DDAbstractLogger implementation.
  643. // For extensive documentation please refer to the DDAbstractLogger implementation.
  644. if ([self isOnInternalLoggerQueue]) {
  645. block();
  646. } else {
  647. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  648. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  649. dispatch_async(globalLoggingQueue, ^{
  650. dispatch_async(self.loggerQueue, block);
  651. });
  652. }
  653. }
  654. - (void)rollLogFileNow {
  655. NSLogVerbose(@"DDFileLogger: rollLogFileNow");
  656. if (_currentLogFileHandle == nil) {
  657. return;
  658. }
  659. [_currentLogFileHandle synchronizeFile];
  660. [_currentLogFileHandle closeFile];
  661. _currentLogFileHandle = nil;
  662. _currentLogFileInfo.isArchived = YES;
  663. if ([logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]) {
  664. [logFileManager didRollAndArchiveLogFile:(_currentLogFileInfo.filePath)];
  665. }
  666. _currentLogFileInfo = nil;
  667. if (_currentLogFileVnode) {
  668. dispatch_source_cancel(_currentLogFileVnode);
  669. _currentLogFileVnode = NULL;
  670. }
  671. if (_rollingTimer) {
  672. dispatch_source_cancel(_rollingTimer);
  673. _rollingTimer = NULL;
  674. }
  675. }
  676. - (void)maybeRollLogFileDueToAge {
  677. if (_rollingFrequency > 0.0 && _currentLogFileInfo.age >= _rollingFrequency) {
  678. NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
  679. [self rollLogFileNow];
  680. } else {
  681. [self scheduleTimerToRollLogFileDueToAge];
  682. }
  683. }
  684. - (void)maybeRollLogFileDueToSize {
  685. // This method is called from logMessage.
  686. // Keep it FAST.
  687. // Note: Use direct access to maximumFileSize variable.
  688. // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons).
  689. if (_maximumFileSize > 0) {
  690. unsigned long long fileSize = [_currentLogFileHandle offsetInFile];
  691. if (fileSize >= _maximumFileSize) {
  692. NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize);
  693. [self rollLogFileNow];
  694. }
  695. }
  696. }
  697. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  698. #pragma mark File Logging
  699. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  700. /**
  701. * Returns the log file that should be used.
  702. * If there is an existing log file that is suitable,
  703. * within the constraints of maximumFileSize and rollingFrequency, then it is returned.
  704. *
  705. * Otherwise a new file is created and returned.
  706. **/
  707. - (DDLogFileInfo *)currentLogFileInfo {
  708. if (_currentLogFileInfo == nil) {
  709. NSArray *sortedLogFileInfos = [logFileManager sortedLogFileInfos];
  710. if ([sortedLogFileInfos count] > 0) {
  711. DDLogFileInfo *mostRecentLogFileInfo = sortedLogFileInfos[0];
  712. BOOL shouldArchiveMostRecent = NO;
  713. if (mostRecentLogFileInfo.isArchived) {
  714. shouldArchiveMostRecent = NO;
  715. } else if (_maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= _maximumFileSize) {
  716. shouldArchiveMostRecent = YES;
  717. } else if (_rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= _rollingFrequency) {
  718. shouldArchiveMostRecent = YES;
  719. }
  720. #if TARGET_OS_IPHONE
  721. // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
  722. //
  723. // But in case if app is able to launch from background we need to have an ability to open log file any time we
  724. // want (even if device is locked). Thats why that attribute have to be changed to
  725. // NSFileProtectionCompleteUntilFirstUserAuthentication.
  726. //
  727. // If previous log was created when app wasn't running in background, but now it is - we archive it and create
  728. // a new one.
  729. //
  730. // If user has owerwritten to NSFileProtectionNone there is no neeed to create a new one.
  731. if (!_doNotReuseLogFiles && doesAppRunInBackground()) {
  732. NSString *key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey];
  733. if ([key length] > 0 && !([key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication] || [key isEqualToString:NSFileProtectionNone])) {
  734. shouldArchiveMostRecent = YES;
  735. }
  736. }
  737. #endif
  738. if (!_doNotReuseLogFiles && !mostRecentLogFileInfo.isArchived && !shouldArchiveMostRecent) {
  739. NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", mostRecentLogFileInfo.fileName);
  740. _currentLogFileInfo = mostRecentLogFileInfo;
  741. } else {
  742. if (shouldArchiveMostRecent) {
  743. mostRecentLogFileInfo.isArchived = YES;
  744. if ([logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) {
  745. [logFileManager didArchiveLogFile:(mostRecentLogFileInfo.filePath)];
  746. }
  747. }
  748. }
  749. }
  750. if (_currentLogFileInfo == nil) {
  751. NSString *currentLogFilePath = [logFileManager createNewLogFile];
  752. _currentLogFileInfo = [[DDLogFileInfo alloc] initWithFilePath:currentLogFilePath];
  753. }
  754. }
  755. return _currentLogFileInfo;
  756. }
  757. - (NSFileHandle *)currentLogFileHandle {
  758. if (_currentLogFileHandle == nil) {
  759. NSString *logFilePath = [[self currentLogFileInfo] filePath];
  760. _currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
  761. [_currentLogFileHandle seekToEndOfFile];
  762. if (_currentLogFileHandle) {
  763. [self scheduleTimerToRollLogFileDueToAge];
  764. // Here we are monitoring the log file. In case if it would be deleted ormoved
  765. // somewhere we want to roll it and use a new one.
  766. _currentLogFileVnode = dispatch_source_create(
  767. DISPATCH_SOURCE_TYPE_VNODE,
  768. [_currentLogFileHandle fileDescriptor],
  769. DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME,
  770. self.loggerQueue
  771. );
  772. dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool {
  773. NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one");
  774. [self rollLogFileNow];
  775. } });
  776. #if !OS_OBJECT_USE_OBJC
  777. dispatch_source_t vnode = _currentLogFileVnode;
  778. dispatch_source_set_cancel_handler(_currentLogFileVnode, ^{
  779. dispatch_release(vnode);
  780. });
  781. #endif
  782. dispatch_resume(_currentLogFileVnode);
  783. }
  784. }
  785. return _currentLogFileHandle;
  786. }
  787. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  788. #pragma mark DDLogger Protocol
  789. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  790. static int exception_count = 0;
  791. - (void)logMessage:(DDLogMessage *)logMessage {
  792. NSString *message = logMessage->_message;
  793. BOOL isFormatted = NO;
  794. if (_logFormatter) {
  795. message = [_logFormatter formatLogMessage:logMessage];
  796. isFormatted = message != logMessage->_message;
  797. }
  798. if (message) {
  799. if ((!isFormatted || _automaticallyAppendNewlineForCustomFormatters) &&
  800. (![message hasSuffix:@"\n"])) {
  801. message = [message stringByAppendingString:@"\n"];
  802. }
  803. NSData *logData = [message dataUsingEncoding:NSUTF8StringEncoding];
  804. @try {
  805. [[self currentLogFileHandle] writeData:logData];
  806. [self maybeRollLogFileDueToSize];
  807. } @catch (NSException *exception) {
  808. exception_count++;
  809. if (exception_count <= 10) {
  810. NSLogError(@"DDFileLogger.logMessage: %@", exception);
  811. if (exception_count == 10) {
  812. NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them.");
  813. }
  814. }
  815. }
  816. }
  817. }
  818. - (void)willRemoveLogger {
  819. // If you override me be sure to invoke [super willRemoveLogger];
  820. [self rollLogFileNow];
  821. }
  822. - (NSString *)loggerName {
  823. return @"cocoa.lumberjack.fileLogger";
  824. }
  825. @end
  826. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  827. #pragma mark -
  828. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  829. #if TARGET_IPHONE_SIMULATOR
  830. NSString * const kDDXAttrArchivedName = @"archived";
  831. #else
  832. NSString * const kDDXAttrArchivedName = @"lumberjack.log.archived";
  833. #endif
  834. @interface DDLogFileInfo () {
  835. __strong NSString *_filePath;
  836. __strong NSString *_fileName;
  837. __strong NSDictionary *_fileAttributes;
  838. __strong NSDate *_creationDate;
  839. __strong NSDate *_modificationDate;
  840. unsigned long long _fileSize;
  841. }
  842. @end
  843. @implementation DDLogFileInfo
  844. @synthesize filePath;
  845. @dynamic fileName;
  846. @dynamic fileAttributes;
  847. @dynamic creationDate;
  848. @dynamic modificationDate;
  849. @dynamic fileSize;
  850. @dynamic age;
  851. @dynamic isArchived;
  852. #pragma mark Lifecycle
  853. + (instancetype)logFileWithPath:(NSString *)aFilePath {
  854. return [[self alloc] initWithFilePath:aFilePath];
  855. }
  856. - (instancetype)initWithFilePath:(NSString *)aFilePath {
  857. if ((self = [super init])) {
  858. filePath = [aFilePath copy];
  859. }
  860. return self;
  861. }
  862. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  863. #pragma mark Standard Info
  864. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  865. - (NSDictionary *)fileAttributes {
  866. if (_fileAttributes == nil) {
  867. _fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
  868. }
  869. return _fileAttributes;
  870. }
  871. - (NSString *)fileName {
  872. if (_fileName == nil) {
  873. _fileName = [filePath lastPathComponent];
  874. }
  875. return _fileName;
  876. }
  877. - (NSDate *)modificationDate {
  878. if (_modificationDate == nil) {
  879. _modificationDate = self.fileAttributes[NSFileModificationDate];
  880. }
  881. return _modificationDate;
  882. }
  883. - (NSDate *)creationDate {
  884. if (_creationDate == nil) {
  885. _creationDate = self.fileAttributes[NSFileCreationDate];
  886. }
  887. return _creationDate;
  888. }
  889. - (unsigned long long)fileSize {
  890. if (_fileSize == 0) {
  891. _fileSize = [self.fileAttributes[NSFileSize] unsignedLongLongValue];
  892. }
  893. return _fileSize;
  894. }
  895. - (NSTimeInterval)age {
  896. return [[self creationDate] timeIntervalSinceNow] * -1.0;
  897. }
  898. - (NSString *)description {
  899. return [@{ @"filePath": self.filePath ? : @"",
  900. @"fileName": self.fileName ? : @"",
  901. @"fileAttributes": self.fileAttributes ? : @"",
  902. @"creationDate": self.creationDate ? : @"",
  903. @"modificationDate": self.modificationDate ? : @"",
  904. @"fileSize": @(self.fileSize),
  905. @"age": @(self.age),
  906. @"isArchived": @(self.isArchived) } description];
  907. }
  908. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  909. #pragma mark Archiving
  910. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  911. - (BOOL)isArchived {
  912. #if TARGET_IPHONE_SIMULATOR
  913. // Extended attributes don't work properly on the simulator.
  914. // So we have to use a less attractive alternative.
  915. // See full explanation in the header file.
  916. return [self hasExtensionAttributeWithName:kDDXAttrArchivedName];
  917. #else
  918. return [self hasExtendedAttributeWithName:kDDXAttrArchivedName];
  919. #endif
  920. }
  921. - (void)setIsArchived:(BOOL)flag {
  922. #if TARGET_IPHONE_SIMULATOR
  923. // Extended attributes don't work properly on the simulator.
  924. // So we have to use a less attractive alternative.
  925. // See full explanation in the header file.
  926. if (flag) {
  927. [self addExtensionAttributeWithName:kDDXAttrArchivedName];
  928. } else {
  929. [self removeExtensionAttributeWithName:kDDXAttrArchivedName];
  930. }
  931. #else
  932. if (flag) {
  933. [self addExtendedAttributeWithName:kDDXAttrArchivedName];
  934. } else {
  935. [self removeExtendedAttributeWithName:kDDXAttrArchivedName];
  936. }
  937. #endif
  938. }
  939. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  940. #pragma mark Changes
  941. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  942. - (void)reset {
  943. _fileName = nil;
  944. _fileAttributes = nil;
  945. _creationDate = nil;
  946. _modificationDate = nil;
  947. }
  948. - (void)renameFile:(NSString *)newFileName {
  949. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  950. // See full explanation in the header file.
  951. if (![newFileName isEqualToString:[self fileName]]) {
  952. NSString *fileDir = [filePath stringByDeletingLastPathComponent];
  953. NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
  954. NSLogVerbose(@"DDLogFileInfo: Renaming file: '%@' -> '%@'", self.fileName, newFileName);
  955. NSError *error = nil;
  956. if ([[NSFileManager defaultManager] fileExistsAtPath:newFilePath] &&
  957. ![[NSFileManager defaultManager] removeItemAtPath:newFilePath error:&error]) {
  958. NSLogError(@"DDLogFileInfo: Error deleting archive (%@): %@", self.fileName, error);
  959. }
  960. if (![[NSFileManager defaultManager] moveItemAtPath:filePath toPath:newFilePath error:&error]) {
  961. NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
  962. }
  963. filePath = newFilePath;
  964. [self reset];
  965. }
  966. }
  967. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  968. #pragma mark Attribute Management
  969. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  970. #if TARGET_IPHONE_SIMULATOR
  971. // Extended attributes don't work properly on the simulator.
  972. // So we have to use a less attractive alternative.
  973. // See full explanation in the header file.
  974. - (BOOL)hasExtensionAttributeWithName:(NSString *)attrName {
  975. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  976. // See full explanation in the header file.
  977. // Split the file name into components. File name may have various format, but generally
  978. // structure is same:
  979. //
  980. // <name part>.<extension part> and <name part>.archived.<extension part>
  981. // or
  982. // <name part> and <name part>.archived
  983. //
  984. // So we want to search for the attrName in the components (ignoring the first array index).
  985. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  986. // Watch out for file names without an extension
  987. for (NSUInteger i = 1; i < components.count; i++) {
  988. NSString *attr = [components objectAtIndex:i];
  989. if ([attrName isEqualToString:attr]) {
  990. return YES;
  991. }
  992. }
  993. return NO;
  994. }
  995. - (void)addExtensionAttributeWithName:(NSString *)attrName {
  996. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  997. // See full explanation in the header file.
  998. if ([attrName length] == 0) {
  999. return;
  1000. }
  1001. // Example:
  1002. // attrName = "archived"
  1003. //
  1004. // "mylog.txt" -> "mylog.archived.txt"
  1005. // "mylog" -> "mylog.archived"
  1006. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  1007. NSUInteger count = [components count];
  1008. NSUInteger estimatedNewLength = [[self fileName] length] + [attrName length] + 1;
  1009. NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
  1010. if (count > 0) {
  1011. [newFileName appendString:[components objectAtIndex:0]];
  1012. }
  1013. NSString *lastExt = @"";
  1014. NSUInteger i;
  1015. for (i = 1; i < count; i++) {
  1016. NSString *attr = [components objectAtIndex:i];
  1017. if ([attr length] == 0) {
  1018. continue;
  1019. }
  1020. if ([attrName isEqualToString:attr]) {
  1021. // Extension attribute already exists in file name
  1022. return;
  1023. }
  1024. if ([lastExt length] > 0) {
  1025. [newFileName appendFormat:@".%@", lastExt];
  1026. }
  1027. lastExt = attr;
  1028. }
  1029. [newFileName appendFormat:@".%@", attrName];
  1030. if ([lastExt length] > 0) {
  1031. [newFileName appendFormat:@".%@", lastExt];
  1032. }
  1033. [self renameFile:newFileName];
  1034. }
  1035. - (void)removeExtensionAttributeWithName:(NSString *)attrName {
  1036. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  1037. // See full explanation in the header file.
  1038. if ([attrName length] == 0) {
  1039. return;
  1040. }
  1041. // Example:
  1042. // attrName = "archived"
  1043. //
  1044. // "mylog.archived.txt" -> "mylog.txt"
  1045. // "mylog.archived" -> "mylog"
  1046. NSArray *components = [[self fileName] componentsSeparatedByString:@"."];
  1047. NSUInteger count = [components count];
  1048. NSUInteger estimatedNewLength = [[self fileName] length];
  1049. NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
  1050. if (count > 0) {
  1051. [newFileName appendString:[components objectAtIndex:0]];
  1052. }
  1053. BOOL found = NO;
  1054. NSUInteger i;
  1055. for (i = 1; i < count; i++) {
  1056. NSString *attr = [components objectAtIndex:i];
  1057. if ([attrName isEqualToString:attr]) {
  1058. found = YES;
  1059. } else {
  1060. [newFileName appendFormat:@".%@", attr];
  1061. }
  1062. }
  1063. if (found) {
  1064. [self renameFile:newFileName];
  1065. }
  1066. }
  1067. #else /* if TARGET_IPHONE_SIMULATOR */
  1068. - (BOOL)hasExtendedAttributeWithName:(NSString *)attrName {
  1069. const char *path = [filePath UTF8String];
  1070. const char *name = [attrName UTF8String];
  1071. ssize_t result = getxattr(path, name, NULL, 0, 0, 0);
  1072. return (result >= 0);
  1073. }
  1074. - (void)addExtendedAttributeWithName:(NSString *)attrName {
  1075. const char *path = [filePath UTF8String];
  1076. const char *name = [attrName UTF8String];
  1077. int result = setxattr(path, name, NULL, 0, 0, 0);
  1078. if (result < 0) {
  1079. NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %s",
  1080. attrName,
  1081. self.fileName,
  1082. strerror(errno));
  1083. }
  1084. }
  1085. - (void)removeExtendedAttributeWithName:(NSString *)attrName {
  1086. const char *path = [filePath UTF8String];
  1087. const char *name = [attrName UTF8String];
  1088. int result = removexattr(path, name, 0);
  1089. if (result < 0 && errno != ENOATTR) {
  1090. NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %s",
  1091. attrName,
  1092. self.fileName,
  1093. strerror(errno));
  1094. }
  1095. }
  1096. #endif /* if TARGET_IPHONE_SIMULATOR */
  1097. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1098. #pragma mark Comparisons
  1099. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1100. - (BOOL)isEqual:(id)object {
  1101. if ([object isKindOfClass:[self class]]) {
  1102. DDLogFileInfo *another = (DDLogFileInfo *)object;
  1103. return [filePath isEqualToString:[another filePath]];
  1104. }
  1105. return NO;
  1106. }
  1107. - (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another {
  1108. NSDate *us = [self creationDate];
  1109. NSDate *them = [another creationDate];
  1110. NSComparisonResult result = [us compare:them];
  1111. if (result == NSOrderedAscending) {
  1112. return NSOrderedDescending;
  1113. }
  1114. if (result == NSOrderedDescending) {
  1115. return NSOrderedAscending;
  1116. }
  1117. return NSOrderedSame;
  1118. }
  1119. - (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another {
  1120. NSDate *us = [self modificationDate];
  1121. NSDate *them = [another modificationDate];
  1122. NSComparisonResult result = [us compare:them];
  1123. if (result == NSOrderedAscending) {
  1124. return NSOrderedDescending;
  1125. }
  1126. if (result == NSOrderedDescending) {
  1127. return NSOrderedAscending;
  1128. }
  1129. return NSOrderedSame;
  1130. }
  1131. @end
  1132. #if TARGET_OS_IPHONE
  1133. /**
  1134. * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
  1135. *
  1136. * But in case if app is able to launch from background we need to have an ability to open log file any time we
  1137. * want (even if device is locked). Thats why that attribute have to be changed to
  1138. * NSFileProtectionCompleteUntilFirstUserAuthentication.
  1139. */
  1140. BOOL doesAppRunInBackground() {
  1141. BOOL answer = NO;
  1142. NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
  1143. for (NSString *mode in backgroundModes) {
  1144. if (mode.length > 0) {
  1145. answer = YES;
  1146. break;
  1147. }
  1148. }
  1149. return answer;
  1150. }
  1151. #endif