Bez popisu

AWSDDFileLogger.m 50KB

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