From 3f9c1a759c9f0315be81465416a0565a7f8b6fbe Mon Sep 17 00:00:00 2001 From: lhyqy5 Date: Thu, 6 Nov 2025 15:26:20 +0800 Subject: [PATCH] log --- AppRunMan/Tweak.x | 8 ++ AppRunMan/server/XSLog.h | 22 ++++++ AppRunMan/server/XSLog.m | 162 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 AppRunMan/server/XSLog.h create mode 100644 AppRunMan/server/XSLog.m diff --git a/AppRunMan/Tweak.x b/AppRunMan/Tweak.x index 0a966da..04090af 100644 --- a/AppRunMan/Tweak.x +++ b/AppRunMan/Tweak.x @@ -12,6 +12,14 @@ #import "server/MySimpleServer.h" #import "server/FloatingWindow.h" +#import "server/XSLog.h" + +// 重定向 NSLog 到文件 +__attribute__((constructor)) static void SetupXSLog() { + XSLogSetupLogWithFilePath(@"/var/mobile/Documents/AppRunMain.log", 5 * 1024 * 1024); + XSLogRedirectNSLog(); +} + @interface UIWindow (FloatingWindow) @end diff --git a/AppRunMan/server/XSLog.h b/AppRunMan/server/XSLog.h new file mode 100644 index 0000000..172a83d --- /dev/null +++ b/AppRunMan/server/XSLog.h @@ -0,0 +1,22 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + + +/// @param logFileName 日志文件名 +/// @param maxFileSize 日志文件最大大小(字节),超过此大小将创建新文件 +void XSLogSetupLogWithFileName(NSString *logFileName, unsigned long long maxFileSize); + +/// @param logFilePath 日志文件路径 +/// @param maxFileSize 日志文件最大大小(字节),超过此大小将创建新文件 +void XSLogSetupLogWithFilePath(NSString *logFilePath, unsigned long long maxFileSize); + + +void XSLogMessage(NSString *format, ...) __attribute__((format(NSString, 1, 2))); + +void XSLogRedirectNSLog(void); + +// 定义一个宏,方便替换NSLog +#define XSLog(format, ...) XSLogMessage((format), ##__VA_ARGS__) + +NS_ASSUME_NONNULL_END diff --git a/AppRunMan/server/XSLog.m b/AppRunMan/server/XSLog.m new file mode 100644 index 0000000..87bb174 --- /dev/null +++ b/AppRunMan/server/XSLog.m @@ -0,0 +1,162 @@ +#import "XSLog.h" + +static NSString *kLogFilePath = @"app.log"; +static unsigned long long kMaxFileSize = 10 * 1024 * 1024; // 10 MB +static NSFileHandle *logFileHandle; +static dispatch_queue_t logQueue; +static int logCount = 0; // 用于控制滚动检查频率 +static const int kLogCheckFrequency = 100; // 每100条日志检查一次文件大小 + +// 内部函数声明 +static void setupLogFileHandle(NSString *logFilePath); +static void checkAndRollLogFile(NSString *logFilePath); +static NSString *getLogFilePath(NSString *logFileName); + + + +void XSLogSetupLogWithFilePath(NSString *logFilePath, + unsigned long long maxFileSize) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + kLogFilePath = logFilePath; + kMaxFileSize = maxFileSize; + logQueue = dispatch_queue_create("com.yourcompany.app.logQueue", + DISPATCH_QUEUE_SERIAL); + + // 初始化文件句柄 + setupLogFileHandle(logFilePath); + }); +} + +void XSLogSetupLogWithFileName(NSString *logFileName, + unsigned long long maxFileSize) { + kLogFilePath = getLogFilePath(logFileName); + XSLogSetupLogWithFilePath(kLogFilePath,maxFileSize); +} + +static NSString *getLogFilePath(NSString *logFileName) { + // 获取Documents目录路径 + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, + NSUserDomainMask, YES); + NSString *documentsDirectory = [paths firstObject]; + NSString *logFilePath = + [documentsDirectory stringByAppendingPathComponent:logFileName]; + return logFilePath; +} + +static void setupLogFileHandle(NSString *logFilePath) { + // 检查文件大小,如果超过最大值则进行滚动 + checkAndRollLogFile(logFilePath); + + // 关闭旧的句柄(如果存在) + if (logFileHandle) { + [logFileHandle closeFile]; + logFileHandle = nil; + } + + // 打开文件句柄 + logFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; + if (!logFileHandle) { + // 如果文件不存在,则创建 + [[NSFileManager defaultManager] createFileAtPath:logFilePath + contents:nil + attributes:nil]; + logFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; + } + + if (logFileHandle) { + // 将文件句柄移动到文件末尾 + [logFileHandle seekToEndOfFile]; + } else { + fprintf(stderr, "Error: Could not open log file for writing.\n"); + } +} + + +static void checkAndRollLogFile(NSString *logFilePath) { + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:logFilePath]) { + NSError *error = nil; + NSDictionary *fileAttributes = + [fileManager attributesOfItemAtPath:logFilePath error:&error]; + if (fileAttributes) { + unsigned long long fileSize = [fileAttributes fileSize]; + if (fileSize > kMaxFileSize) { + // 文件过大,进行滚动 + NSString *backupFilePath = + [logFilePath stringByAppendingString:@".bak"]; + [fileManager removeItemAtPath:backupFilePath + error:nil]; // 移除旧的备份文件 + [fileManager moveItemAtPath:logFilePath + toPath:backupFilePath + error:&error]; // 将当前文件备份 + if (error) { + fprintf(stderr, "Error rolling log file: %s\n", + error.localizedDescription.UTF8String); + } + // 创建新的空日志文件 + [fileManager createFileAtPath:logFilePath contents:nil attributes:nil]; + // 重新设置文件句柄 + setupLogFileHandle(kLogFilePath); + } + } else { + fprintf(stderr, "Error getting file attributes: %s\n", + error.localizedDescription.UTF8String); + } + } +} + +void XSLogRedirectNSLog(void) { + if (logFileHandle) { + int fd = [logFileHandle fileDescriptor]; + if (fd != -1) { + // 将标准错误输出重定向到日志文件 + if (dup2(fd, STDERR_FILENO) == -1) { + fprintf(stderr, "Error redirecting stderr to log file.\n"); + } + } else { + fprintf(stderr, "Error: logFileHandle has an invalid file descriptor.\n"); + } + } else { + fprintf(stderr, "Error: logFileHandle is not initialized. Cannot redirect stderr.\n"); + } +} + +void XSLogMessage(NSString *format, ...) { + va_list args; + va_start(args, format); + NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; + va_end(args); + + dispatch_async(logQueue, ^{ + // 获取当前时间 + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS"; + NSString *timestamp = [dateFormatter stringFromDate:[NSDate date]]; + + // 格式化日志消息 + NSString *logString = + [NSString stringWithFormat:@"%@ %@\n", timestamp, message]; + + // 将日志写入文件 + NSData *data = [logString dataUsingEncoding:NSUTF8StringEncoding]; + if (logFileHandle) { + @try { + [logFileHandle writeData:data]; + // 每次写入后,递增计数器 + logCount++; + // 每隔 kLogCheckFrequency 条日志检查一次文件大小 + if (logCount >= kLogCheckFrequency) { + logCount = 0; // 重置计数器 + checkAndRollLogFile(kLogFilePath); + } + } @catch (NSException *exception) { + fprintf(stderr, "Error writing to log file: %s\n", + exception.reason.UTF8String); + } + } + + // 同时输出到控制台,以便调试 + fprintf(stderr, "%s", [logString UTF8String]); + }); +}