DDFileLogger.m 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2021, 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. #if !__has_feature(objc_arc)
  16. #error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  17. #endif
  18. #import <sys/xattr.h>
  19. #import "DDFileLogger+Internal.h"
  20. // We probably shouldn't be using DDLog() statements within the DDLog implementation.
  21. // But we still want to leave our log statements for any future debugging,
  22. // and to allow other developers to trace the implementation (which is a great learning tool).
  23. //
  24. // So we use primitive logging macros around NSLog.
  25. // We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
  26. #ifndef DD_NSLOG_LEVEL
  27. #define DD_NSLOG_LEVEL 2
  28. #endif
  29. #define NSLogError(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
  30. #define NSLogWarn(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
  31. #define NSLogInfo(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
  32. #define NSLogDebug(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
  33. #define NSLogVerbose(frmt, ...) do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
  34. #if TARGET_OS_IPHONE
  35. BOOL doesAppRunInBackground(void);
  36. #endif
  37. unsigned long long const kDDDefaultLogMaxFileSize = 1024 * 1024; // 1 MB
  38. NSTimeInterval const kDDDefaultLogRollingFrequency = 60 * 60 * 24; // 24 Hours
  39. NSUInteger const kDDDefaultLogMaxNumLogFiles = 5; // 5 Files
  40. unsigned long long const kDDDefaultLogFilesDiskQuota = 20 * 1024 * 1024; // 20 MB
  41. NSTimeInterval const kDDRollingLeeway = 1.0; // 1s
  42. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  43. #pragma mark -
  44. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  45. @interface DDLogFileManagerDefault () {
  46. NSDateFormatter *_fileDateFormatter;
  47. NSUInteger _maximumNumberOfLogFiles;
  48. unsigned long long _logFilesDiskQuota;
  49. NSString *_logsDirectory;
  50. #if TARGET_OS_IPHONE
  51. NSFileProtectionType _defaultFileProtectionLevel;
  52. #endif
  53. }
  54. @end
  55. @implementation DDLogFileManagerDefault
  56. @synthesize maximumNumberOfLogFiles = _maximumNumberOfLogFiles;
  57. @synthesize logFilesDiskQuota = _logFilesDiskQuota;
  58. - (instancetype)init {
  59. return [self initWithLogsDirectory:nil];
  60. }
  61. - (instancetype)initWithLogsDirectory:(nullable NSString *)aLogsDirectory {
  62. if ((self = [super init])) {
  63. _maximumNumberOfLogFiles = kDDDefaultLogMaxNumLogFiles;
  64. _logFilesDiskQuota = kDDDefaultLogFilesDiskQuota;
  65. _fileDateFormatter = [[NSDateFormatter alloc] init];
  66. [_fileDateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
  67. [_fileDateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
  68. [_fileDateFormatter setDateFormat: @"yyyy'-'MM'-'dd'--'HH'-'mm'-'ss'-'SSS'"];
  69. if (aLogsDirectory.length > 0) {
  70. _logsDirectory = [aLogsDirectory copy];
  71. } else {
  72. _logsDirectory = [[self defaultLogsDirectory] copy];
  73. }
  74. NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]);
  75. NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]);
  76. }
  77. return self;
  78. }
  79. #if TARGET_OS_IPHONE
  80. - (instancetype)initWithLogsDirectory:(NSString *)logsDirectory
  81. defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel {
  82. if ((self = [self initWithLogsDirectory:logsDirectory])) {
  83. if ([fileProtectionLevel isEqualToString:NSFileProtectionNone] ||
  84. [fileProtectionLevel isEqualToString:NSFileProtectionComplete] ||
  85. [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUnlessOpen] ||
  86. [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) {
  87. _defaultFileProtectionLevel = fileProtectionLevel;
  88. }
  89. }
  90. return self;
  91. }
  92. #endif
  93. - (void)deleteOldFilesForConfigurationChange {
  94. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  95. @autoreleasepool {
  96. // See method header for queue reasoning.
  97. [self deleteOldLogFiles];
  98. }
  99. });
  100. }
  101. - (void)setLogFilesDiskQuota:(unsigned long long)logFilesDiskQuota {
  102. if (_logFilesDiskQuota != logFilesDiskQuota) {
  103. _logFilesDiskQuota = logFilesDiskQuota;
  104. NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: logFilesDiskQuota");
  105. [self deleteOldFilesForConfigurationChange];
  106. }
  107. }
  108. - (void)setMaximumNumberOfLogFiles:(NSUInteger)maximumNumberOfLogFiles {
  109. if (_maximumNumberOfLogFiles != maximumNumberOfLogFiles) {
  110. _maximumNumberOfLogFiles = maximumNumberOfLogFiles;
  111. NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles");
  112. [self deleteOldFilesForConfigurationChange];
  113. }
  114. }
  115. #if TARGET_OS_IPHONE
  116. - (NSFileProtectionType)logFileProtection {
  117. if (_defaultFileProtectionLevel.length > 0) {
  118. return _defaultFileProtectionLevel;
  119. } else if (doesAppRunInBackground()) {
  120. return NSFileProtectionCompleteUntilFirstUserAuthentication;
  121. } else {
  122. return NSFileProtectionCompleteUnlessOpen;
  123. }
  124. }
  125. #endif
  126. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  127. #pragma mark File Deleting
  128. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  129. /**
  130. * Deletes archived log files that exceed the maximumNumberOfLogFiles or logFilesDiskQuota configuration values.
  131. * Method may take a while to execute since we're performing IO. It's not critical that this is synchronized with
  132. * log output, since the files we're deleting are all archived and not in use, therefore this method is called on a
  133. * background queue.
  134. **/
  135. - (void)deleteOldLogFiles {
  136. NSLogVerbose(@"DDLogFileManagerDefault: deleteOldLogFiles");
  137. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  138. NSUInteger firstIndexToDelete = NSNotFound;
  139. const unsigned long long diskQuota = self.logFilesDiskQuota;
  140. const NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles;
  141. if (diskQuota) {
  142. unsigned long long used = 0;
  143. for (NSUInteger i = 0; i < sortedLogFileInfos.count; i++) {
  144. DDLogFileInfo *info = sortedLogFileInfos[i];
  145. used += info.fileSize;
  146. if (used > diskQuota) {
  147. firstIndexToDelete = i;
  148. break;
  149. }
  150. }
  151. }
  152. if (maxNumLogFiles) {
  153. if (firstIndexToDelete == NSNotFound) {
  154. firstIndexToDelete = maxNumLogFiles;
  155. } else {
  156. firstIndexToDelete = MIN(firstIndexToDelete, maxNumLogFiles);
  157. }
  158. }
  159. if (firstIndexToDelete == 0) {
  160. // Do we consider the first file?
  161. // We are only supposed to be deleting archived files.
  162. // In most cases, the first file is likely the log file that is currently being written to.
  163. // So in most cases, we do not want to consider this file for deletion.
  164. if (sortedLogFileInfos.count > 0) {
  165. DDLogFileInfo *logFileInfo = sortedLogFileInfos[0];
  166. if (!logFileInfo.isArchived) {
  167. // Don't delete active file.
  168. ++firstIndexToDelete;
  169. }
  170. }
  171. }
  172. if (firstIndexToDelete != NSNotFound) {
  173. // removing all log files starting with firstIndexToDelete
  174. for (NSUInteger i = firstIndexToDelete; i < sortedLogFileInfos.count; i++) {
  175. DDLogFileInfo *logFileInfo = sortedLogFileInfos[i];
  176. NSError *error = nil;
  177. BOOL success = [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:&error];
  178. if (success) {
  179. NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName);
  180. } else {
  181. NSLogError(@"DDLogFileManagerDefault: Error deleting file %@", error);
  182. }
  183. }
  184. }
  185. }
  186. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  187. #pragma mark Log Files
  188. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  189. /**
  190. * Returns the path to the default logs directory.
  191. * If the logs directory doesn't exist, this method automatically creates it.
  192. **/
  193. - (NSString *)defaultLogsDirectory {
  194. #if TARGET_OS_IPHONE
  195. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  196. NSString *baseDir = paths.firstObject;
  197. NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
  198. #else
  199. NSString *appName = [[NSProcessInfo processInfo] processName];
  200. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
  201. NSString *basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory();
  202. NSString *logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName];
  203. #endif
  204. return logsDirectory;
  205. }
  206. - (NSString *)logsDirectory {
  207. // We could do this check once, during initialization, and not bother again.
  208. // But this way the code continues to work if the directory gets deleted while the code is running.
  209. NSAssert(_logsDirectory.length > 0, @"Directory must be set.");
  210. NSError *err = nil;
  211. BOOL success = [[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory
  212. withIntermediateDirectories:YES
  213. attributes:nil
  214. error:&err];
  215. if (success == NO) {
  216. NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", err);
  217. }
  218. return _logsDirectory;
  219. }
  220. - (BOOL)isLogFile:(NSString *)fileName {
  221. NSString *appName = [self applicationName];
  222. // We need to add a space to the name as otherwise we could match applications that have the name prefix.
  223. BOOL hasProperPrefix = [fileName hasPrefix:[appName stringByAppendingString:@" "]];
  224. BOOL hasProperSuffix = [fileName hasSuffix:@".log"];
  225. return (hasProperPrefix && hasProperSuffix);
  226. }
  227. // if you change formatter, then change sortedLogFileInfos method also accordingly
  228. - (NSDateFormatter *)logFileDateFormatter {
  229. return _fileDateFormatter;
  230. }
  231. - (NSArray *)unsortedLogFilePaths {
  232. NSString *logsDirectory = [self logsDirectory];
  233. NSArray *fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:nil];
  234. NSMutableArray *unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]];
  235. for (NSString *fileName in fileNames) {
  236. // Filter out any files that aren't log files. (Just for extra safety)
  237. #if TARGET_IPHONE_SIMULATOR
  238. // This is only used on the iPhone simulator for backward compatibility reason.
  239. //
  240. // In case of iPhone simulator there can be 'archived' extension. isLogFile:
  241. // method knows nothing about it. Thus removing it for this method.
  242. NSString *theFileName = [fileName stringByReplacingOccurrencesOfString:@".archived"
  243. withString:@""];
  244. if ([self isLogFile:theFileName])
  245. #else
  246. if ([self isLogFile:fileName])
  247. #endif
  248. {
  249. NSString *filePath = [logsDirectory stringByAppendingPathComponent:fileName];
  250. [unsortedLogFilePaths addObject:filePath];
  251. }
  252. }
  253. return unsortedLogFilePaths;
  254. }
  255. - (NSArray *)unsortedLogFileNames {
  256. NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
  257. NSMutableArray *unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
  258. for (NSString *filePath in unsortedLogFilePaths) {
  259. [unsortedLogFileNames addObject:[filePath lastPathComponent]];
  260. }
  261. return unsortedLogFileNames;
  262. }
  263. - (NSArray *)unsortedLogFileInfos {
  264. NSArray *unsortedLogFilePaths = [self unsortedLogFilePaths];
  265. NSMutableArray *unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
  266. for (NSString *filePath in unsortedLogFilePaths) {
  267. DDLogFileInfo *logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath];
  268. [unsortedLogFileInfos addObject:logFileInfo];
  269. }
  270. return unsortedLogFileInfos;
  271. }
  272. - (NSArray *)sortedLogFilePaths {
  273. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  274. NSMutableArray *sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
  275. for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
  276. [sortedLogFilePaths addObject:[logFileInfo filePath]];
  277. }
  278. return sortedLogFilePaths;
  279. }
  280. - (NSArray *)sortedLogFileNames {
  281. NSArray *sortedLogFileInfos = [self sortedLogFileInfos];
  282. NSMutableArray *sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
  283. for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
  284. [sortedLogFileNames addObject:[logFileInfo fileName]];
  285. }
  286. return sortedLogFileNames;
  287. }
  288. - (NSArray *)sortedLogFileInfos {
  289. return [[self unsortedLogFileInfos] sortedArrayUsingComparator:^NSComparisonResult(DDLogFileInfo *obj1,
  290. DDLogFileInfo *obj2) {
  291. NSDate *date1 = [NSDate new];
  292. NSDate *date2 = [NSDate new];
  293. NSArray<NSString *> *arrayComponent = [[obj1 fileName] componentsSeparatedByString:@" "];
  294. if (arrayComponent.count > 0) {
  295. NSString *stringDate = arrayComponent.lastObject;
  296. stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""];
  297. #if TARGET_IPHONE_SIMULATOR
  298. // This is only used on the iPhone simulator for backward compatibility reason.
  299. stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""];
  300. #endif
  301. date1 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj1 creationDate];
  302. }
  303. arrayComponent = [[obj2 fileName] componentsSeparatedByString:@" "];
  304. if (arrayComponent.count > 0) {
  305. NSString *stringDate = arrayComponent.lastObject;
  306. stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""];
  307. #if TARGET_IPHONE_SIMULATOR
  308. // This is only used on the iPhone simulator for backward compatibility reason.
  309. stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""];
  310. #endif
  311. date2 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj2 creationDate];
  312. }
  313. return [date2 compare:date1 ?: [NSDate new]];
  314. }];
  315. }
  316. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  317. #pragma mark Creation
  318. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  319. //if you change newLogFileName , then change isLogFile method also accordingly
  320. - (NSString *)newLogFileName {
  321. NSString *appName = [self applicationName];
  322. NSDateFormatter *dateFormatter = [self logFileDateFormatter];
  323. NSString *formattedDate = [dateFormatter stringFromDate:[NSDate date]];
  324. return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate];
  325. }
  326. - (nullable NSString *)logFileHeader {
  327. return nil;
  328. }
  329. - (NSData *)logFileHeaderData {
  330. NSString *fileHeaderStr = [self logFileHeader];
  331. if (fileHeaderStr.length == 0) {
  332. return nil;
  333. }
  334. if (![fileHeaderStr hasSuffix:@"\n"]) {
  335. fileHeaderStr = [fileHeaderStr stringByAppendingString:@"\n"];
  336. }
  337. return [fileHeaderStr dataUsingEncoding:NSUTF8StringEncoding];
  338. }
  339. - (NSString *)createNewLogFileWithError:(NSError *__autoreleasing _Nullable *)error {
  340. static NSUInteger MAX_ALLOWED_ERROR = 5;
  341. NSString *fileName = [self newLogFileName];
  342. NSString *logsDirectory = [self logsDirectory];
  343. NSData *fileHeader = [self logFileHeaderData];
  344. if (fileHeader == nil) {
  345. fileHeader = [NSData new];
  346. }
  347. NSUInteger attempt = 1;
  348. NSUInteger criticalErrors = 0;
  349. NSError *lastCriticalError;
  350. do {
  351. if (criticalErrors >= MAX_ALLOWED_ERROR) {
  352. NSLogError(@"DDLogFileManagerDefault: Bailing file creation, encountered %ld errors.",
  353. (unsigned long)criticalErrors);
  354. *error = lastCriticalError;
  355. return nil;
  356. }
  357. NSString *actualFileName = fileName;
  358. if (attempt > 1) {
  359. NSString *extension = [actualFileName pathExtension];
  360. actualFileName = [actualFileName stringByDeletingPathExtension];
  361. actualFileName = [actualFileName stringByAppendingFormat:@" %lu", (unsigned long)attempt];
  362. if (extension.length) {
  363. actualFileName = [actualFileName stringByAppendingPathExtension:extension];
  364. }
  365. }
  366. NSString *filePath = [logsDirectory stringByAppendingPathComponent:actualFileName];
  367. NSError *currentError = nil;
  368. BOOL success = [fileHeader writeToFile:filePath options:NSDataWritingAtomic error:&currentError];
  369. #if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
  370. if (success) {
  371. // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
  372. //
  373. // But in case if app is able to launch from background we need to have an ability to open log file any time we
  374. // want (even if device is locked). Thats why that attribute have to be changed to
  375. // NSFileProtectionCompleteUntilFirstUserAuthentication.
  376. NSDictionary *attributes = @{NSFileProtectionKey: [self logFileProtection]};
  377. success = [[NSFileManager defaultManager] setAttributes:attributes
  378. ofItemAtPath:filePath
  379. error:&currentError];
  380. }
  381. #endif
  382. if (success) {
  383. NSLogVerbose(@"DDLogFileManagerDefault: Created new log file: %@", actualFileName);
  384. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  385. // Since we just created a new log file, we may need to delete some old log files
  386. [self deleteOldLogFiles];
  387. });
  388. return filePath;
  389. } else if (currentError.code == NSFileWriteFileExistsError) {
  390. attempt++;
  391. continue;
  392. } else {
  393. NSLogError(@"DDLogFileManagerDefault: Critical error while creating log file: %@", currentError);
  394. criticalErrors++;
  395. lastCriticalError = currentError;
  396. continue;
  397. }
  398. return filePath;
  399. } while (YES);
  400. }
  401. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  402. #pragma mark Utility
  403. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  404. - (NSString *)applicationName {
  405. static NSString *_appName;
  406. static dispatch_once_t onceToken;
  407. dispatch_once(&onceToken, ^{
  408. _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
  409. if (_appName.length == 0) {
  410. _appName = [[NSProcessInfo processInfo] processName];
  411. }
  412. if (_appName.length == 0) {
  413. _appName = @"";
  414. }
  415. });
  416. return _appName;
  417. }
  418. @end
  419. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  420. #pragma mark -
  421. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  422. @interface DDLogFileFormatterDefault () {
  423. NSDateFormatter *_dateFormatter;
  424. }
  425. @end
  426. @implementation DDLogFileFormatterDefault
  427. - (instancetype)init {
  428. return [self initWithDateFormatter:nil];
  429. }
  430. - (instancetype)initWithDateFormatter:(nullable NSDateFormatter *)aDateFormatter {
  431. if ((self = [super init])) {
  432. if (aDateFormatter) {
  433. _dateFormatter = aDateFormatter;
  434. } else {
  435. _dateFormatter = [[NSDateFormatter alloc] init];
  436. [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
  437. [_dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
  438. [_dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
  439. [_dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
  440. }
  441. }
  442. return self;
  443. }
  444. - (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
  445. NSString *dateAndTime = [_dateFormatter stringFromDate:logMessage->_timestamp];
  446. return [NSString stringWithFormat:@"%@ %@", dateAndTime, logMessage->_message];
  447. }
  448. @end
  449. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  450. #pragma mark -
  451. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  452. @interface DDFileLogger () {
  453. id <DDLogFileManager> _logFileManager;
  454. DDLogFileInfo *_currentLogFileInfo;
  455. NSFileHandle *_currentLogFileHandle;
  456. dispatch_source_t _currentLogFileVnode;
  457. NSTimeInterval _rollingFrequency;
  458. dispatch_source_t _rollingTimer;
  459. unsigned long long _maximumFileSize;
  460. dispatch_queue_t _completionQueue;
  461. }
  462. @end
  463. #pragma clang diagnostic push
  464. #pragma clang diagnostic ignored "-Wincomplete-implementation"
  465. @implementation DDFileLogger
  466. #pragma clang diagnostic pop
  467. - (instancetype)init {
  468. DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];
  469. return [self initWithLogFileManager:defaultLogFileManager completionQueue:nil];
  470. }
  471. - (instancetype)initWithLogFileManager:(id<DDLogFileManager>)logFileManager {
  472. return [self initWithLogFileManager:logFileManager completionQueue:nil];
  473. }
  474. - (instancetype)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager
  475. completionQueue:(nullable dispatch_queue_t)dispatchQueue {
  476. if ((self = [super init])) {
  477. _completionQueue = dispatchQueue ?: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  478. _maximumFileSize = kDDDefaultLogMaxFileSize;
  479. _rollingFrequency = kDDDefaultLogRollingFrequency;
  480. _automaticallyAppendNewlineForCustomFormatters = YES;
  481. _logFileManager = aLogFileManager;
  482. _logFormatter = [DDLogFileFormatterDefault new];
  483. }
  484. return self;
  485. }
  486. - (void)lt_cleanup {
  487. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  488. if (_currentLogFileHandle != nil) {
  489. if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
  490. __autoreleasing NSError *error;
  491. BOOL synchronized = [_currentLogFileHandle synchronizeAndReturnError:&error];
  492. if (!synchronized) {
  493. NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
  494. }
  495. BOOL closed = [_currentLogFileHandle closeAndReturnError:&error];
  496. if (!closed) {
  497. NSLogError(@"DDFileLogger: Failed to close file: %@", error);
  498. }
  499. } else {
  500. @try {
  501. [_currentLogFileHandle synchronizeFile];
  502. } @catch (NSException *exception) {
  503. NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
  504. }
  505. [_currentLogFileHandle closeFile];
  506. }
  507. _currentLogFileHandle = nil;
  508. }
  509. if (_currentLogFileVnode) {
  510. dispatch_source_cancel(_currentLogFileVnode);
  511. _currentLogFileVnode = NULL;
  512. }
  513. if (_rollingTimer) {
  514. dispatch_source_cancel(_rollingTimer);
  515. _rollingTimer = NULL;
  516. }
  517. }
  518. - (void)dealloc {
  519. if (self.isOnInternalLoggerQueue) {
  520. [self lt_cleanup];
  521. } else {
  522. dispatch_sync(self.loggerQueue, ^{
  523. [self lt_cleanup];
  524. });
  525. }
  526. }
  527. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  528. #pragma mark Properties
  529. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  530. - (unsigned long long)maximumFileSize {
  531. __block unsigned long long result;
  532. dispatch_block_t block = ^{
  533. result = self->_maximumFileSize;
  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_sync(globalLoggingQueue, ^{
  547. dispatch_sync(self.loggerQueue, block);
  548. });
  549. return result;
  550. }
  551. - (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize {
  552. dispatch_block_t block = ^{
  553. @autoreleasepool {
  554. self->_maximumFileSize = newMaximumFileSize;
  555. if (self->_currentLogFileHandle != nil || [self lt_currentLogFileHandle] != nil) {
  556. [self lt_maybeRollLogFileDueToSize];
  557. }
  558. }
  559. };
  560. // The design of this method is taken from the DDAbstractLogger implementation.
  561. // For extensive documentation please refer to the DDAbstractLogger implementation.
  562. // Note: The internal implementation MUST access the maximumFileSize variable directly,
  563. // This method is designed explicitly for external access.
  564. //
  565. // Using "self." syntax to go through this method will cause immediate deadlock.
  566. // This is the intended result. Fix it by accessing the ivar directly.
  567. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  568. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  569. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  570. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  571. dispatch_async(globalLoggingQueue, ^{
  572. dispatch_async(self.loggerQueue, block);
  573. });
  574. }
  575. - (NSTimeInterval)rollingFrequency {
  576. __block NSTimeInterval result;
  577. dispatch_block_t block = ^{
  578. result = self->_rollingFrequency;
  579. };
  580. // The design of this method is taken from the DDAbstractLogger implementation.
  581. // For extensive documentation please refer to the DDAbstractLogger implementation.
  582. // Note: The internal implementation should access the rollingFrequency variable directly,
  583. // This method is designed explicitly for external access.
  584. //
  585. // Using "self." syntax to go through this method will cause immediate deadlock.
  586. // This is the intended result. Fix it by accessing the ivar directly.
  587. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  588. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  589. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  590. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  591. dispatch_sync(globalLoggingQueue, ^{
  592. dispatch_sync(self.loggerQueue, block);
  593. });
  594. return result;
  595. }
  596. - (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency {
  597. dispatch_block_t block = ^{
  598. @autoreleasepool {
  599. self->_rollingFrequency = newRollingFrequency;
  600. [self lt_maybeRollLogFileDueToAge];
  601. }
  602. };
  603. // The design of this method is taken from the DDAbstractLogger implementation.
  604. // For extensive documentation please refer to the DDAbstractLogger implementation.
  605. // Note: The internal implementation should access the rollingFrequency variable directly,
  606. // This method is designed explicitly for external access.
  607. //
  608. // Using "self." syntax to go through this method will cause immediate deadlock.
  609. // This is the intended result. Fix it by accessing the ivar directly.
  610. // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
  611. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  612. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  613. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  614. dispatch_async(globalLoggingQueue, ^{
  615. dispatch_async(self.loggerQueue, block);
  616. });
  617. }
  618. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  619. #pragma mark File Rolling
  620. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  621. - (void)lt_scheduleTimerToRollLogFileDueToAge {
  622. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  623. if (_rollingTimer) {
  624. dispatch_source_cancel(_rollingTimer);
  625. _rollingTimer = NULL;
  626. }
  627. if (_currentLogFileInfo == nil || _rollingFrequency <= 0.0) {
  628. return;
  629. }
  630. NSDate *logFileCreationDate = [_currentLogFileInfo creationDate];
  631. NSTimeInterval frequency = MIN(_rollingFrequency, DBL_MAX - [logFileCreationDate timeIntervalSinceReferenceDate]);
  632. NSDate *logFileRollingDate = [logFileCreationDate dateByAddingTimeInterval:frequency];
  633. NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
  634. NSLogVerbose(@"DDFileLogger: logFileCreationDate : %@", logFileCreationDate);
  635. NSLogVerbose(@"DDFileLogger: actual rollingFrequency: %f", frequency);
  636. NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
  637. _rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _loggerQueue);
  638. __weak __auto_type weakSelf = self;
  639. dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool {
  640. [weakSelf lt_maybeRollLogFileDueToAge];
  641. } });
  642. #if !OS_OBJECT_USE_OBJC
  643. dispatch_source_t theRollingTimer = _rollingTimer;
  644. dispatch_source_set_cancel_handler(_rollingTimer, ^{
  645. dispatch_release(theRollingTimer);
  646. });
  647. #endif
  648. static NSTimeInterval const kDDMaxTimerDelay = LLONG_MAX / NSEC_PER_SEC;
  649. int64_t delay = (int64_t)(MIN([logFileRollingDate timeIntervalSinceNow], kDDMaxTimerDelay) * (NSTimeInterval) NSEC_PER_SEC);
  650. dispatch_time_t fireTime = dispatch_time(DISPATCH_TIME_NOW, delay);
  651. dispatch_source_set_timer(_rollingTimer, fireTime, DISPATCH_TIME_FOREVER, (uint64_t)kDDRollingLeeway * NSEC_PER_SEC);
  652. if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *))
  653. dispatch_activate(_rollingTimer);
  654. else
  655. dispatch_resume(_rollingTimer);
  656. }
  657. - (void)rollLogFile {
  658. [self rollLogFileWithCompletionBlock:nil];
  659. }
  660. - (void)rollLogFileWithCompletionBlock:(nullable void (^)(void))completionBlock {
  661. // This method is public.
  662. // We need to execute the rolling on our logging thread/queue.
  663. dispatch_block_t block = ^{
  664. @autoreleasepool {
  665. [self lt_rollLogFileNow];
  666. if (completionBlock) {
  667. dispatch_async(self->_completionQueue, ^{
  668. completionBlock();
  669. });
  670. }
  671. }
  672. };
  673. // The design of this method is taken from the DDAbstractLogger implementation.
  674. // For extensive documentation please refer to the DDAbstractLogger implementation.
  675. if ([self isOnInternalLoggerQueue]) {
  676. block();
  677. } else {
  678. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  679. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  680. dispatch_async(globalLoggingQueue, ^{
  681. dispatch_async(self.loggerQueue, block);
  682. });
  683. }
  684. }
  685. - (void)lt_rollLogFileNow {
  686. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  687. NSLogVerbose(@"DDFileLogger: rollLogFileNow");
  688. if (_currentLogFileHandle == nil) {
  689. return;
  690. }
  691. if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
  692. __autoreleasing NSError *error;
  693. BOOL synchronized = [_currentLogFileHandle synchronizeAndReturnError:&error];
  694. if (!synchronized) {
  695. NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
  696. }
  697. BOOL closed = [_currentLogFileHandle closeAndReturnError:&error];
  698. if (!closed) {
  699. NSLogError(@"DDFileLogger: Failed to close file: %@", error);
  700. }
  701. } else {
  702. @try {
  703. [_currentLogFileHandle synchronizeFile];
  704. } @catch (NSException *exception) {
  705. NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
  706. }
  707. [_currentLogFileHandle closeFile];
  708. }
  709. _currentLogFileHandle = nil;
  710. _currentLogFileInfo.isArchived = YES;
  711. const BOOL logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)];
  712. const BOOL logFileManagerRespondsToSelector = (logFileManagerRespondsToNewArchiveSelector
  713. || [_logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]);
  714. NSString *archivedFilePath = (logFileManagerRespondsToSelector) ? [_currentLogFileInfo.filePath copy] : nil;
  715. _currentLogFileInfo = nil;
  716. if (logFileManagerRespondsToSelector) {
  717. dispatch_block_t block;
  718. if (logFileManagerRespondsToNewArchiveSelector) {
  719. block = ^{
  720. [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:YES];
  721. };
  722. } else {
  723. block = ^{
  724. #pragma clang diagnostic push
  725. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  726. [self->_logFileManager didRollAndArchiveLogFile:archivedFilePath];
  727. #pragma clang diagnostic pop
  728. };
  729. }
  730. dispatch_async(_completionQueue, block);
  731. }
  732. if (_currentLogFileVnode) {
  733. dispatch_source_cancel(_currentLogFileVnode);
  734. _currentLogFileVnode = nil;
  735. }
  736. if (_rollingTimer) {
  737. dispatch_source_cancel(_rollingTimer);
  738. _rollingTimer = nil;
  739. }
  740. }
  741. - (void)lt_maybeRollLogFileDueToAge {
  742. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  743. if (_rollingFrequency > 0.0 && (_currentLogFileInfo.age + kDDRollingLeeway) >= _rollingFrequency) {
  744. NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
  745. [self lt_rollLogFileNow];
  746. } else {
  747. [self lt_scheduleTimerToRollLogFileDueToAge];
  748. }
  749. }
  750. - (void)lt_maybeRollLogFileDueToSize {
  751. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  752. // This method is called from logMessage.
  753. // Keep it FAST.
  754. // Note: Use direct access to maximumFileSize variable.
  755. // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons).
  756. if (_currentLogFileHandle != nil && _maximumFileSize > 0) {
  757. unsigned long long fileSize;
  758. if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
  759. __autoreleasing NSError *error;
  760. BOOL succeed = [_currentLogFileHandle getOffset:&fileSize error:&error];
  761. if (!succeed) {
  762. NSLogError(@"DDFileLogger: Failed to get offset: %@", error);
  763. return;
  764. }
  765. } else {
  766. fileSize = [_currentLogFileHandle offsetInFile];
  767. }
  768. if (fileSize >= _maximumFileSize) {
  769. NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize);
  770. [self lt_rollLogFileNow];
  771. }
  772. }
  773. }
  774. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  775. #pragma mark File Logging
  776. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  777. - (BOOL)lt_shouldLogFileBeArchived:(DDLogFileInfo *)mostRecentLogFileInfo {
  778. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  779. if ([self shouldArchiveRecentLogFileInfo:mostRecentLogFileInfo]) {
  780. return YES;
  781. } else if (_maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= _maximumFileSize) {
  782. return YES;
  783. } else if (_rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= _rollingFrequency) {
  784. return YES;
  785. }
  786. #if TARGET_OS_IPHONE
  787. // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
  788. //
  789. // But in case if app is able to launch from background we need to have an ability to open log file any time we
  790. // want (even if device is locked). Thats why that attribute have to be changed to
  791. // NSFileProtectionCompleteUntilFirstUserAuthentication.
  792. //
  793. // If previous log was created when app wasn't running in background, but now it is - we archive it and create
  794. // a new one.
  795. //
  796. // If user has overwritten to NSFileProtectionNone there is no need to create a new one.
  797. if (doesAppRunInBackground()) {
  798. NSFileProtectionType key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey];
  799. BOOL isUntilFirstAuth = [key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication];
  800. BOOL isNone = [key isEqualToString:NSFileProtectionNone];
  801. if (key != nil && !isUntilFirstAuth && !isNone) {
  802. return YES;
  803. }
  804. }
  805. #endif
  806. return NO;
  807. }
  808. /**
  809. * Returns the log file that should be used.
  810. * If there is an existing log file that is suitable, within the
  811. * constraints of maximumFileSize and rollingFrequency, then it is returned.
  812. *
  813. * Otherwise a new file is created and returned.
  814. **/
  815. - (DDLogFileInfo *)currentLogFileInfo {
  816. // The design of this method is taken from the DDAbstractLogger implementation.
  817. // For extensive documentation please refer to the DDAbstractLogger implementation.
  818. // Do not access this method on any Lumberjack queue, will deadlock.
  819. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  820. NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
  821. __block DDLogFileInfo *info = nil;
  822. dispatch_block_t block = ^{
  823. info = [self lt_currentLogFileInfo];
  824. };
  825. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  826. dispatch_sync(globalLoggingQueue, ^{
  827. dispatch_sync(self->_loggerQueue, block);
  828. });
  829. return info;
  830. }
  831. - (DDLogFileInfo *)lt_currentLogFileInfo {
  832. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  833. // Get the current log file info ivar (might be nil).
  834. DDLogFileInfo *newCurrentLogFile = _currentLogFileInfo;
  835. // Check if we're resuming and if so, get the first of the sorted log file infos.
  836. BOOL isResuming = newCurrentLogFile == nil;
  837. if (isResuming) {
  838. NSArray *sortedLogFileInfos = [_logFileManager sortedLogFileInfos];
  839. newCurrentLogFile = sortedLogFileInfos.firstObject;
  840. }
  841. // Check if the file we've found is still valid. Otherwise create a new one.
  842. if (newCurrentLogFile != nil && [self lt_shouldUseLogFile:newCurrentLogFile isResuming:isResuming]) {
  843. if (isResuming) {
  844. NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", newCurrentLogFile.fileName);
  845. }
  846. _currentLogFileInfo = newCurrentLogFile;
  847. } else {
  848. NSString *currentLogFilePath;
  849. if ([_logFileManager respondsToSelector:@selector(createNewLogFileWithError:)]) {
  850. __autoreleasing NSError *error;
  851. currentLogFilePath = [_logFileManager createNewLogFileWithError:&error];
  852. if (!currentLogFilePath) {
  853. NSLogError(@"DDFileLogger: Failed to create new log file: %@", error);
  854. }
  855. } else {
  856. #pragma clang diagnostic push
  857. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  858. NSAssert([_logFileManager respondsToSelector:@selector(createNewLogFile)],
  859. @"Invalid log file manager! Responds neither to `-createNewLogFileWithError:` nor `-createNewLogFile`!");
  860. currentLogFilePath = [_logFileManager createNewLogFile];
  861. #pragma clang diagnostic pop
  862. }
  863. // Use static factory method here, since it checks for nil (and is unavailable to Swift).
  864. _currentLogFileInfo = [DDLogFileInfo logFileWithPath:currentLogFilePath];
  865. }
  866. return _currentLogFileInfo;
  867. }
  868. - (BOOL)lt_shouldUseLogFile:(nonnull DDLogFileInfo *)logFileInfo isResuming:(BOOL)isResuming {
  869. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  870. NSParameterAssert(logFileInfo);
  871. // Check if the log file is archived. We must not use archived log files.
  872. if (logFileInfo.isArchived) {
  873. return NO;
  874. }
  875. // If we're resuming, we need to check if the log file is allowed for reuse or needs to be archived.
  876. if (isResuming && (_doNotReuseLogFiles || [self lt_shouldLogFileBeArchived:logFileInfo])) {
  877. logFileInfo.isArchived = YES;
  878. const BOOL logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)];
  879. if (logFileManagerRespondsToNewArchiveSelector || [_logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) {
  880. NSString *archivedFilePath = [logFileInfo.filePath copy];
  881. dispatch_block_t block;
  882. if (logFileManagerRespondsToNewArchiveSelector) {
  883. block = ^{
  884. [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:NO];
  885. };
  886. } else {
  887. block = ^{
  888. #pragma clang diagnostic push
  889. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  890. [self->_logFileManager didArchiveLogFile:archivedFilePath];
  891. #pragma clang diagnostic pop
  892. };
  893. }
  894. dispatch_async(_completionQueue, block);
  895. }
  896. return NO;
  897. }
  898. // All checks have passed. It's valid.
  899. return YES;
  900. }
  901. - (void)lt_monitorCurrentLogFileForExternalChanges {
  902. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  903. NSAssert(_currentLogFileHandle, @"Can not monitor without handle.");
  904. _currentLogFileVnode = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
  905. (uintptr_t)[_currentLogFileHandle fileDescriptor],
  906. DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
  907. _loggerQueue);
  908. __weak __auto_type weakSelf = self;
  909. dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool {
  910. NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one");
  911. [weakSelf lt_rollLogFileNow];
  912. } });
  913. #if !OS_OBJECT_USE_OBJC
  914. dispatch_source_t vnode = _currentLogFileVnode;
  915. dispatch_source_set_cancel_handler(_currentLogFileVnode, ^{
  916. dispatch_release(vnode);
  917. });
  918. #endif
  919. if (@available(macOS 10.12, iOS 10.0, tvOS 10.0, watchOS 3.0, *)) {
  920. dispatch_activate(_currentLogFileVnode);
  921. } else {
  922. dispatch_resume(_currentLogFileVnode);
  923. }
  924. }
  925. - (NSFileHandle *)lt_currentLogFileHandle {
  926. NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
  927. if (_currentLogFileHandle == nil) {
  928. NSString *logFilePath = [[self lt_currentLogFileInfo] filePath];
  929. _currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
  930. if (_currentLogFileHandle != nil) {
  931. if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
  932. __autoreleasing NSError *error;
  933. BOOL succeed = [_currentLogFileHandle seekToEndReturningOffset:nil error:&error];
  934. if (!succeed) {
  935. NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error);
  936. }
  937. } else {
  938. [_currentLogFileHandle seekToEndOfFile];
  939. }
  940. [self lt_scheduleTimerToRollLogFileDueToAge];
  941. [self lt_monitorCurrentLogFileForExternalChanges];
  942. }
  943. }
  944. return _currentLogFileHandle;
  945. }
  946. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  947. #pragma mark DDLogger Protocol
  948. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  949. static int exception_count = 0;
  950. - (void)logMessage:(DDLogMessage *)logMessage {
  951. // Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us.
  952. NSData *data = [self lt_dataForMessage:logMessage];
  953. if (data.length == 0) {
  954. return;
  955. }
  956. [self lt_logData:data];
  957. }
  958. - (void)willLogMessage:(DDLogFileInfo *)logFileInfo {}
  959. - (void)didLogMessage:(DDLogFileInfo *)logFileInfo {
  960. [self lt_maybeRollLogFileDueToSize];
  961. }
  962. - (BOOL)shouldArchiveRecentLogFileInfo:(__unused DDLogFileInfo *)recentLogFileInfo {
  963. return NO;
  964. }
  965. - (void)willRemoveLogger {
  966. [self lt_rollLogFileNow];
  967. }
  968. - (void)flush {
  969. // This method is public.
  970. // We need to execute the rolling on our logging thread/queue.
  971. dispatch_block_t block = ^{
  972. @autoreleasepool {
  973. [self lt_flush];
  974. }
  975. };
  976. // The design of this method is taken from the DDAbstractLogger implementation.
  977. // For extensive documentation please refer to the DDAbstractLogger implementation.
  978. if ([self isOnInternalLoggerQueue]) {
  979. block();
  980. } else {
  981. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  982. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  983. dispatch_sync(globalLoggingQueue, ^{
  984. dispatch_sync(self.loggerQueue, block);
  985. });
  986. }
  987. }
  988. - (void)lt_flush {
  989. NSAssert([self isOnInternalLoggerQueue], @"flush should only be executed on internal queue.");
  990. if (_currentLogFileHandle != nil) {
  991. if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
  992. __autoreleasing NSError *error;
  993. BOOL succeed = [_currentLogFileHandle synchronizeAndReturnError:&error];
  994. if (!succeed) {
  995. NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
  996. }
  997. } else {
  998. @try {
  999. [_currentLogFileHandle synchronizeFile];
  1000. } @catch (NSException *exception) {
  1001. NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
  1002. }
  1003. }
  1004. }
  1005. }
  1006. - (DDLoggerName)loggerName {
  1007. return DDLoggerNameFile;
  1008. }
  1009. @end
  1010. @implementation DDFileLogger (Internal)
  1011. - (void)logData:(NSData *)data {
  1012. // This method is public.
  1013. // We need to execute the rolling on our logging thread/queue.
  1014. dispatch_block_t block = ^{
  1015. @autoreleasepool {
  1016. [self lt_logData:data];
  1017. }
  1018. };
  1019. // The design of this method is taken from the DDAbstractLogger implementation.
  1020. // For extensive documentation please refer to the DDAbstractLogger implementation.
  1021. if ([self isOnInternalLoggerQueue]) {
  1022. block();
  1023. } else {
  1024. dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
  1025. NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
  1026. dispatch_sync(globalLoggingQueue, ^{
  1027. dispatch_sync(self.loggerQueue, block);
  1028. });
  1029. }
  1030. }
  1031. - (void)lt_deprecationCatchAll {}
  1032. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  1033. if (aSelector == @selector(willLogMessage) || aSelector == @selector(didLogMessage)) {
  1034. // Ignore calls to deprecated methods.
  1035. return [self methodSignatureForSelector:@selector(lt_deprecationCatchAll)];
  1036. }
  1037. return [super methodSignatureForSelector:aSelector];
  1038. }
  1039. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  1040. if (anInvocation.selector != @selector(lt_deprecationCatchAll)) {
  1041. [super forwardInvocation:anInvocation];
  1042. }
  1043. }
  1044. - (void)lt_logData:(NSData *)data {
  1045. static BOOL implementsDeprecatedWillLog = NO;
  1046. static BOOL implementsDeprecatedDidLog = NO;
  1047. static dispatch_once_t onceToken;
  1048. dispatch_once(&onceToken, ^{
  1049. implementsDeprecatedWillLog = [self respondsToSelector:@selector(willLogMessage)];
  1050. implementsDeprecatedDidLog = [self respondsToSelector:@selector(didLogMessage)];
  1051. });
  1052. NSAssert([self isOnInternalLoggerQueue], @"logMessage should only be executed on internal queue.");
  1053. if (data.length == 0) {
  1054. return;
  1055. }
  1056. @try {
  1057. if (implementsDeprecatedWillLog) {
  1058. #pragma clang diagnostic push
  1059. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1060. [self willLogMessage];
  1061. #pragma clang diagnostic pop
  1062. } else {
  1063. [self willLogMessage:_currentLogFileInfo];
  1064. }
  1065. NSFileHandle *handle = [self lt_currentLogFileHandle];
  1066. if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
  1067. __autoreleasing NSError *error;
  1068. BOOL sought = [handle seekToEndReturningOffset:nil error:&error];
  1069. if (!sought) {
  1070. NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error);
  1071. }
  1072. BOOL written = [handle writeData:data error:&error];
  1073. if (!written) {
  1074. NSLogError(@"DDFileLogger: Failed to write data: %@", error);
  1075. }
  1076. } else {
  1077. [handle seekToEndOfFile];
  1078. [handle writeData:data];
  1079. }
  1080. if (implementsDeprecatedDidLog) {
  1081. #pragma clang diagnostic push
  1082. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1083. [self didLogMessage];
  1084. #pragma clang diagnostic pop
  1085. } else {
  1086. [self didLogMessage:_currentLogFileInfo];
  1087. }
  1088. } @catch (NSException *exception) {
  1089. exception_count++;
  1090. if (exception_count <= 10) {
  1091. NSLogError(@"DDFileLogger.logMessage: %@", exception);
  1092. if (exception_count == 10) {
  1093. NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them.");
  1094. }
  1095. }
  1096. }
  1097. }
  1098. - (NSData *)lt_dataForMessage:(DDLogMessage *)logMessage {
  1099. NSAssert([self isOnInternalLoggerQueue], @"logMessage should only be executed on internal queue.");
  1100. NSString *message = logMessage->_message;
  1101. BOOL isFormatted = NO;
  1102. if (_logFormatter != nil) {
  1103. message = [_logFormatter formatLogMessage:logMessage];
  1104. isFormatted = message != logMessage->_message;
  1105. }
  1106. if (message.length == 0) {
  1107. return nil;
  1108. }
  1109. BOOL shouldFormat = !isFormatted || _automaticallyAppendNewlineForCustomFormatters;
  1110. if (shouldFormat && ![message hasSuffix:@"\n"]) {
  1111. message = [message stringByAppendingString:@"\n"];
  1112. }
  1113. return [message dataUsingEncoding:NSUTF8StringEncoding];
  1114. }
  1115. @end
  1116. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1117. #pragma mark -
  1118. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1119. static NSString * const kDDXAttrArchivedName = @"lumberjack.log.archived";
  1120. @interface DDLogFileInfo () {
  1121. __strong NSString *_filePath;
  1122. __strong NSString *_fileName;
  1123. __strong NSDictionary *_fileAttributes;
  1124. __strong NSDate *_creationDate;
  1125. __strong NSDate *_modificationDate;
  1126. unsigned long long _fileSize;
  1127. }
  1128. #if TARGET_IPHONE_SIMULATOR
  1129. // Old implementation of extended attributes on the simulator.
  1130. - (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName;
  1131. - (void)_removeExtensionAttributeWithName:(NSString *)attrName;
  1132. #endif
  1133. @end
  1134. @implementation DDLogFileInfo
  1135. @synthesize filePath;
  1136. @dynamic fileName;
  1137. @dynamic fileAttributes;
  1138. @dynamic creationDate;
  1139. @dynamic modificationDate;
  1140. @dynamic fileSize;
  1141. @dynamic age;
  1142. @dynamic isArchived;
  1143. #pragma mark Lifecycle
  1144. + (instancetype)logFileWithPath:(NSString *)aFilePath {
  1145. if (!aFilePath) return nil;
  1146. return [[self alloc] initWithFilePath:aFilePath];
  1147. }
  1148. - (instancetype)initWithFilePath:(NSString *)aFilePath {
  1149. NSParameterAssert(aFilePath);
  1150. if ((self = [super init])) {
  1151. filePath = [aFilePath copy];
  1152. }
  1153. return self;
  1154. }
  1155. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1156. #pragma mark Standard Info
  1157. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1158. - (NSDictionary *)fileAttributes {
  1159. if (_fileAttributes == nil && filePath != nil) {
  1160. NSError *error = nil;
  1161. _fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
  1162. if (error) {
  1163. NSLogError(@"DDLogFileInfo: Failed to read file attributes: %@", error);
  1164. }
  1165. }
  1166. return _fileAttributes ?: @{};
  1167. }
  1168. - (NSString *)fileName {
  1169. if (_fileName == nil) {
  1170. _fileName = [filePath lastPathComponent];
  1171. }
  1172. return _fileName;
  1173. }
  1174. - (NSDate *)modificationDate {
  1175. if (_modificationDate == nil) {
  1176. _modificationDate = self.fileAttributes[NSFileModificationDate];
  1177. }
  1178. return _modificationDate;
  1179. }
  1180. - (NSDate *)creationDate {
  1181. if (_creationDate == nil) {
  1182. _creationDate = self.fileAttributes[NSFileCreationDate];
  1183. }
  1184. return _creationDate;
  1185. }
  1186. - (unsigned long long)fileSize {
  1187. if (_fileSize == 0) {
  1188. _fileSize = [self.fileAttributes[NSFileSize] unsignedLongLongValue];
  1189. }
  1190. return _fileSize;
  1191. }
  1192. - (NSTimeInterval)age {
  1193. return -[[self creationDate] timeIntervalSinceNow];
  1194. }
  1195. - (NSString *)description {
  1196. return [@{ @"filePath": self.filePath ? : @"",
  1197. @"fileName": self.fileName ? : @"",
  1198. @"fileAttributes": self.fileAttributes ? : @"",
  1199. @"creationDate": self.creationDate ? : @"",
  1200. @"modificationDate": self.modificationDate ? : @"",
  1201. @"fileSize": @(self.fileSize),
  1202. @"age": @(self.age),
  1203. @"isArchived": @(self.isArchived) } description];
  1204. }
  1205. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1206. #pragma mark Archiving
  1207. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1208. - (BOOL)isArchived {
  1209. return [self hasExtendedAttributeWithName:kDDXAttrArchivedName];
  1210. }
  1211. - (void)setIsArchived:(BOOL)flag {
  1212. if (flag) {
  1213. [self addExtendedAttributeWithName:kDDXAttrArchivedName];
  1214. } else {
  1215. [self removeExtendedAttributeWithName:kDDXAttrArchivedName];
  1216. }
  1217. }
  1218. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1219. #pragma mark Changes
  1220. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1221. - (void)reset {
  1222. _fileName = nil;
  1223. _fileAttributes = nil;
  1224. _creationDate = nil;
  1225. _modificationDate = nil;
  1226. }
  1227. - (void)renameFile:(NSString *)newFileName {
  1228. // This method is only used on the iPhone simulator, where normal extended attributes are broken.
  1229. // See full explanation in the header file.
  1230. if (![newFileName isEqualToString:[self fileName]]) {
  1231. NSFileManager* fileManager = [NSFileManager defaultManager];
  1232. NSString *fileDir = [filePath stringByDeletingLastPathComponent];
  1233. NSString *newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
  1234. // We only want to assert when we're not using the simulator, as we're "archiving" a log file with this method in the sim
  1235. // (in which case the file might not exist anymore and neither does it parent folder).
  1236. #if defined(DEBUG) && (!defined(TARGET_IPHONE_SIMULATOR) || !TARGET_IPHONE_SIMULATOR)
  1237. BOOL directory = NO;
  1238. [fileManager fileExistsAtPath:fileDir isDirectory:&directory];
  1239. NSAssert(directory, @"Containing directory must exist.");
  1240. #endif
  1241. NSError *error = nil;
  1242. BOOL success = [fileManager removeItemAtPath:newFilePath error:&error];
  1243. if (!success && error.code != NSFileNoSuchFileError) {
  1244. NSLogError(@"DDLogFileInfo: Error deleting archive (%@): %@", self.fileName, error);
  1245. }
  1246. success = [fileManager moveItemAtPath:filePath toPath:newFilePath error:&error];
  1247. // When a log file is deleted, moved or renamed on the simulator, we attempt to rename it as a
  1248. // result of "archiving" it, but since the file doesn't exist anymore, needless error logs are printed
  1249. // We therefore ignore this error, and assert that the directory we are copying into exists (which
  1250. // is the only other case where this error code can come up).
  1251. #if TARGET_IPHONE_SIMULATOR
  1252. if (!success && error.code != NSFileNoSuchFileError)
  1253. #else
  1254. if (!success)
  1255. #endif
  1256. {
  1257. NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
  1258. }
  1259. filePath = newFilePath;
  1260. [self reset];
  1261. }
  1262. }
  1263. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1264. #pragma mark Attribute Management
  1265. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1266. #if TARGET_IPHONE_SIMULATOR
  1267. // Old implementation of extended attributes on the simulator.
  1268. // Extended attributes were not working properly on the simulator
  1269. // due to misuse of setxattr() function.
  1270. // Now that this is fixed in the new implementation, we want to keep
  1271. // backward compatibility with previous simulator installations.
  1272. static NSString * const kDDExtensionSeparator = @".";
  1273. static NSString *_xattrToExtensionName(NSString *attrName) {
  1274. static NSDictionary<NSString *, NSString *>* _xattrToExtensionNameMap;
  1275. static dispatch_once_t _token;
  1276. dispatch_once(&_token, ^{
  1277. _xattrToExtensionNameMap = @{ kDDXAttrArchivedName: @"archived" };
  1278. });
  1279. return [_xattrToExtensionNameMap objectForKey:attrName];
  1280. }
  1281. - (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName {
  1282. // This method is only used on the iPhone simulator for backward compatibility reason.
  1283. // Split the file name into components. File name may have various format, but generally
  1284. // structure is same:
  1285. //
  1286. // <name part>.<extension part> and <name part>.archived.<extension part>
  1287. // or
  1288. // <name part> and <name part>.archived
  1289. //
  1290. // So we want to search for the attrName in the components (ignoring the first array index).
  1291. NSArray *components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator];
  1292. // Watch out for file names without an extension
  1293. for (NSUInteger i = 1; i < components.count; i++) {
  1294. NSString *attr = components[i];
  1295. if ([attrName isEqualToString:attr]) {
  1296. return YES;
  1297. }
  1298. }
  1299. return NO;
  1300. }
  1301. - (void)_removeExtensionAttributeWithName:(NSString *)attrName {
  1302. // This method is only used on the iPhone simulator for backward compatibility reason.
  1303. if ([attrName length] == 0) {
  1304. return;
  1305. }
  1306. // Example:
  1307. // attrName = "archived"
  1308. //
  1309. // "mylog.archived.txt" -> "mylog.txt"
  1310. // "mylog.archived" -> "mylog"
  1311. NSArray *components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator];
  1312. NSUInteger count = [components count];
  1313. NSUInteger estimatedNewLength = [[self fileName] length];
  1314. NSMutableString *newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
  1315. if (count > 0) {
  1316. [newFileName appendString:components.firstObject];
  1317. }
  1318. BOOL found = NO;
  1319. NSUInteger i;
  1320. for (i = 1; i < count; i++) {
  1321. NSString *attr = components[i];
  1322. if ([attrName isEqualToString:attr]) {
  1323. found = YES;
  1324. } else {
  1325. [newFileName appendString:kDDExtensionSeparator];
  1326. [newFileName appendString:attr];
  1327. }
  1328. }
  1329. if (found) {
  1330. [self renameFile:newFileName];
  1331. }
  1332. }
  1333. #endif /* if TARGET_IPHONE_SIMULATOR */
  1334. - (BOOL)hasExtendedAttributeWithName:(NSString *)attrName {
  1335. const char *path = [filePath fileSystemRepresentation];
  1336. const char *name = [attrName UTF8String];
  1337. BOOL hasExtendedAttribute = NO;
  1338. char buffer[1];
  1339. ssize_t result = getxattr(path, name, buffer, 1, 0, 0);
  1340. // Fast path
  1341. if (result > 0 && buffer[0] == '\1') {
  1342. hasExtendedAttribute = YES;
  1343. }
  1344. // Maintain backward compatibility, but fix it for future checks
  1345. else if (result >= 0) {
  1346. hasExtendedAttribute = YES;
  1347. [self addExtendedAttributeWithName:attrName];
  1348. }
  1349. #if TARGET_IPHONE_SIMULATOR
  1350. else if ([self _hasExtensionAttributeWithName:_xattrToExtensionName(attrName)]) {
  1351. hasExtendedAttribute = YES;
  1352. [self addExtendedAttributeWithName:attrName];
  1353. }
  1354. #endif
  1355. return hasExtendedAttribute;
  1356. }
  1357. - (void)addExtendedAttributeWithName:(NSString *)attrName {
  1358. const char *path = [filePath fileSystemRepresentation];
  1359. const char *name = [attrName UTF8String];
  1360. int result = setxattr(path, name, "\1", 1, 0, 0);
  1361. if (result < 0) {
  1362. if (errno != ENOENT) {
  1363. NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %s",
  1364. attrName,
  1365. filePath,
  1366. strerror(errno));
  1367. } else {
  1368. NSLogDebug(@"DDLogFileInfo: File does not exist in setxattr(%@, %@): error = %s",
  1369. attrName,
  1370. filePath,
  1371. strerror(errno));
  1372. }
  1373. }
  1374. #if TARGET_IPHONE_SIMULATOR
  1375. else {
  1376. [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)];
  1377. }
  1378. #endif
  1379. }
  1380. - (void)removeExtendedAttributeWithName:(NSString *)attrName {
  1381. const char *path = [filePath fileSystemRepresentation];
  1382. const char *name = [attrName UTF8String];
  1383. int result = removexattr(path, name, 0);
  1384. if (result < 0 && errno != ENOATTR) {
  1385. NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %s",
  1386. attrName,
  1387. self.fileName,
  1388. strerror(errno));
  1389. }
  1390. #if TARGET_IPHONE_SIMULATOR
  1391. [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)];
  1392. #endif
  1393. }
  1394. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1395. #pragma mark Comparisons
  1396. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  1397. - (BOOL)isEqual:(id)object {
  1398. if ([object isKindOfClass:[self class]]) {
  1399. DDLogFileInfo *another = (DDLogFileInfo *)object;
  1400. return [filePath isEqualToString:[another filePath]];
  1401. }
  1402. return NO;
  1403. }
  1404. - (NSUInteger)hash {
  1405. return [filePath hash];
  1406. }
  1407. - (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another {
  1408. __auto_type us = [self creationDate];
  1409. __auto_type them = [another creationDate];
  1410. return [them compare:us];
  1411. }
  1412. - (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another {
  1413. __auto_type us = [self modificationDate];
  1414. __auto_type them = [another modificationDate];
  1415. return [them compare:us];
  1416. }
  1417. @end
  1418. #if TARGET_OS_IPHONE
  1419. /**
  1420. * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
  1421. *
  1422. * But in case if app is able to launch from background we need to have an ability to open log file any time we
  1423. * want (even if device is locked). Thats why that attribute have to be changed to
  1424. * NSFileProtectionCompleteUntilFirstUserAuthentication.
  1425. */
  1426. BOOL doesAppRunInBackground() {
  1427. BOOL answer = NO;
  1428. NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
  1429. for (NSString *mode in backgroundModes) {
  1430. if (mode.length > 0) {
  1431. answer = YES;
  1432. break;
  1433. }
  1434. }
  1435. return answer;
  1436. }
  1437. #endif