DDFileLogger.h 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. // Software License Agreement (BSD License)
  2. //
  3. // Copyright (c) 2010-2019, 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. // Disable legacy macros
  16. #ifndef DD_LEGACY_MACROS
  17. #define DD_LEGACY_MACROS 0
  18. #endif
  19. #import <CocoaLumberjack/DDLog.h>
  20. NS_ASSUME_NONNULL_BEGIN
  21. @class DDLogFileInfo;
  22. /**
  23. * This class provides a logger to write log statements to a file.
  24. **/
  25. // Default configuration and safety/sanity values.
  26. //
  27. // maximumFileSize -> kDDDefaultLogMaxFileSize
  28. // rollingFrequency -> kDDDefaultLogRollingFrequency
  29. // maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles
  30. // logFilesDiskQuota -> kDDDefaultLogFilesDiskQuota
  31. //
  32. // You should carefully consider the proper configuration values for your application.
  33. extern unsigned long long const kDDDefaultLogMaxFileSize;
  34. extern NSTimeInterval const kDDDefaultLogRollingFrequency;
  35. extern NSUInteger const kDDDefaultLogMaxNumLogFiles;
  36. extern unsigned long long const kDDDefaultLogFilesDiskQuota;
  37. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  38. #pragma mark -
  39. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  40. /**
  41. * The LogFileManager protocol is designed to allow you to control all aspects of your log files.
  42. *
  43. * The primary purpose of this is to allow you to do something with the log files after they have been rolled.
  44. * Perhaps you want to compress them to save disk space.
  45. * Perhaps you want to upload them to an FTP server.
  46. * Perhaps you want to run some analytics on the file.
  47. *
  48. * A default LogFileManager is, of course, provided.
  49. * The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property.
  50. *
  51. * This protocol provides various methods to fetch the list of log files.
  52. *
  53. * There are two variants: sorted and unsorted.
  54. * If sorting is not necessary, the unsorted variant is obviously faster.
  55. * The sorted variant will return an array sorted by when the log files were created,
  56. * with the most recently created log file at index 0, and the oldest log file at the end of the array.
  57. *
  58. * You can fetch only the log file paths (full path including name), log file names (name only),
  59. * or an array of `DDLogFileInfo` objects.
  60. * The `DDLogFileInfo` class is documented below, and provides a handy wrapper that
  61. * gives you easy access to various file attributes such as the creation date or the file size.
  62. */
  63. @protocol DDLogFileManager <NSObject>
  64. @required
  65. // Public properties
  66. /**
  67. * The maximum number of archived log files to keep on disk.
  68. * For example, if this property is set to 3,
  69. * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk.
  70. * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted.
  71. *
  72. * You may optionally disable this option by setting it to zero.
  73. **/
  74. @property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
  75. /**
  76. * The maximum space that logs can take. On rolling logfile all old log files that exceed logFilesDiskQuota will
  77. * be deleted.
  78. *
  79. * You may optionally disable this option by setting it to zero.
  80. **/
  81. @property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota;
  82. // Public methods
  83. /**
  84. * Returns the logs directory (path)
  85. */
  86. @property (nonatomic, readonly, copy) NSString *logsDirectory;
  87. /**
  88. * Returns an array of `NSString` objects,
  89. * each of which is the filePath to an existing log file on disk.
  90. **/
  91. @property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFilePaths;
  92. /**
  93. * Returns an array of `NSString` objects,
  94. * each of which is the fileName of an existing log file on disk.
  95. **/
  96. @property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFileNames;
  97. /**
  98. * Returns an array of `DDLogFileInfo` objects,
  99. * each representing an existing log file on disk,
  100. * and containing important information about the log file such as it's modification date and size.
  101. **/
  102. @property (nonatomic, readonly, strong) NSArray<DDLogFileInfo *> *unsortedLogFileInfos;
  103. /**
  104. * Just like the `unsortedLogFilePaths` method, but sorts the array.
  105. * The items in the array are sorted by creation date.
  106. * The first item in the array will be the most recently created log file.
  107. **/
  108. @property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFilePaths;
  109. /**
  110. * Just like the `unsortedLogFileNames` method, but sorts the array.
  111. * The items in the array are sorted by creation date.
  112. * The first item in the array will be the most recently created log file.
  113. **/
  114. @property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFileNames;
  115. /**
  116. * Just like the `unsortedLogFileInfos` method, but sorts the array.
  117. * The items in the array are sorted by creation date.
  118. * The first item in the array will be the most recently created log file.
  119. **/
  120. @property (nonatomic, readonly, strong) NSArray<DDLogFileInfo *> *sortedLogFileInfos;
  121. // Private methods (only to be used by DDFileLogger)
  122. /**
  123. * Generates a new unique log file path, and creates the corresponding log file.
  124. * This method is executed directly on the file logger's internal queue.
  125. * The file has to exist by the time the method returns.
  126. **/
  127. - (NSString *)createNewLogFile;
  128. @optional
  129. // Notifications from DDFileLogger
  130. /**
  131. * Called when a log file was archived. Executed on global queue with default priority.
  132. */
  133. - (void)didArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didArchiveLogFile(atPath:));
  134. /**
  135. * Called when the roll action was executed and the log was archived.
  136. * Executed on global queue with default priority.
  137. */
  138. - (void)didRollAndArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didRollAndArchiveLogFile(atPath:));
  139. @end
  140. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  141. #pragma mark -
  142. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  143. /**
  144. * Default log file manager.
  145. *
  146. * All log files are placed inside the logsDirectory.
  147. * If a specific logsDirectory isn't specified, the default directory is used.
  148. * On Mac, this is in `~/Library/Logs/<Application Name>`.
  149. * On iPhone, this is in `~/Library/Caches/Logs`.
  150. *
  151. * Log files are named `"<bundle identifier> <date> <time>.log"`
  152. * Example: `com.organization.myapp 2013-12-03 17-14.log`
  153. *
  154. * Archived log files are automatically deleted according to the `maximumNumberOfLogFiles` property.
  155. **/
  156. @interface DDLogFileManagerDefault : NSObject <DDLogFileManager>
  157. /**
  158. * Default initializer
  159. */
  160. - (instancetype)init;
  161. /**
  162. * If logDirectory is not specified, then a folder called "Logs" is created in the app's cache directory.
  163. * While running on the simulator, the "Logs" folder is located in the library temporary directory.
  164. */
  165. - (instancetype)initWithLogsDirectory:(NSString * __nullable)logsDirectory NS_DESIGNATED_INITIALIZER;
  166. #if TARGET_OS_IPHONE
  167. /*
  168. * Calling this constructor you can override the default "automagically" chosen NSFileProtection level.
  169. * Useful if you are writing a command line utility / CydiaSubstrate addon for iOS that has no NSBundle
  170. * or like SpringBoard no BackgroundModes key in the NSBundle:
  171. * iPhone:~ root# cycript -p SpringBoard
  172. * cy# [NSBundle mainBundle]
  173. * #"NSBundle </System/Library/CoreServices/SpringBoard.app> (loaded)"
  174. * cy# [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
  175. * null
  176. * cy#
  177. **/
  178. - (instancetype)initWithLogsDirectory:(NSString * __nullable)logsDirectory
  179. defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel;
  180. #endif
  181. /*
  182. * Methods to override.
  183. *
  184. * Log files are named `"<bundle identifier> <date> <time>.log"`
  185. * Example: `com.organization.myapp 2013-12-03 17-14.log`
  186. *
  187. * If you wish to change default filename, you can override following two methods.
  188. * - `newLogFileName` method would be called on new logfile creation.
  189. * - `isLogFile:` method would be called to filter log files from all other files in logsDirectory.
  190. * You have to parse given filename and return YES if it is logFile.
  191. *
  192. * **NOTE**
  193. * `newLogFileName` returns filename. If appropriate file already exists, number would be added
  194. * to filename before extension. You have to handle this case in isLogFile: method.
  195. *
  196. * Example:
  197. * - newLogFileName returns `"com.organization.myapp 2013-12-03.log"`,
  198. * file `"com.organization.myapp 2013-12-03.log"` would be created.
  199. * - after some time `"com.organization.myapp 2013-12-03.log"` is archived
  200. * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
  201. * file `"com.organization.myapp 2013-12-03 2.log"` would be created.
  202. * - after some time `"com.organization.myapp 2013-12-03 1.log"` is archived
  203. * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
  204. * file `"com.organization.myapp 2013-12-03 3.log"` would be created.
  205. **/
  206. /**
  207. * Generates log file name with default format `"<bundle identifier> <date> <time>.log"`
  208. * Example: `MobileSafari 2013-12-03 17-14.log`
  209. *
  210. * You can change it by overriding `newLogFileName` and `isLogFile:` methods.
  211. **/
  212. @property (readonly, copy) NSString *newLogFileName;
  213. /**
  214. * Default log file name is `"<bundle identifier> <date> <time>.log"`.
  215. * Example: `MobileSafari 2013-12-03 17-14.log`
  216. *
  217. * You can change it by overriding `newLogFileName` and `isLogFile:` methods.
  218. **/
  219. - (BOOL)isLogFile:(NSString *)fileName NS_SWIFT_NAME(isLogFile(withName:));
  220. /**
  221. * New log files are created empty by default in `createNewLogFile:` method
  222. *
  223. * If you wish to specify a common file header to use in your log files,
  224. * you can set the initial log file contents by overriding `logFileHeader`
  225. **/
  226. @property (readonly, copy, nullable) NSString *logFileHeader;
  227. /* Inherited from DDLogFileManager protocol:
  228. @property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
  229. @property (readwrite, assign, atomic) NSUInteger logFilesDiskQuota;
  230. - (NSString *)logsDirectory;
  231. - (NSArray *)unsortedLogFilePaths;
  232. - (NSArray *)unsortedLogFileNames;
  233. - (NSArray *)unsortedLogFileInfos;
  234. - (NSArray *)sortedLogFilePaths;
  235. - (NSArray *)sortedLogFileNames;
  236. - (NSArray *)sortedLogFileInfos;
  237. */
  238. @end
  239. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  240. #pragma mark -
  241. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  242. /**
  243. * Most users will want file log messages to be prepended with the date and time.
  244. * Rather than forcing the majority of users to write their own formatter,
  245. * we will supply a logical default formatter.
  246. * Users can easily replace this formatter with their own by invoking the `setLogFormatter:` method.
  247. * It can also be removed by calling `setLogFormatter:`, and passing a nil parameter.
  248. *
  249. * In addition to the convenience of having a logical default formatter,
  250. * it will also provide a template that makes it easy for developers to copy and change.
  251. **/
  252. @interface DDLogFileFormatterDefault : NSObject <DDLogFormatter>
  253. /**
  254. * Default initializer
  255. */
  256. - (instancetype)init;
  257. /**
  258. * Designated initializer, requires a date formatter
  259. */
  260. - (instancetype)initWithDateFormatter:(NSDateFormatter * __nullable)dateFormatter NS_DESIGNATED_INITIALIZER;
  261. @end
  262. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  263. #pragma mark -
  264. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  265. /**
  266. * The standard implementation for a file logger
  267. */
  268. @interface DDFileLogger : DDAbstractLogger <DDLogger>
  269. /**
  270. * Default initializer.
  271. */
  272. - (instancetype)init;
  273. /**
  274. * Designated initializer, requires a `DDLogFileManager` instance.
  275. * A global queue w/ default priority is used to run callbacks.
  276. * If needed, specify queue using `initWithLogFileManager:completionQueue:`.
  277. */
  278. - (instancetype)initWithLogFileManager:(id <DDLogFileManager> __nullable)logFileManager;
  279. /**
  280. * Designated initializer, requires a `DDLogFileManager` instance.
  281. * The completionQueue is used to execute `didArchiveLogFile`, `didRollAndArchiveLogFile`,
  282. * and the callback in `rollLog`. If nil, a global queue w/ default priority is used.
  283. */
  284. - (instancetype)initWithLogFileManager:(id <DDLogFileManager> __nullable)logFileManager
  285. completionQueue:(dispatch_queue_t __nullable)dispatchQueue NS_DESIGNATED_INITIALIZER;
  286. /**
  287. * Called when the logger is about to write message. Call super before your implementation.
  288. */
  289. - (void)willLogMessage NS_REQUIRES_SUPER;
  290. /**
  291. * Called when the logger wrote message. Call super after your implementation.
  292. */
  293. - (void)didLogMessage NS_REQUIRES_SUPER;
  294. /**
  295. * Writes all in-memory log data to the permanent storage. Call super before your implementation.
  296. * Don't call this method directly, instead use the `[DDLog flushLog]` to ensure all log messages are included in flush.
  297. */
  298. - (void)flush NS_REQUIRES_SUPER;
  299. /**
  300. * Called when the logger checks archive or not current log file.
  301. * Override this method to extend standard behavior. By default returns NO.
  302. * This is executed directly on the logger's internal queue, so keep processing light!
  303. */
  304. - (BOOL)shouldArchiveRecentLogFileInfo:(DDLogFileInfo *)recentLogFileInfo;
  305. /**
  306. * Log File Rolling:
  307. *
  308. * `maximumFileSize`:
  309. * The approximate maximum size (in bytes) to allow log files to grow.
  310. * If a log file is larger than this value after a log statement is appended,
  311. * then the log file is rolled.
  312. *
  313. * `rollingFrequency`
  314. * How often to roll the log file.
  315. * The frequency is given as an `NSTimeInterval`, which is a double that specifies the interval in seconds.
  316. * Once the log file gets to be this old, it is rolled.
  317. *
  318. * `doNotReuseLogFiles`
  319. * When set, will always create a new log file at application launch.
  320. *
  321. * Both the `maximumFileSize` and the `rollingFrequency` are used to manage rolling.
  322. * Whichever occurs first will cause the log file to be rolled.
  323. *
  324. * For example:
  325. * The `rollingFrequency` is 24 hours,
  326. * but the log file surpasses the `maximumFileSize` after only 20 hours.
  327. * The log file will be rolled at that 20 hour mark.
  328. * A new log file will be created, and the 24 hour timer will be restarted.
  329. *
  330. * You may optionally disable rolling due to filesize by setting `maximumFileSize` to zero.
  331. * If you do so, rolling is based solely on `rollingFrequency`.
  332. *
  333. * You may optionally disable rolling due to time by setting `rollingFrequency` to zero (or any non-positive number).
  334. * If you do so, rolling is based solely on `maximumFileSize`.
  335. *
  336. * If you disable both `maximumFileSize` and `rollingFrequency`, then the log file won't ever be rolled.
  337. * This is strongly discouraged.
  338. **/
  339. @property (readwrite, assign) unsigned long long maximumFileSize;
  340. /**
  341. * See description for `maximumFileSize`
  342. */
  343. @property (readwrite, assign) NSTimeInterval rollingFrequency;
  344. /**
  345. * See description for `maximumFileSize`
  346. */
  347. @property (readwrite, assign, atomic) BOOL doNotReuseLogFiles;
  348. /**
  349. * The DDLogFileManager instance can be used to retrieve the list of log files,
  350. * and configure the maximum number of archived log files to keep.
  351. *
  352. * @see DDLogFileManager.maximumNumberOfLogFiles
  353. **/
  354. @property (strong, nonatomic, readonly) id <DDLogFileManager> logFileManager;
  355. /**
  356. * When using a custom formatter you can set the `logMessage` method not to append
  357. * `\n` character after each output. This allows for some greater flexibility with
  358. * custom formatters. Default value is YES.
  359. **/
  360. @property (nonatomic, readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;
  361. /**
  362. * You can optionally force the current log file to be rolled with this method.
  363. * CompletionBlock will be called on main queue.
  364. */
  365. - (void)rollLogFileWithCompletionBlock:(void (^ __nullable)(void))completionBlock
  366. NS_SWIFT_NAME(rollLogFile(withCompletion:));
  367. /**
  368. * Method is deprecated.
  369. * @deprecated Use `rollLogFileWithCompletionBlock:` method instead.
  370. */
  371. - (void)rollLogFile __attribute((deprecated));
  372. // Inherited from DDAbstractLogger
  373. // - (id <DDLogFormatter>)logFormatter;
  374. // - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
  375. /**
  376. * Returns the log file that should be used.
  377. * If there is an existing log file that is suitable,
  378. * within the constraints of `maximumFileSize` and `rollingFrequency`, then it is returned.
  379. *
  380. * Otherwise a new file is created and returned.
  381. **/
  382. @property (nonatomic, readonly, strong) DDLogFileInfo *currentLogFileInfo;
  383. @end
  384. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  385. #pragma mark -
  386. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  387. /**
  388. * `DDLogFileInfo` is a simple class that provides access to various file attributes.
  389. * It provides good performance as it only fetches the information if requested,
  390. * and it caches the information to prevent duplicate fetches.
  391. *
  392. * It was designed to provide quick snapshots of the current state of log files,
  393. * and to help sort log files in an array.
  394. *
  395. * This class does not monitor the files, or update it's cached attribute values if the file changes on disk.
  396. * This is not what the class was designed for.
  397. *
  398. * If you absolutely must get updated values,
  399. * you can invoke the reset method which will clear the cache.
  400. **/
  401. @interface DDLogFileInfo : NSObject
  402. @property (strong, nonatomic, readonly) NSString *filePath;
  403. @property (strong, nonatomic, readonly) NSString *fileName;
  404. #if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
  405. @property (strong, nonatomic, readonly) NSDictionary<NSFileAttributeKey, id> *fileAttributes;
  406. #else
  407. @property (strong, nonatomic, readonly) NSDictionary<NSString *, id> *fileAttributes;
  408. #endif
  409. @property (strong, nonatomic, readonly) NSDate *creationDate;
  410. @property (strong, nonatomic, readonly) NSDate *modificationDate;
  411. @property (nonatomic, readonly) unsigned long long fileSize;
  412. @property (nonatomic, readonly) NSTimeInterval age;
  413. @property (nonatomic, readwrite) BOOL isArchived;
  414. + (instancetype)logFileWithPath:(NSString *)filePath NS_SWIFT_UNAVAILABLE("Use init(filePath:)");
  415. - (instancetype)init NS_UNAVAILABLE;
  416. - (instancetype)initWithFilePath:(NSString *)filePath NS_DESIGNATED_INITIALIZER;
  417. - (void)reset;
  418. - (void)renameFile:(NSString *)newFileName NS_SWIFT_NAME(renameFile(to:));
  419. #if TARGET_IPHONE_SIMULATOR
  420. // So here's the situation.
  421. // Extended attributes are perfect for what we're trying to do here (marking files as archived).
  422. // This is exactly what extended attributes were designed for.
  423. //
  424. // But Apple screws us over on the simulator.
  425. // Everytime you build-and-go, they copy the application into a new folder on the hard drive,
  426. // and as part of the process they strip extended attributes from our log files.
  427. // Normally, a copy of a file preserves extended attributes.
  428. // So obviously Apple has gone to great lengths to piss us off.
  429. //
  430. // Thus we use a slightly different tactic for marking log files as archived in the simulator.
  431. // That way it "just works" and there's no confusion when testing.
  432. //
  433. // The difference in method names is indicative of the difference in functionality.
  434. // On the simulator we add an attribute by appending a filename extension.
  435. //
  436. // For example:
  437. // "mylog.txt" -> "mylog.archived.txt"
  438. // "mylog" -> "mylog.archived"
  439. - (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;
  440. - (void)addExtensionAttributeWithName:(NSString *)attrName;
  441. - (void)removeExtensionAttributeWithName:(NSString *)attrName;
  442. #else /* if TARGET_IPHONE_SIMULATOR */
  443. // Normal use of extended attributes used everywhere else,
  444. // such as on Macs and on iPhone devices.
  445. - (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;
  446. - (void)addExtendedAttributeWithName:(NSString *)attrName;
  447. - (void)removeExtendedAttributeWithName:(NSString *)attrName;
  448. #endif /* if TARGET_IPHONE_SIMULATOR */
  449. - (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
  450. - (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;
  451. @end
  452. NS_ASSUME_NONNULL_END