Files
featherVoice/QXLive/Manager/QXLogger.m

887 lines
30 KiB
Mathematica
Raw Normal View History

2025-12-22 10:16:05 +08:00
//
// QXLogger.m
// QXLive
//
// Created by on 2025/12/18.
//
#import "QXLogger.h"
#import <sys/utsname.h>
#import <mach/mach.h>
#include <CommonCrypto/CommonCrypto.h>
//
static dispatch_queue_t _logQueue = nil;
static QXLogConfig *_config = nil;
static NSMutableArray<NSString *> *_logBuffer = nil;
static NSMutableArray<void (^)(QXLogLevel, NSString *, NSString *)> *_customHandlers = nil;
static BOOL _isEnabled = YES;
static NSDateFormatter *_dateFormatter = nil;
static NSFileHandle *_currentFileHandle = nil;
static NSString *_currentLogFilePath = nil;
static void (^_uploadHandler)(NSArray<NSString *> *, void (^)(BOOL)) = nil;
@implementation QXLogConfig
+ (instancetype)sharedConfig {
static QXLogConfig *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[QXLogConfig alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_minLogLevel = QXLogLevelDebug;
_logTarget = QXLogTargetConsole | QXLogTargetFile;
_maxLogFileSize = 5 * 1024 * 1024; // 5MB
_maxLogFileCount = 10;
_logSaveDays = 7;
_enableNetworkUpload = NO;
_enableConsoleColor = YES;
_addDateToFile = YES;
_enableAsync = YES;
_encryptLogFile = NO;
}
return self;
}
@end
@implementation QXLogger
#pragma mark -
+ (void)initialize {
if (self == [QXLogger class]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_logQueue = dispatch_queue_create("com.qx.log.queue", DISPATCH_QUEUE_SERIAL);
_config = [QXLogConfig sharedConfig];
_logBuffer = [NSMutableArray array];
_customHandlers = [NSMutableArray array];
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
//
[self setupLogger];
});
}
}
+ (void)setupLogger {
//
[self createLogDirectory];
//
[self cleanExpiredLogs];
//
[self findOrCreateLogFile];
//
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
//
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
[self info:@"QXLogger 初始化完成"];
}
#pragma mark -
+ (void)findOrCreateLogFile {
NSString *logDir = [self logDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
//
NSError *error = nil;
NSArray *files = [fm contentsOfDirectoryAtPath:logDir error:&error];
if (error) {
[self error:@"读取日志目录失败: %@", error.localizedDescription];
[self createNewLogFile];
return;
}
//
NSArray *logFiles = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF BEGINSWITH 'qxlog_'"]];
if (logFiles.count == 0) {
//
[self info:@"没有找到日志文件,创建新文件"];
[self createNewLogFile];
return;
}
//
NSArray *sortedFiles = [logFiles sortedArrayUsingSelector:@selector(compare:)];
NSString *latestFileName = sortedFiles.lastObject;
NSString *latestFilePath = [logDir stringByAppendingPathComponent:latestFileName];
//
NSDictionary *attrs = [fm attributesOfItemAtPath:latestFilePath error:nil];
unsigned long long fileSize = attrs ? [attrs fileSize] : 0;
CGFloat fileSizeMB = fileSize / 1024.0 / 1024.0;
CGFloat maxSizeMB = _config.maxLogFileSize / 1024.0 / 1024.0;
if (fileSize < _config.maxLogFileSize) {
// 使
[self openExistingLogFile:latestFilePath];
[self info:@"继续使用现有日志文件: %@ (%.2fMB/%.2fMB)", latestFileName, fileSizeMB, maxSizeMB];
} else {
//
[self createNewLogFile];
[self info:@"日志文件已满,创建新文件"];
}
}
+ (void)openExistingLogFile:(NSString *)filePath {
@synchronized (self) {
//
if (_currentFileHandle) {
@try {
[_currentFileHandle synchronizeFile];
[_currentFileHandle closeFile];
} @catch (NSException *exception) {
[self error:@"关闭文件句柄失败: %@", exception];
}
_currentFileHandle = nil;
}
//
NSError *error = nil;
_currentFileHandle = [NSFileHandle fileHandleForWritingToURL:[NSURL fileURLWithPath:filePath] error:&error];
if (error) {
[self error:@"打开现有日志文件失败: %@", error.localizedDescription];
[self createNewLogFile];
return;
}
//
@try {
[_currentFileHandle seekToEndOfFile];
} @catch (NSException *exception) {
[self error:@"移动到文件末尾失败: %@", exception];
[self createNewLogFile];
return;
}
_currentLogFilePath = filePath;
//
[self writeRestartMarker];
}
}
+ (void)createNewLogFile {
@synchronized (self) {
//
if (_currentFileHandle) {
@try {
[_currentFileHandle synchronizeFile];
[_currentFileHandle closeFile];
} @catch (NSException *exception) {
[self error:@"关闭文件句柄失败: %@", exception];
}
_currentFileHandle = nil;
}
//
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyyMMdd"];
NSString *dateStr = [formatter stringFromDate:[NSDate date]];
//
NSInteger nextIndex = [self getNextFileIndexForDate:dateStr];
NSString *fileName = [NSString stringWithFormat:@"qxlog_%@_%03ld.log", dateStr, (long)nextIndex];
NSString *filePath = [[self logDirectory] stringByAppendingPathComponent:fileName];
//
NSFileManager *fm = [NSFileManager defaultManager];
[fm createFileAtPath:filePath contents:nil attributes:nil];
//
NSError *error = nil;
_currentFileHandle = [NSFileHandle fileHandleForWritingToURL:[NSURL fileURLWithPath:filePath] error:&error];
if (error) {
[self error:@"打开日志文件失败: %@", error.localizedDescription];
return;
}
_currentLogFilePath = filePath;
//
[self writeFileHeader];
//
[self checkLogFileCount];
[self info:@"创建新日志文件: %@", fileName];
}
}
+ (NSInteger)getNextFileIndexForDate:(NSString *)dateStr {
NSString *logDir = [self logDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *files = [fm contentsOfDirectoryAtPath:logDir error:&error];
if (error) return 1;
NSString *prefix = [NSString stringWithFormat:@"qxlog_%@_", dateStr];
NSArray *todayFiles = [files filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:@"SELF BEGINSWITH %@", prefix]];
if (todayFiles.count == 0) return 1;
//
NSInteger maxIndex = 0;
for (NSString *fileName in todayFiles) {
// qxlog_20240101_001.log -> 001
NSString *baseName = [fileName stringByDeletingPathExtension];
NSArray *components = [baseName componentsSeparatedByString:@"_"];
if (components.count == 3) {
NSString *indexStr = components[2];
NSInteger index = [indexStr integerValue];
if (index > maxIndex) {
maxIndex = index;
}
}
}
return maxIndex + 1;
}
#pragma mark -
+ (void)writeRestartMarker {
if (!_currentFileHandle) return;
NSMutableString *marker = [NSMutableString string];
[marker appendString:@"\n\n"];
[marker appendString:@"═══════════════════════════════════════════════════\n"];
[marker appendFormat:@"🚀 应用重新启动 - %@\n", [_dateFormatter stringFromDate:[NSDate date]]];
[marker appendString:@"═══════════════════════════════════════════════════\n\n"];
NSData *markerData = [marker dataUsingEncoding:NSUTF8StringEncoding];
@try {
[_currentFileHandle writeData:markerData];
[_currentFileHandle synchronizeFile];
} @catch (NSException *exception) {
[self error:@"写入重启标记失败: %@", exception];
}
}
+ (void)writeFileHeader {
if (!_currentFileHandle) return;
NSMutableString *header = [NSMutableString string];
[header appendString:@"═══════════════════════════════════════════════════\n"];
[header appendString:@"📱 QXLogger 日志文件\n"];
[header appendString:@"═══════════════════════════════════════════════════\n"];
[header appendFormat:@"创建时间: %@\n", [_dateFormatter stringFromDate:[NSDate date]]];
[header appendFormat:@"设备型号: %@\n", [self deviceModel]];
[header appendFormat:@"系统版本: %@ %@\n", [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion];
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] ?:
[[NSBundle mainBundle] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleNameKey];
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"] ?: @"";
NSString *build = [[NSBundle mainBundle] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey] ?: @"";
[header appendFormat:@"应用信息: %@ %@ (%@)\n", appName ?: @"", version, build];
[header appendFormat:@"文件限制: %.2fMB/文件, 最多%lu个文件\n",
_config.maxLogFileSize / 1024.0 / 1024.0,
(unsigned long)_config.maxLogFileCount];
[header appendString:@"═══════════════════════════════════════════════════\n\n"];
NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
@try {
[_currentFileHandle writeData:headerData];
[_currentFileHandle synchronizeFile];
} @catch (NSException *exception) {
[self error:@"写入文件头失败: %@", exception];
}
}
#pragma mark -
+ (void)outputToFile:(NSString *)log {
@autoreleasepool {
//
[self checkLogFileSize];
//
if (!_currentFileHandle) {
[self findOrCreateLogFile];
if (!_currentFileHandle) return;
}
//
NSString *logWithNewline = [log stringByAppendingString:@"\n"];
NSData *logData = [logWithNewline dataUsingEncoding:NSUTF8StringEncoding];
@try {
[_currentFileHandle seekToEndOfFile];
[_currentFileHandle writeData:logData];
[_currentFileHandle synchronizeFile];
} @catch (NSException *exception) {
[self error:@"写入日志文件失败: %@", exception];
//
[self createNewLogFile];
}
}
}
+ (void)checkLogFileSize {
if (!_currentLogFilePath) return;
NSFileManager *fm = [NSFileManager defaultManager];
NSDictionary *attrs = [fm attributesOfItemAtPath:_currentLogFilePath error:nil];
if (attrs) {
unsigned long long fileSize = [attrs fileSize];
if (fileSize >= _config.maxLogFileSize) {
[self info:@"日志文件达到限制 (%.2fMB),创建新文件", fileSize / 1024.0 / 1024.0];
[self createNewLogFile];
}
}
}
#pragma mark -
+ (void)logWithLevel:(QXLogLevel)level tag:(nullable NSString *)tag format:(NSString *)format args:(va_list)args {
if (!_isEnabled || level < _config.minLogLevel) return;
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
if (_config.enableAsync) {
dispatch_async(_logQueue, ^{
[self processLogWithLevel:level tag:tag message:message];
});
} else {
dispatch_sync(_logQueue, ^{
[self processLogWithLevel:level tag:tag message:message];
});
}
}
+ (void)processLogWithLevel:(QXLogLevel)level tag:(nullable NSString *)tag message:(NSString *)message {
//
NSString *formattedLog = [self formatLogWithLevel:level tag:tag message:message];
//
if (_config.logTarget & QXLogTargetConsole) {
[self outputToConsole:formattedLog level:level];
}
if (_config.logTarget & QXLogTargetFile) {
[self outputToFile:formattedLog];
}
//
for (void (^handler)(QXLogLevel, NSString *, NSString *) in _customHandlers) {
handler(level, tag ?: @"", message);
}
}
+ (NSString *)formatLogWithLevel:(QXLogLevel)level tag:(nullable NSString *)tag message:(NSString *)message {
NSString *timestamp = [_dateFormatter stringFromDate:[NSDate date]];
NSString *levelStr = [self stringFromLogLevel:level];
NSString *threadInfo = [NSThread isMainThread] ? @"Main" : @"Background";
NSMutableString *log = [NSMutableString string];
[log appendFormat:@"%@ [%@]", timestamp, threadInfo];
if (_config.logPrefix.length > 0) {
[log appendFormat:@" [%@]", _config.logPrefix];
}
if (tag.length > 0) {
[log appendFormat:@" [%@]", tag];
}
[log appendFormat:@" [%@] %@", levelStr, message];
return [log copy];
}
+ (NSString *)stringFromLogLevel:(QXLogLevel)level {
switch (level) {
case QXLogLevelVerbose: return @"VERBOSE";
case QXLogLevelDebug: return @"DEBUG";
case QXLogLevelInfo: return @"INFO";
case QXLogLevelWarning: return @"WARNING";
case QXLogLevelError: return @"ERROR";
case QXLogLevelFatal: return @"FATAL";
default: return @"UNKNOWN";
}
}
+ (void)outputToConsole:(NSString *)log level:(QXLogLevel)level {
if (_config.enableConsoleColor) {
printf("%s\n", [self coloredLog:log level:level].UTF8String);
} else {
printf("%s\n", log.UTF8String);
}
}
+ (NSString *)coloredLog:(NSString *)log level:(QXLogLevel)level {
NSString *colorCode;
switch (level) {
case QXLogLevelVerbose: colorCode = @"\033[0;37m"; break; //
case QXLogLevelDebug: colorCode = @"\033[0;36m"; break; //
case QXLogLevelInfo: colorCode = @"\033[0;32m"; break; // 绿
case QXLogLevelWarning: colorCode = @"\033[0;33m"; break; //
case QXLogLevelError: colorCode = @"\033[0;31m"; break; //
case QXLogLevelFatal: colorCode = @"\033[0;35m"; break; //
default: colorCode = @"\033[0m";
}
return [NSString stringWithFormat:@"%@%@\033[0m", colorCode, log];
}
#pragma mark -
+ (void)verbose:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelVerbose tag:nil format:format args:args];
va_end(args);
}
+ (void)verboseWithTag:(NSString *)tag format:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelVerbose tag:tag format:format args:args];
va_end(args);
}
+ (void)debug:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelDebug tag:nil format:format args:args];
va_end(args);
}
+ (void)debugWithTag:(NSString *)tag format:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelDebug tag:tag format:format args:args];
va_end(args);
}
+ (void)info:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelInfo tag:nil format:format args:args];
va_end(args);
}
+ (void)infoWithTag:(NSString *)tag format:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelInfo tag:tag format:format args:args];
va_end(args);
}
+ (void)warning:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelWarning tag:nil format:format args:args];
va_end(args);
}
+ (void)warningWithTag:(NSString *)tag format:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelWarning tag:tag format:format args:args];
va_end(args);
}
+ (void)error:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelError tag:nil format:format args:args];
va_end(args);
}
+ (void)errorWithTag:(NSString *)tag format:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelError tag:tag format:format args:args];
va_end(args);
}
+ (void)fatal:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelFatal tag:nil format:format args:args];
va_end(args);
}
+ (void)fatalWithTag:(NSString *)tag format:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithLevel:QXLogLevelFatal tag:tag format:format args:args];
va_end(args);
}
#pragma mark -
+ (void)network:(NSString *)method url:(NSString *)url params:(id)params response:(id)response error:(NSError *)error {
NSMutableString *log = [NSMutableString string];
[log appendFormat:@"🌐 %@ %@\n", method, url];
if (params) {
[log appendFormat:@"Params: %@\n", [self jsonStringFromObject:params]];
}
if (response) {
[log appendFormat:@"Response: %@\n", [self jsonStringFromObject:response]];
}
if (error) {
[log appendFormat:@"Error: %@ (Code: %ld)", error.localizedDescription, (long)error.code];
}
[self debugWithTag:@"Network" format:@"%@", log];
}
+ (void)performance:(NSString *)tag startTime:(CFAbsoluteTime)startTime {
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
CFAbsoluteTime duration = (endTime - startTime) * 1000;
NSString *level = duration > 100 ? @"⚠️" : @"✅";
[self debugWithTag:@"Performance" format:@"%@ %@: %.2fms", level, tag, duration];
}
+ (void)userAction:(NSString *)action params:(NSDictionary *)params {
NSString *paramsStr = params ? [self jsonStringFromObject:params] : @"{}";
[self infoWithTag:@"UserAction" format:@"👤 %@ %@", action, paramsStr];
}
+ (void)crash:(NSException *)exception {
NSString *log = [NSString stringWithFormat:
@"💥 Crash: %@\n"
@"Reason: %@\n"
@"Stack Trace:\n%@",
exception.name,
exception.reason,
exception.callStackSymbols];
[self fatalWithTag:@"Crash" format:@"%@", log];
//
dispatch_sync(_logQueue, ^{
[self outputToFile:log];
});
}
+ (void)memoryWarning {
struct task_basic_info info;
mach_msg_type_number_t size = sizeof(info);
kern_return_t kerr = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &size);
if (kerr == KERN_SUCCESS) {
CGFloat usedMemory = info.resident_size / 1024.0 / 1024.0;
[self warningWithTag:@"Memory" format:@"⚠️ Memory Warning: %.2f MB used", usedMemory];
}
}
#pragma mark -
+ (NSString *)logDirectory {
NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
return [docDir stringByAppendingPathComponent:@"QXLogs"];
}
+ (void)createLogDirectory {
NSString *logDir = [self logDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:logDir]) {
NSError *error = nil;
[fm createDirectoryAtPath:logDir withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
[self error:@"创建日志目录失败: %@", error.localizedDescription];
}
}
}
+ (NSArray<NSString *> *)getAllLogFilePaths {
NSString *logDir = [self logDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *files = [fm contentsOfDirectoryAtPath:logDir error:&error];
if (error) return @[];
//
NSArray *logFiles = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF BEGINSWITH 'qxlog_'"]];
logFiles = [logFiles sortedArrayUsingSelector:@selector(compare:)];
//
NSMutableArray *fullPaths = [NSMutableArray array];
for (NSString *file in logFiles) {
[fullPaths addObject:[logDir stringByAppendingPathComponent:file]];
}
return [fullPaths copy];
}
+ (NSString *)getLatestLogFilePath {
NSArray *logFiles = [self getAllLogFilePaths];
return logFiles.lastObject;
}
+ (NSString *)getLogContentFromFile:(NSString *)filePath {
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
return nil;
}
NSError *error = nil;
NSString *content = [NSString stringWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
[self error:@"读取日志文件失败: %@", error.localizedDescription];
return nil;
}
return content;
}
+ (void)checkLogFileCount {
NSString *logDir = [self logDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *files = [fm contentsOfDirectoryAtPath:logDir error:&error];
if (!error) {
NSArray *logFiles = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF BEGINSWITH 'qxlog_'"]];
logFiles = [logFiles sortedArrayUsingSelector:@selector(compare:)];
if (logFiles.count > _config.maxLogFileCount) {
NSUInteger filesToDelete = logFiles.count - _config.maxLogFileCount;
for (NSUInteger i = 0; i < filesToDelete; i++) {
NSString *filePath = [logDir stringByAppendingPathComponent:logFiles[i]];
[fm removeItemAtPath:filePath error:nil];
[self info:@"删除旧日志文件: %@", logFiles[i]];
}
}
}
}
+ (void)cleanExpiredLogs {
if (_config.logSaveDays <= 0) return;
NSString *logDir = [self logDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *files = [fm contentsOfDirectoryAtPath:logDir error:&error];
if (error) return;
NSDate *now = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
for (NSString *file in files) {
if ([file hasPrefix:@"qxlog_"]) {
NSString *filePath = [logDir stringByAppendingPathComponent:file];
NSDictionary *attrs = [fm attributesOfItemAtPath:filePath error:nil];
if (attrs) {
NSDate *creationDate = [attrs fileCreationDate];
if (creationDate) {
NSDateComponents *components = [calendar components:NSCalendarUnitDay
fromDate:creationDate
toDate:now
options:0];
if (components.day > _config.logSaveDays) {
[fm removeItemAtPath:filePath error:nil];
[self info:@"删除过期日志文件: %@ (创建于: %@)", file, creationDate];
}
}
}
}
}
}
+ (void)cleanAllLogFiles {
NSString *logDir = [self logDirectory];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *files = [fm contentsOfDirectoryAtPath:logDir error:&error];
if (!error) {
for (NSString *file in files) {
if ([file hasPrefix:@"qxlog_"]) {
NSString *filePath = [logDir stringByAppendingPathComponent:file];
[fm removeItemAtPath:filePath error:nil];
}
}
}
[self createNewLogFile];
[self info:@"已清除所有日志文件"];
}
#pragma mark -
+ (unsigned long long)getCurrentLogFileSize {
if (!_currentLogFilePath) return 0;
NSFileManager *fm = [NSFileManager defaultManager];
NSDictionary *attrs = [fm attributesOfItemAtPath:_currentLogFilePath error:nil];
return attrs ? [attrs fileSize] : 0;
}
+ (CGFloat)getCurrentLogFileSizeMB {
return [self getCurrentLogFileSize] / 1024.0 / 1024.0;
}
+ (CGFloat)getTotalLogSizeMB {
NSArray *logFiles = [self getAllLogFilePaths];
NSFileManager *fm = [NSFileManager defaultManager];
CGFloat totalSize = 0;
for (NSString *filePath in logFiles) {
NSDictionary *attrs = [fm attributesOfItemAtPath:filePath error:nil];
if (attrs) {
totalSize += [attrs fileSize] / 1024.0 / 1024.0;
}
}
return totalSize;
}
+ (NSDictionary *)getLogFileInfo:(NSString *)filePath {
NSFileManager *fm = [NSFileManager defaultManager];
NSDictionary *attrs = [fm attributesOfItemAtPath:filePath error:nil];
if (!attrs) return @{};
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
CGFloat fileSizeMB = [attrs fileSize] / 1024.0 / 1024.0;
NSDate *creationDate = [attrs fileCreationDate];
NSDate *modificationDate = [attrs fileModificationDate];
return @{
@"fileName": [filePath lastPathComponent],
@"fileSize": @([attrs fileSize]),
@"fileSizeMB": @(fileSizeMB),
@"creationDate": creationDate ? [formatter stringFromDate:creationDate] : @"",
@"modificationDate": modificationDate ? [formatter stringFromDate:modificationDate] : @"",
@"filePath": filePath
};
}
+ (NSArray<NSDictionary *> *)getAllLogFileInfos {
NSArray *logFiles = [self getAllLogFilePaths];
NSMutableArray *infos = [NSMutableArray array];
for (NSString *filePath in logFiles) {
[infos addObject:[self getLogFileInfo:filePath]];
}
return [infos copy];
}
+ (void)checkFileSizeManually {
[self checkLogFileSize];
}
#pragma mark -
+ (void)uploadLogsWithCompletion:(void (^)(BOOL, NSError * _Nullable))completion {
//
if (completion) completion(NO, nil);
}
+ (void)setUploadHandler:(void (^)(NSArray<NSString *> *, void (^)(BOOL)))handler {
_uploadHandler = [handler copy];
}
#pragma mark -
+ (QXLogConfig *)config {
return _config;
}
+ (void)updateConfig:(void (^)(QXLogConfig *))block {
if (block) {
block(_config);
}
}
+ (void)enable:(BOOL)enabled {
_isEnabled = enabled;
}
+ (BOOL)isEnabled {
return _isEnabled;
}
+ (void)addCustomLogHandler:(void (^)(QXLogLevel, NSString *, NSString *))handler {
if (handler) {
[_customHandlers addObject:[handler copy]];
}
}
#pragma mark -
+ (NSString *)deviceModel {
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
}
+ (NSString *)jsonStringFromObject:(id)object {
if (!object) return @"null";
if ([object isKindOfClass:[NSString class]]) {
return object;
}
if ([NSJSONSerialization isValidJSONObject:object]) {
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
options:NSJSONWritingPrettyPrinted
error:&error];
if (!error) {
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
}
return [object description];
}
#pragma mark -
static void uncaughtExceptionHandler(NSException *exception) {
[QXLogger crash:exception];
//
usleep(200000);
//
if (NSGetUncaughtExceptionHandler()) {
NSGetUncaughtExceptionHandler()(exception);
}
}
+ (void)handleMemoryWarning {
[self memoryWarning];
}
@end