|
|
// Software License Agreement (BSD License)
//
// Copyright (c) 2010-2016, Deusty, LLC
// All rights reserved.
//
// Redistribution and use of this software in source and binary forms,
// with or without modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Neither the name of Deusty nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific
// prior written permission of Deusty, LLC.
// Disable legacy macros
#ifndef DD_LEGACY_MACROS
#define DD_LEGACY_MACROS 0
#endif
#import "OSSDDLog.h"
@class OSSDDLogFileInfo;
/**
* This class provides a logger to write log statements to a file. **/
// Default configuration and safety/sanity values.
//
// maximumFileSize -> kDDDefaultLogMaxFileSize
// rollingFrequency -> kDDDefaultLogRollingFrequency
// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles
// logFilesDiskQuota -> kDDDefaultLogFilesDiskQuota
//
// You should carefully consider the proper configuration values for your application.
extern unsigned long long const osskDDDefaultLogMaxFileSize; extern NSTimeInterval const osskDDDefaultLogRollingFrequency; extern NSUInteger const osskDDDefaultLogMaxNumLogFiles; extern unsigned long long const osskDDDefaultLogFilesDiskQuota;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The LogFileManager protocol is designed to allow you to control all aspects of your log files. * * The primary purpose of this is to allow you to do something with the log files after they have been rolled. * Perhaps you want to compress them to save disk space. * Perhaps you want to upload them to an FTP server. * Perhaps you want to run some analytics on the file. * * A default LogFileManager is, of course, provided. * The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property. * * This protocol provides various methods to fetch the list of log files. * * There are two variants: sorted and unsorted. * If sorting is not necessary, the unsorted variant is obviously faster. * The sorted variant will return an array sorted by when the log files were created, * with the most recently created log file at index 0, and the oldest log file at the end of the array. * * You can fetch only the log file paths (full path including name), log file names (name only), * or an array of `DDLogFileInfo` objects. * The `DDLogFileInfo` class is documented below, and provides a handy wrapper that * gives you easy access to various file attributes such as the creation date or the file size. */ @protocol OSSDDLogFileManager <NSObject> @required
// Public properties
/**
* The maximum number of archived log files to keep on disk. * For example, if this property is set to 3, * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk. * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted. * * You may optionally disable this option by setting it to zero. **/ @property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
/**
* The maximum space that logs can take. On rolling logfile all old logfiles that exceed logFilesDiskQuota will * be deleted. * * You may optionally disable this option by setting it to zero. **/ @property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota;
// Public methods
/**
* Returns the logs directory (path) */ @property (nonatomic, readonly, copy) NSString *logsDirectory;
/**
* Returns an array of `NSString` objects, * each of which is the filePath to an existing log file on disk. **/ @property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFilePaths;
/**
* Returns an array of `NSString` objects, * each of which is the fileName of an existing log file on disk. **/ @property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFileNames;
/**
* Returns an array of `DDLogFileInfo` objects, * each representing an existing log file on disk, * and containing important information about the log file such as it's modification date and size. **/ @property (nonatomic, readonly, strong) NSArray<OSSDDLogFileInfo *> *unsortedLogFileInfos;
/**
* Just like the `unsortedLogFilePaths` method, but sorts the array. * The items in the array are sorted by creation date. * The first item in the array will be the most recently created log file. **/ @property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFilePaths;
/**
* Just like the `unsortedLogFileNames` method, but sorts the array. * The items in the array are sorted by creation date. * The first item in the array will be the most recently created log file. **/ @property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFileNames;
/**
* Just like the `unsortedLogFileInfos` method, but sorts the array. * The items in the array are sorted by creation date. * The first item in the array will be the most recently created log file. **/ @property (nonatomic, readonly, strong) NSArray<OSSDDLogFileInfo *> *sortedLogFileInfos;
// Private methods (only to be used by DDFileLogger)
/**
* Generates a new unique log file path, and creates the corresponding log file. **/ - (NSString *)createNewLogFile;
@optional
// Notifications from DDFileLogger
/**
* Called when a log file was archieved */ - (void)didArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didArchiveLogFile(atPath:));
/**
* Called when the roll action was executed and the log was archieved */ - (void)didRollAndArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didRollAndArchiveLogFile(atPath:));
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Default log file manager. * * All log files are placed inside the logsDirectory. * If a specific logsDirectory isn't specified, the default directory is used. * On Mac, this is in `~/Library/Logs/<Application Name>`. * On iPhone, this is in `~/Library/Caches/Logs`. * * Log files are named `"<bundle identifier> <date> <time>.log"` * Example: `com.organization.myapp 2013-12-03 17-14.log` * * Archived log files are automatically deleted according to the `maximumNumberOfLogFiles` property. **/ @interface OSSDDLogFileManagerDefault : NSObject <OSSDDLogFileManager>
/**
* Default initializer */ - (instancetype)init;
/**
* Designated initialized, requires the logs directory */ - (instancetype)initWithLogsDirectory:(NSString *)logsDirectory NS_DESIGNATED_INITIALIZER;
#if TARGET_OS_IPHONE
/*
* Calling this constructor you can override the default "automagically" chosen NSFileProtection level. * Useful if you are writing a command line utility / CydiaSubstrate addon for iOS that has no NSBundle * or like SpringBoard no BackgroundModes key in the NSBundle: * iPhone:~ root# cycript -p SpringBoard * cy# [NSBundle mainBundle] * #"NSBundle </System/Library/CoreServices/SpringBoard.app> (loaded)" * cy# [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"]; * null * cy# **/ - (instancetype)initWithLogsDirectory:(NSString *)logsDirectory defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel; #endif
/*
* Methods to override. * * Log files are named `"<bundle identifier> <date> <time>.log"` * Example: `com.organization.myapp 2013-12-03 17-14.log` * * If you wish to change default filename, you can override following two methods. * - `newLogFileName` method would be called on new logfile creation. * - `isLogFile:` method would be called to filter logfiles from all other files in logsDirectory. * You have to parse given filename and return YES if it is logFile. * * **NOTE** * `newLogFileName` returns filename. If appropriate file already exists, number would be added * to filename before extension. You have to handle this case in isLogFile: method. * * Example: * - newLogFileName returns `"com.organization.myapp 2013-12-03.log"`, * file `"com.organization.myapp 2013-12-03.log"` would be created. * - after some time `"com.organization.myapp 2013-12-03.log"` is archived * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`, * file `"com.organization.myapp 2013-12-03 2.log"` would be created. * - after some time `"com.organization.myapp 2013-12-03 1.log"` is archived * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`, * file `"com.organization.myapp 2013-12-03 3.log"` would be created. **/
/**
* Generates log file name with default format `"<bundle identifier> <date> <time>.log"` * Example: `MobileSafari 2013-12-03 17-14.log` * * You can change it by overriding `newLogFileName` and `isLogFile:` methods. **/ @property (readonly, copy) NSString *newLogFileName;
/**
* Default log file name is `"<bundle identifier> <date> <time>.log"`. * Example: `MobileSafari 2013-12-03 17-14.log` * * You can change it by overriding `newLogFileName` and `isLogFile:` methods. **/ - (BOOL)isLogFile:(NSString *)fileName NS_SWIFT_NAME(isLogFile(withName:));
/* Inherited from DDLogFileManager protocol:
@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles; @property (readwrite, assign, atomic) NSUInteger logFilesDiskQuota;
- (NSString *)logsDirectory;
- (NSArray *)unsortedLogFilePaths; - (NSArray *)unsortedLogFileNames; - (NSArray *)unsortedLogFileInfos;
- (NSArray *)sortedLogFilePaths; - (NSArray *)sortedLogFileNames; - (NSArray *)sortedLogFileInfos;
*/
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Most users will want file log messages to be prepended with the date and time. * Rather than forcing the majority of users to write their own formatter, * we will supply a logical default formatter. * Users can easily replace this formatter with their own by invoking the `setLogFormatter:` method. * It can also be removed by calling `setLogFormatter:`, and passing a nil parameter. * * In addition to the convenience of having a logical default formatter, * it will also provide a template that makes it easy for developers to copy and change. **/ @interface OSSDDLogFileFormatterDefault : NSObject <OSSDDLogFormatter>
/**
* Default initializer */ - (instancetype)init;
/**
* Designated initializer, requires a date formatter */ - (instancetype)initWithDateFormatter:(NSDateFormatter *)dateFormatter NS_DESIGNATED_INITIALIZER;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The standard implementation for a file logger */ @interface OSSDDFileLogger : OSSDDAbstractLogger <OSSDDLogger> { OSSDDLogFileInfo *_currentLogFileInfo; }
/**
* Default initializer */ - (instancetype)init;
/**
* Designated initializer, requires a `DDLogFileManager` instance */ - (instancetype)initWithLogFileManager:(id <OSSDDLogFileManager>)logFileManager NS_DESIGNATED_INITIALIZER;
/**
* Called when the logger is about to write message. Call super before your implementation. */ - (void)willLogMessage NS_REQUIRES_SUPER;
/**
* Called when the logger wrote message. Call super after your implementation. */ - (void)didLogMessage NS_REQUIRES_SUPER;
/**
* Called when the logger checks archive or not current log file. * Override this method to exdend standart behavior. By default returns NO. */ - (BOOL)shouldArchiveRecentLogFileInfo:(OSSDDLogFileInfo *)recentLogFileInfo;
/**
* Log File Rolling: * * `maximumFileSize`: * The approximate maximum size (in bytes) to allow log files to grow. * If a log file is larger than this value after a log statement is appended, * then the log file is rolled. * * `rollingFrequency` * How often to roll the log file. * The frequency is given as an `NSTimeInterval`, which is a double that specifies the interval in seconds. * Once the log file gets to be this old, it is rolled. * * `doNotReuseLogFiles` * When set, will always create a new log file at application launch. * * Both the `maximumFileSize` and the `rollingFrequency` are used to manage rolling. * Whichever occurs first will cause the log file to be rolled. * * For example: * The `rollingFrequency` is 24 hours, * but the log file surpasses the `maximumFileSize` after only 20 hours. * The log file will be rolled at that 20 hour mark. * A new log file will be created, and the 24 hour timer will be restarted. * * You may optionally disable rolling due to filesize by setting `maximumFileSize` to zero. * If you do so, rolling is based solely on `rollingFrequency`. * * You may optionally disable rolling due to time by setting `rollingFrequency` to zero (or any non-positive number). * If you do so, rolling is based solely on `maximumFileSize`. * * If you disable both `maximumFileSize` and `rollingFrequency`, then the log file won't ever be rolled. * This is strongly discouraged. **/ @property (readwrite, assign) unsigned long long maximumFileSize;
/**
* See description for `maximumFileSize` */ @property (readwrite, assign) NSTimeInterval rollingFrequency;
/**
* See description for `maximumFileSize` */ @property (readwrite, assign, atomic) BOOL doNotReuseLogFiles;
/**
* The DDLogFileManager instance can be used to retrieve the list of log files, * and configure the maximum number of archived log files to keep. * * @see DDLogFileManager.maximumNumberOfLogFiles **/ @property (strong, nonatomic, readonly) id <OSSDDLogFileManager> logFileManager;
/**
* When using a custom formatter you can set the `logMessage` method not to append * `\n` character after each output. This allows for some greater flexibility with * custom formatters. Default value is YES. **/ @property (nonatomic, readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;
/**
* You can optionally force the current log file to be rolled with this method. * CompletionBlock will be called on main queue. */ - (void)rollLogFileWithCompletionBlock:(void (^)(void))completionBlock NS_SWIFT_NAME(rollLogFile(withCompletion:));
/**
* Method is deprecated. * @deprecated Use `rollLogFileWithCompletionBlock:` method instead. */ - (void)rollLogFile __attribute((deprecated));
// Inherited from DDAbstractLogger
// - (id <DDLogFormatter>)logFormatter;
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
/**
* Returns the log file that should be used. * If there is an existing log file that is suitable, * within the constraints of `maximumFileSize` and `rollingFrequency`, then it is returned. * * Otherwise a new file is created and returned. **/ @property (nonatomic, readonly, strong) OSSDDLogFileInfo *currentLogFileInfo;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* `DDLogFileInfo` is a simple class that provides access to various file attributes. * It provides good performance as it only fetches the information if requested, * and it caches the information to prevent duplicate fetches. * * It was designed to provide quick snapshots of the current state of log files, * and to help sort log files in an array. * * This class does not monitor the files, or update it's cached attribute values if the file changes on disk. * This is not what the class was designed for. * * If you absolutely must get updated values, * you can invoke the reset method which will clear the cache. **/ @interface OSSDDLogFileInfo : NSObject
@property (strong, nonatomic, readonly) NSString *filePath; @property (strong, nonatomic, readonly) NSString *fileName;
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (strong, nonatomic, readonly) NSDictionary<NSFileAttributeKey, id> *fileAttributes; #else
@property (strong, nonatomic, readonly) NSDictionary<NSString *, id> *fileAttributes; #endif
@property (strong, nonatomic, readonly) NSDate *creationDate; @property (strong, nonatomic, readonly) NSDate *modificationDate;
@property (nonatomic, readonly) unsigned long long fileSize;
@property (nonatomic, readonly) NSTimeInterval age;
@property (nonatomic, readwrite) BOOL isArchived;
+ (instancetype)logFileWithPath:(NSString *)filePath NS_SWIFT_UNAVAILABLE("Use init(filePath:)");
- (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithFilePath:(NSString *)filePath NS_DESIGNATED_INITIALIZER;
- (void)reset; - (void)renameFile:(NSString *)newFileName NS_SWIFT_NAME(renameFile(to:));
#if TARGET_IPHONE_SIMULATOR
// So here's the situation.
// Extended attributes are perfect for what we're trying to do here (marking files as archived).
// This is exactly what extended attributes were designed for.
//
// But Apple screws us over on the simulator.
// Everytime you build-and-go, they copy the application into a new folder on the hard drive,
// and as part of the process they strip extended attributes from our log files.
// Normally, a copy of a file preserves extended attributes.
// So obviously Apple has gone to great lengths to piss us off.
//
// Thus we use a slightly different tactic for marking log files as archived in the simulator.
// That way it "just works" and there's no confusion when testing.
//
// The difference in method names is indicative of the difference in functionality.
// On the simulator we add an attribute by appending a filename extension.
//
// For example:
// "mylog.txt" -> "mylog.archived.txt"
// "mylog" -> "mylog.archived"
- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;
- (void)addExtensionAttributeWithName:(NSString *)attrName; - (void)removeExtensionAttributeWithName:(NSString *)attrName;
#else /* if TARGET_IPHONE_SIMULATOR */
// Normal use of extended attributes used everywhere else,
// such as on Macs and on iPhone devices.
- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;
- (void)addExtendedAttributeWithName:(NSString *)attrName; - (void)removeExtendedAttributeWithName:(NSString *)attrName;
#endif /* if TARGET_IPHONE_SIMULATOR */
@end
|