900 lines
33 KiB
Objective-C
900 lines
33 KiB
Objective-C
// Software License Agreement (BSD License)
|
|
//
|
|
// Copyright (c) 2010-2023, 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.
|
|
|
|
#if !__has_feature(objc_arc)
|
|
#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
|
#endif
|
|
|
|
#import <sys/xattr.h>
|
|
|
|
#import "DDFileLogger.h"
|
|
#import "DDTTYLogger.h"
|
|
#import <sys/uio.h>
|
|
// We probably shouldn't be using DDLog() statements within the DDLog implementation.
|
|
// But we still want to leave our log statements for any future debugging,
|
|
// and to allow other developers to trace the implementation (which is a great learning tool).
|
|
//
|
|
// So we use primitive logging macros around NSLog.
|
|
// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
|
|
|
|
|
|
|
|
unsigned long long const kDDDefaultLogMaxFileSize = 1024 * 1024 * 5; // 5 MB
|
|
NSTimeInterval const kDDDefaultLogRollingFrequency = 60 * 60 * 24 * 15; // 24 * 15 Hours
|
|
NSUInteger const kDDDefaultLogMaxNumLogFiles = 5; // 5 Files
|
|
unsigned long long const kDDDefaultLogFilesDiskQuota = 20 * 1024 * 1024; // 20 MB
|
|
|
|
NSTimeInterval const kDDRollingLeeway = 1.0; // 1s
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
@interface DDFileLogger () {
|
|
id <DDLogFileManager> _logFileManager;
|
|
|
|
DDLogFileInfo *_currentLogFileInfo;
|
|
NSFileHandle *_currentLogFileHandle;
|
|
|
|
dispatch_source_t _currentLogFileVnode;
|
|
|
|
NSTimeInterval _rollingFrequency;
|
|
dispatch_source_t _rollingTimer;
|
|
|
|
unsigned long long _maximumFileSize;
|
|
|
|
dispatch_queue_t _completionQueue;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wincomplete-implementation"
|
|
@implementation DDFileLogger
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
|
|
- (instancetype)init {
|
|
DDLogFileManagerDefault *defaultLogFileManager = [[DDLogFileManagerDefault alloc] init];
|
|
return [self initWithLogFileManager:defaultLogFileManager logfileFormatter:nil completionQueue:nil];
|
|
}
|
|
|
|
- (instancetype)initWithLogFileManager:(id<DDLogFileManager>)logFileManager logfileFormatter:(nullable DDLogFileFormatterDefault *)logfileFormatter{
|
|
return [self initWithLogFileManager:logFileManager logfileFormatter:logfileFormatter completionQueue:nil];
|
|
}
|
|
|
|
- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager logfileFormatter:(nullable DDLogFileFormatterDefault *)logfileFormatter completionQueue:(nullable dispatch_queue_t)dispatchQueue {
|
|
if ((self = [super initWithTag:aLogFileManager.directoryName])) {
|
|
_completionQueue = dispatchQueue ?: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
|
|
|
_maximumFileSize = kDDDefaultLogMaxFileSize;
|
|
_rollingFrequency = kDDDefaultLogRollingFrequency;
|
|
_automaticallyAppendNewlineForCustomFormatters = YES;
|
|
|
|
_logFileManager = aLogFileManager;
|
|
_logFormatter = logfileFormatter;
|
|
self.loggerTag = aLogFileManager.directoryName;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)lt_cleanup {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
|
|
if (_currentLogFileHandle != nil) {
|
|
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
|
|
__autoreleasing NSError *error = nil;
|
|
BOOL success = [_currentLogFileHandle synchronizeAndReturnError:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
|
|
}
|
|
success = [_currentLogFileHandle closeAndReturnError:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to close file: %@", error);
|
|
}
|
|
} else {
|
|
@try {
|
|
[_currentLogFileHandle synchronizeFile];
|
|
} @catch (NSException *exception) {
|
|
NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
|
|
}
|
|
[_currentLogFileHandle closeFile];
|
|
}
|
|
_currentLogFileHandle = nil;
|
|
}
|
|
|
|
if (_currentLogFileVnode) {
|
|
dispatch_source_cancel(_currentLogFileVnode);
|
|
_currentLogFileVnode = NULL;
|
|
}
|
|
|
|
if (_rollingTimer) {
|
|
dispatch_source_cancel(_rollingTimer);
|
|
_rollingTimer = NULL;
|
|
}
|
|
}
|
|
|
|
- (void)dealloc {
|
|
if (self.isOnInternalLoggerQueue) {
|
|
[self lt_cleanup];
|
|
} else {
|
|
dispatch_sync(self.loggerQueue, ^{
|
|
[self lt_cleanup];
|
|
});
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark Properties
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
- (unsigned long long)maximumFileSize {
|
|
__block unsigned long long result;
|
|
|
|
dispatch_block_t block = ^{
|
|
result = self->_maximumFileSize;
|
|
};
|
|
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
|
|
// Note: The internal implementation MUST access the maximumFileSize variable directly,
|
|
// This method is designed explicitly for external access.
|
|
//
|
|
// Using "self." syntax to go through this method will cause immediate deadlock.
|
|
// This is the intended result. Fix it by accessing the ivar directly.
|
|
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
|
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
|
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
|
|
dispatch_sync(globalLoggingQueue, ^{
|
|
dispatch_sync(self.loggerQueue, block);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize {
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool {
|
|
self->_maximumFileSize = newMaximumFileSize;
|
|
if (self->_currentLogFileHandle != nil || [self lt_currentLogFileHandle] != nil) {
|
|
[self lt_maybeRollLogFileDueToSize];
|
|
}
|
|
}
|
|
};
|
|
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
|
|
// Note: The internal implementation MUST access the maximumFileSize variable directly,
|
|
// This method is designed explicitly for external access.
|
|
//
|
|
// Using "self." syntax to go through this method will cause immediate deadlock.
|
|
// This is the intended result. Fix it by accessing the ivar directly.
|
|
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
|
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
|
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
|
|
dispatch_async(globalLoggingQueue, ^{
|
|
dispatch_async(self.loggerQueue, block);
|
|
});
|
|
}
|
|
|
|
- (NSTimeInterval)rollingFrequency {
|
|
__block NSTimeInterval result;
|
|
|
|
dispatch_block_t block = ^{
|
|
result = self->_rollingFrequency;
|
|
};
|
|
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
|
|
// Note: The internal implementation should access the rollingFrequency variable directly,
|
|
// This method is designed explicitly for external access.
|
|
//
|
|
// Using "self." syntax to go through this method will cause immediate deadlock.
|
|
// This is the intended result. Fix it by accessing the ivar directly.
|
|
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
|
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
|
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
|
|
dispatch_sync(globalLoggingQueue, ^{
|
|
dispatch_sync(self.loggerQueue, block);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency {
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool {
|
|
self->_rollingFrequency = newRollingFrequency;
|
|
if (self->_currentLogFileHandle != nil || [self lt_currentLogFileHandle] != nil) {
|
|
[self lt_maybeRollLogFileDueToAge];
|
|
}
|
|
}
|
|
};
|
|
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
|
|
// Note: The internal implementation should access the rollingFrequency variable directly,
|
|
// This method is designed explicitly for external access.
|
|
//
|
|
// Using "self." syntax to go through this method will cause immediate deadlock.
|
|
// This is the intended result. Fix it by accessing the ivar directly.
|
|
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
|
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
|
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
|
|
dispatch_async(globalLoggingQueue, ^{
|
|
dispatch_async(self.loggerQueue, block);
|
|
});
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark File Rolling
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
- (void)lt_scheduleTimerToRollLogFileDueToAge {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
|
|
if (_rollingTimer) {
|
|
dispatch_source_cancel(_rollingTimer);
|
|
_rollingTimer = NULL;
|
|
}
|
|
|
|
if (_currentLogFileInfo == nil || _rollingFrequency <= 0.0) {
|
|
return;
|
|
}
|
|
|
|
NSDate *logFileCreationDate = [_currentLogFileInfo creationDate];
|
|
NSTimeInterval frequency = MIN(_rollingFrequency, DBL_MAX - [logFileCreationDate timeIntervalSinceReferenceDate]);
|
|
NSDate *logFileRollingDate = [logFileCreationDate dateByAddingTimeInterval:frequency];
|
|
|
|
NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
|
|
NSLogVerbose(@"DDFileLogger: logFileCreationDate : %@", logFileCreationDate);
|
|
NSLogVerbose(@"DDFileLogger: actual rollingFrequency: %f", frequency);
|
|
NSLogVerbose(@"DDFileLogger: logFileRollingDate : %@", logFileRollingDate);
|
|
|
|
_rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _loggerQueue);
|
|
|
|
__weak __auto_type weakSelf = self;
|
|
dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool {
|
|
[weakSelf lt_maybeRollLogFileDueToAge];
|
|
} });
|
|
|
|
#if !OS_OBJECT_USE_OBJC
|
|
dispatch_source_t theRollingTimer = _rollingTimer;
|
|
dispatch_source_set_cancel_handler(_rollingTimer, ^{
|
|
dispatch_release(theRollingTimer);
|
|
});
|
|
#endif
|
|
|
|
static NSTimeInterval const kDDMaxTimerDelay = LLONG_MAX / NSEC_PER_SEC;
|
|
int64_t delay = (int64_t)(MIN([logFileRollingDate timeIntervalSinceNow], kDDMaxTimerDelay) * (NSTimeInterval) NSEC_PER_SEC);
|
|
__auto_type fireTime = dispatch_walltime(NULL, delay); // `NULL` uses `gettimeofday` internally
|
|
|
|
dispatch_source_set_timer(_rollingTimer, fireTime, DISPATCH_TIME_FOREVER, (uint64_t)kDDRollingLeeway * NSEC_PER_SEC);
|
|
dispatch_activate(_rollingTimer);
|
|
}
|
|
|
|
- (void)rollLogFile {
|
|
[self rollLogFileWithCompletionBlock:nil];
|
|
}
|
|
|
|
- (void)rollLogFileWithCompletionBlock:(nullable void (^)(void))completionBlock {
|
|
// This method is public.
|
|
// We need to execute the rolling on our logging thread/queue.
|
|
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool {
|
|
[self lt_rollLogFileNow];
|
|
|
|
if (completionBlock) {
|
|
dispatch_async(self->_completionQueue, ^{
|
|
completionBlock();
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
|
|
if ([self isOnInternalLoggerQueue]) {
|
|
block();
|
|
} else {
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
|
|
dispatch_async(globalLoggingQueue, ^{
|
|
dispatch_async(self.loggerQueue, block);
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)lt_rollLogFileNow {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
NSLogVerbose(@"DDFileLogger: rollLogFileNow");
|
|
|
|
if (_currentLogFileHandle == nil) {
|
|
return;
|
|
}
|
|
|
|
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
|
|
__autoreleasing NSError *error = nil;
|
|
BOOL success = [_currentLogFileHandle synchronizeAndReturnError:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
|
|
}
|
|
success = [_currentLogFileHandle closeAndReturnError:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to close file: %@", error);
|
|
}
|
|
} else {
|
|
@try {
|
|
[_currentLogFileHandle synchronizeFile];
|
|
} @catch (NSException *exception) {
|
|
NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
|
|
}
|
|
[_currentLogFileHandle closeFile];
|
|
}
|
|
_currentLogFileHandle = nil;
|
|
|
|
_currentLogFileInfo.isArchived = YES;
|
|
|
|
const BOOL logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)];
|
|
const BOOL logFileManagerRespondsToSelector = (logFileManagerRespondsToNewArchiveSelector
|
|
|| [_logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]);
|
|
NSString *archivedFilePath = (logFileManagerRespondsToSelector) ? [_currentLogFileInfo.filePath copy] : nil;
|
|
_currentLogFileInfo = nil;
|
|
|
|
if (logFileManagerRespondsToSelector) {
|
|
dispatch_block_t block;
|
|
if (logFileManagerRespondsToNewArchiveSelector) {
|
|
block = ^{
|
|
[self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:YES];
|
|
};
|
|
} else {
|
|
block = ^{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[self->_logFileManager didRollAndArchiveLogFile:archivedFilePath];
|
|
#pragma clang diagnostic pop
|
|
};
|
|
}
|
|
dispatch_async(_completionQueue, block);
|
|
}
|
|
|
|
if (_currentLogFileVnode) {
|
|
dispatch_source_cancel(_currentLogFileVnode);
|
|
_currentLogFileVnode = nil;
|
|
}
|
|
|
|
if (_rollingTimer) {
|
|
dispatch_source_cancel(_rollingTimer);
|
|
_rollingTimer = nil;
|
|
}
|
|
}
|
|
|
|
- (void)lt_maybeRollLogFileDueToAge {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
|
|
if (_rollingFrequency > 0.0 && (_currentLogFileInfo.age + kDDRollingLeeway) >= _rollingFrequency) {
|
|
NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
|
|
[self lt_rollLogFileNow];
|
|
} else {
|
|
[self lt_scheduleTimerToRollLogFileDueToAge];
|
|
}
|
|
}
|
|
|
|
- (void)lt_maybeRollLogFileDueToSize {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
|
|
// This method is called from logMessage.
|
|
// Keep it FAST.
|
|
|
|
// Note: Use direct access to maximumFileSize variable.
|
|
// We specifically wrote our own getter/setter method to allow us to do this (for performance reasons).
|
|
|
|
if (_currentLogFileHandle != nil && _maximumFileSize > 0) {
|
|
unsigned long long fileSize;
|
|
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
|
|
__autoreleasing NSError *error = nil;
|
|
BOOL success = [_currentLogFileHandle getOffset:&fileSize error:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to get offset: %@", error);
|
|
return;
|
|
}
|
|
} else {
|
|
fileSize = [_currentLogFileHandle offsetInFile];
|
|
}
|
|
|
|
if (fileSize >= _maximumFileSize) {
|
|
NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize);
|
|
|
|
[self lt_rollLogFileNow];
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark File Logging
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
- (BOOL)lt_shouldLogFileBeArchived:(DDLogFileInfo *)mostRecentLogFileInfo {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
|
|
if ([self shouldArchiveRecentLogFileInfo:mostRecentLogFileInfo]) {
|
|
return YES;
|
|
} else if (_maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= _maximumFileSize) {
|
|
return YES;
|
|
} else if (_rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= _rollingFrequency) {
|
|
return YES;
|
|
}
|
|
|
|
#if TARGET_OS_IPHONE
|
|
// When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
|
|
//
|
|
// But in case if app is able to launch from background we need to have an ability to open log file any time we
|
|
// want (even if device is locked). Thats why that attribute have to be changed to
|
|
// NSFileProtectionCompleteUntilFirstUserAuthentication.
|
|
//
|
|
// If previous log was created when app wasn't running in background, but now it is - we archive it and create
|
|
// a new one.
|
|
//
|
|
// If user has overwritten to NSFileProtectionNone there is no need to create a new one.
|
|
if (doesAppRunInBackground()) {
|
|
NSFileProtectionType key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey];
|
|
BOOL isUntilFirstAuth = [key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication];
|
|
BOOL isNone = [key isEqualToString:NSFileProtectionNone];
|
|
|
|
if (key != nil && !isUntilFirstAuth && !isNone) {
|
|
return YES;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return NO;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
**/
|
|
- (DDLogFileInfo *)currentLogFileInfo {
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
// Do not access this method on any Lumberjack queue, will deadlock.
|
|
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
|
|
|
__block DDLogFileInfo *info = nil;
|
|
dispatch_block_t block = ^{
|
|
info = [self lt_currentLogFileInfo];
|
|
};
|
|
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
|
|
dispatch_sync(globalLoggingQueue, ^{
|
|
dispatch_sync(self->_loggerQueue, block);
|
|
});
|
|
|
|
return info;
|
|
}
|
|
|
|
- (DDLogFileInfo *)lt_currentLogFileInfo {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
|
|
// Get the current log file info ivar (might be nil).
|
|
DDLogFileInfo *newCurrentLogFile = _currentLogFileInfo;
|
|
|
|
// Check if we're resuming and if so, get the first of the sorted log file infos.
|
|
BOOL isResuming = newCurrentLogFile == nil;
|
|
if (isResuming) {
|
|
NSArray *sortedLogFileInfos = [_logFileManager sortedLogFileInfos];
|
|
newCurrentLogFile = sortedLogFileInfos.firstObject;
|
|
}
|
|
|
|
// Check if the file we've found is still valid. Otherwise create a new one.
|
|
if (newCurrentLogFile != nil && [self lt_shouldUseLogFile:newCurrentLogFile isResuming:isResuming]) {
|
|
if (isResuming) {
|
|
NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", newCurrentLogFile.fileName);
|
|
}
|
|
_currentLogFileInfo = newCurrentLogFile;
|
|
} else {
|
|
NSString *currentLogFilePath;
|
|
if ([_logFileManager respondsToSelector:@selector(createNewLogFileWithError:)]) {
|
|
__autoreleasing NSError *error; // Don't initialize error to nil since it will be done in -createNewLogFileWithError:
|
|
currentLogFilePath = [_logFileManager createNewLogFileWithError:&error];
|
|
if (!currentLogFilePath) {
|
|
NSLogError(@"DDFileLogger: Failed to create new log file: %@", error);
|
|
}
|
|
} else {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
NSAssert([_logFileManager respondsToSelector:@selector(createNewLogFile)],
|
|
@"Invalid log file manager! Responds neither to `-createNewLogFileWithError:` nor `-createNewLogFile`!");
|
|
currentLogFilePath = [_logFileManager createNewLogFile];
|
|
#pragma clang diagnostic pop
|
|
if (!currentLogFilePath) {
|
|
NSLogError(@"DDFileLogger: Failed to create new log file");
|
|
}
|
|
}
|
|
// Use static factory method here, since it checks for nil (and is unavailable to Swift).
|
|
_currentLogFileInfo = [DDLogFileInfo logFileWithPath:currentLogFilePath];
|
|
}
|
|
|
|
return _currentLogFileInfo;
|
|
}
|
|
|
|
- (BOOL)lt_shouldUseLogFile:(nonnull DDLogFileInfo *)logFileInfo isResuming:(BOOL)isResuming {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
NSParameterAssert(logFileInfo);
|
|
|
|
// Check if the log file is archived. We must not use archived log files.
|
|
if (logFileInfo.isArchived) {
|
|
return NO;
|
|
}
|
|
|
|
// Don't follow symlink
|
|
if (logFileInfo.isSymlink) {
|
|
return NO;
|
|
}
|
|
|
|
// If we're resuming, we need to check if the log file is allowed for reuse or needs to be archived.
|
|
if (isResuming && (_doNotReuseLogFiles || [self lt_shouldLogFileBeArchived:logFileInfo])) {
|
|
logFileInfo.isArchived = YES;
|
|
|
|
const BOOL logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)];
|
|
if (logFileManagerRespondsToNewArchiveSelector || [_logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) {
|
|
NSString *archivedFilePath = [logFileInfo.filePath copy];
|
|
dispatch_block_t block;
|
|
if (logFileManagerRespondsToNewArchiveSelector) {
|
|
block = ^{
|
|
[self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:NO];
|
|
};
|
|
} else {
|
|
block = ^{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[self->_logFileManager didArchiveLogFile:archivedFilePath];
|
|
#pragma clang diagnostic pop
|
|
};
|
|
}
|
|
dispatch_async(_completionQueue, block);
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
// All checks have passed. It's valid.
|
|
return YES;
|
|
}
|
|
|
|
- (void)lt_monitorCurrentLogFileForExternalChanges {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
NSAssert(_currentLogFileHandle, @"Can not monitor without handle.");
|
|
|
|
_currentLogFileVnode = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
|
|
(uintptr_t)[_currentLogFileHandle fileDescriptor],
|
|
DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
|
|
_loggerQueue);
|
|
|
|
__weak __auto_type weakSelf = self;
|
|
dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool {
|
|
NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one");
|
|
[weakSelf lt_rollLogFileNow];
|
|
} });
|
|
|
|
#if !OS_OBJECT_USE_OBJC
|
|
dispatch_source_t vnode = _currentLogFileVnode;
|
|
dispatch_source_set_cancel_handler(_currentLogFileVnode, ^{
|
|
dispatch_release(vnode);
|
|
});
|
|
#endif
|
|
|
|
dispatch_activate(_currentLogFileVnode);
|
|
}
|
|
|
|
- (NSFileHandle *)lt_currentLogFileHandle {
|
|
NSAssert([self isOnInternalLoggerQueue], @"lt_ methods should be on logger queue.");
|
|
|
|
if (_currentLogFileHandle == nil) {
|
|
NSString *logFilePath = [[self lt_currentLogFileInfo] filePath];
|
|
_currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
|
|
if (_currentLogFileHandle != nil) {
|
|
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
|
|
__autoreleasing NSError *error = nil;
|
|
BOOL success = [_currentLogFileHandle seekToEndReturningOffset:nil error:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error);
|
|
}
|
|
} else {
|
|
[_currentLogFileHandle seekToEndOfFile];
|
|
}
|
|
|
|
[self lt_scheduleTimerToRollLogFileDueToAge];
|
|
[self lt_monitorCurrentLogFileForExternalChanges];
|
|
}
|
|
}
|
|
|
|
return _currentLogFileHandle;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark DDLogger Protocol
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
- (void)logMessage:(DDLogMessage *)logMessage {
|
|
// Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us.
|
|
NSData *data = [self lt_dataForMessage:logMessage];
|
|
|
|
if (data.length == 0) {
|
|
return;
|
|
}
|
|
|
|
[self lt_logData:data];
|
|
|
|
if(self.canEchoMessage == YES){
|
|
[[DDTTYLogger sharedInstance] logMessage:logMessage];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)willLogMessage:(DDLogFileInfo *)logFileInfo {}
|
|
|
|
- (void)didLogMessage:(DDLogFileInfo *)logFileInfo {
|
|
[self lt_maybeRollLogFileDueToSize];
|
|
}
|
|
|
|
- (BOOL)shouldArchiveRecentLogFileInfo:(__unused DDLogFileInfo *)recentLogFileInfo {
|
|
return NO;
|
|
}
|
|
|
|
- (void)willRemoveLogger {
|
|
[self lt_rollLogFileNow];
|
|
}
|
|
|
|
- (void)flush {
|
|
// This method is public.
|
|
// We need to execute the rolling on our logging thread/queue.
|
|
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool {
|
|
[self lt_flush];
|
|
}
|
|
};
|
|
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
|
|
if ([self isOnInternalLoggerQueue]) {
|
|
block();
|
|
} else {
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
|
|
dispatch_sync(globalLoggingQueue, ^{
|
|
dispatch_sync(self.loggerQueue, block);
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)lt_flush {
|
|
NSAssert([self isOnInternalLoggerQueue], @"flush should only be executed on internal queue.");
|
|
|
|
if (_currentLogFileHandle != nil) {
|
|
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
|
|
__autoreleasing NSError *error = nil;
|
|
BOOL success = [_currentLogFileHandle synchronizeAndReturnError:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
|
|
}
|
|
} else {
|
|
@try {
|
|
[_currentLogFileHandle synchronizeFile];
|
|
} @catch (NSException *exception) {
|
|
NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int exception_count = 0;
|
|
|
|
- (void)logData:(NSData *)data {
|
|
// This method is public.
|
|
// We need to execute the rolling on our logging thread/queue.
|
|
|
|
dispatch_block_t block = ^{
|
|
@autoreleasepool {
|
|
[self lt_logData:data];
|
|
}
|
|
};
|
|
|
|
// The design of this method is taken from the DDAbstractLogger implementation.
|
|
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
|
|
|
if ([self isOnInternalLoggerQueue]) {
|
|
block();
|
|
} else {
|
|
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
|
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
|
|
|
dispatch_sync(globalLoggingQueue, ^{
|
|
dispatch_sync(self.loggerQueue, block);
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)lt_deprecationCatchAll {}
|
|
|
|
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
|
|
if (aSelector == @selector(willLogMessage) || aSelector == @selector(didLogMessage)) {
|
|
// Ignore calls to deprecated methods.
|
|
return [self methodSignatureForSelector:@selector(lt_deprecationCatchAll)];
|
|
}
|
|
|
|
return [super methodSignatureForSelector:aSelector];
|
|
}
|
|
|
|
- (void)forwardInvocation:(NSInvocation *)anInvocation {
|
|
if (anInvocation.selector != @selector(lt_deprecationCatchAll)) {
|
|
[super forwardInvocation:anInvocation];
|
|
}
|
|
}
|
|
|
|
- (void)lt_logData:(NSData *)data {
|
|
static BOOL implementsDeprecatedWillLog = NO;
|
|
static BOOL implementsDeprecatedDidLog = NO;
|
|
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
implementsDeprecatedWillLog = [self respondsToSelector:@selector(willLogMessage)];
|
|
implementsDeprecatedDidLog = [self respondsToSelector:@selector(didLogMessage)];
|
|
});
|
|
|
|
NSAssert([self isOnInternalLoggerQueue], @"logMessage should only be executed on internal queue.");
|
|
|
|
if (data.length == 0) {
|
|
return;
|
|
}
|
|
|
|
@try {
|
|
// Make sure that _currentLogFileInfo is initialised before being used.
|
|
NSFileHandle *handle = [self lt_currentLogFileHandle];
|
|
|
|
if (implementsDeprecatedWillLog) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[self willLogMessage];
|
|
#pragma clang diagnostic pop
|
|
} else {
|
|
[self willLogMessage:_currentLogFileInfo];
|
|
}
|
|
|
|
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
|
|
__autoreleasing NSError *error = nil;
|
|
BOOL success = [handle seekToEndReturningOffset:nil error:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error);
|
|
}
|
|
success = [handle writeData:data error:&error];
|
|
if (!success) {
|
|
NSLogError(@"DDFileLogger: Failed to write data: %@", error);
|
|
}
|
|
} else {
|
|
[handle seekToEndOfFile];
|
|
[handle writeData:data];
|
|
}
|
|
|
|
if (implementsDeprecatedDidLog) {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[self didLogMessage];
|
|
#pragma clang diagnostic pop
|
|
} else {
|
|
[self didLogMessage:_currentLogFileInfo];
|
|
}
|
|
|
|
} @catch (NSException *exception) {
|
|
exception_count++;
|
|
|
|
if (exception_count <= 10) {
|
|
NSLogError(@"DDFileLogger.logMessage: %@", exception);
|
|
|
|
if (exception_count == 10) {
|
|
NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSData *)lt_dataForMessage:(DDLogMessage *)logMessage {
|
|
NSAssert([self isOnInternalLoggerQueue], @"logMessage should only be executed on internal queue.");
|
|
|
|
NSString *message = logMessage->_message;
|
|
BOOL isFormatted = NO;
|
|
|
|
if (_logFormatter != nil) {
|
|
message = [_logFormatter formatLogMessage:logMessage];
|
|
isFormatted = message != logMessage->_message;
|
|
}
|
|
|
|
if (message.length == 0) {
|
|
return nil;
|
|
}
|
|
|
|
BOOL shouldFormat = !isFormatted || _automaticallyAppendNewlineForCustomFormatters;
|
|
if (shouldFormat && ![message hasSuffix:@"\n"]) {
|
|
message = [message stringByAppendingString:@"\n"];
|
|
}
|
|
|
|
return [message dataUsingEncoding:NSUTF8StringEncoding];
|
|
}
|
|
|
|
|
|
- (DDLoggerName)loggerName {
|
|
if(self.loggerTag.length > 0){
|
|
return [NSString stringWithFormat:@"%@/%@",DDLoggerNameFile,self.loggerTag];
|
|
}
|
|
return DDLoggerNameFile;
|
|
}
|
|
|
|
BOOL doesAppRunInBackground(void) {
|
|
BOOL answer = NO;
|
|
|
|
NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
|
|
|
|
for (NSString *mode in backgroundModes) {
|
|
if (mode.length > 0) {
|
|
answer = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return answer;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|