针对线上问题或者用户运用流程的追寻, 自界说日志是很不错的解决问题的方案,首要思路就是:
本文首要介绍两个方案, 第一种方案是自界说Log文件,来替换NSLog
来运用; 第二种是通过freopen
函数将NSLog的输出日志,重定向保存.
一、自界说Log文件
下面是Log类, 一个开关特点, 一个自界说Log格式输出办法, 一个Log写入文件办法. DEBUG的时分直接输出到控制台, release且Log开关开的时分写入文件
- Log.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#define NSLog(frmt,...) [Log logWithLine:__LINE__ method:[NSString stringWithFormat:@"%s", __FUNCTION__] time:[NSDate date] format:[NSString stringWithFormat:frmt, ## __VA_ARGS__]]
@interface Log : NSObject
+ (void)setFileLogOnOrOff:(BOOL)on;
+ (void)logWithLine:(NSUInteger)line
method:(NSString *)methodName
time:(NSDate *)timeStr
format:(NSString *)format;
@end
NS_ASSUME_NONNULL_END
- Log.m
#import "Log.h"
@implementation Log
static BOOL _fileLogOnOrOff;
+ (void)setFileLogOnOrOff:(BOOL)on {
_fileLogOnOrOff = on;
[Log initHandler];
}
+ (void)logWithLine:(NSUInteger)line
method:(NSString *)methodName
time:(NSDate *)timeStr
format:(NSString *)format {
// 日志时间格式化
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitWeekday |
NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond | NSCalendarUnitNanosecond;
NSDateComponents *comps = [calendar components:unitFlags fromDate:[NSDate date]];
NSString *time = [NSString stringWithFormat:@"%ld-%ld-%ld %ld:%ld:%ld:%@", (long)comps.year, (long)comps.month, (long)comps.day, (long)comps.hour, (long)comps.minute, (long)comps.second, [[NSString stringWithFormat:@"%ld", (long)comps.nanosecond] substringToIndex:2]];
#if DEBUG
// debug 直接输出
fprintf(stderr,"%s %s %tu行: %s.\n", [time UTF8String],[methodName UTF8String],line,[format UTF8String]);
#else
// release && 文件Log开 写入文件
if (_fileLogOnOrOff) {
NSString *logStr = [NSString stringWithFormat:@"[%@]%@ %tu行: ● %@.\n", time, methodName,line,format];
[self writeLogWithString:logStr];
}
#endif
}
+ (void)writeLogWithString:(NSString *)content {
// 称谓自界说,上传服务器的时分记住关联userId就可以, 便于下载
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"custom_log.text"];
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager defaultManager];
// 假设不存在
if(![fileManager fileExistsAtPath:filePath]) {
[content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSLog(@"文件写入失利 errorInfo: %@", error.domain);
}
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:filePath];
[fileHandle seekToEndOfFile];
NSData* stringData = [content dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle writeData:stringData]; // 追加
[fileHandle synchronizeFile];
[fileHandle closeFile];
}
#pragma mark - 初始化失常捕获系统
+ (void)initHandler {
struct sigaction newSignalAction;
memset(&newSignalAction, 0,sizeof(newSignalAction));
newSignalAction.sa_handler = &signalHandler;
sigaction(SIGABRT, &newSignalAction, NULL);
sigaction(SIGILL, &newSignalAction, NULL);
sigaction(SIGSEGV, &newSignalAction, NULL);
sigaction(SIGFPE, &newSignalAction, NULL);
sigaction(SIGBUS, &newSignalAction, NULL);
sigaction(SIGPIPE, &newSignalAction, NULL);
//失常时调用的函数
NSSetUncaughtExceptionHandler(&handleExceptions);
}
void signalHandler(int sig) {
// 打印crash信号信息
NSLog(@"signal = %d", sig);
}
void handleExceptions(NSException *exception) {
NSLog(@"exception = %@",exception);
// 打印仓库信息
NSLog(@"callStackSymbols = %@",[exception callStackSymbols]);
}
@end
1.1 Log文件在内部就区分了是否为DEBUG环境, 所以在运用上直接恳求对应id的接口, 依据后台设置的成果打开本地写入功用即可.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 可以多处放这一段代码, 放到此处是因为进后台的操作较少, 不会在恳求期间漏掉log信息
[self requestFileLogOnOrOffWithUseId:12345 complete:^(BOOL offOrOn) {
// 既是DEBUG用, 此处也可做成同步, 运用dispatch_semaphore CGD信号量即可, 一般不需要这么极端.
[Log setFileLogOnOrOff:offOrOn];
}];
return YES;
}
1.2 触发Log的写入
在所有你想写入记载的方位, 引入头文件, 进行正常的NSLog打印即可, 例如:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"调用了%s办法", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"调用了%s办法", __func__);
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
}
只要是打印了, 都会默许写入到文件中, 比方没有完结按钮的点击工作, 溃散信息也会进行记载:
[2022-2-21 14:28:59:32]-[ViewController viewWillAppear:] 22行: 调用了-[ViewController viewWillAppear:]办法. [2022-2-21 14:29:4:15]-[ViewController viewDidAppear:] 27行: 调用了-[ViewController viewDidAppear:]办法. [2022-2-21 14:29:5:19]handleExceptions 83行: exception = -[ViewController buttonClick]: unrecognized selector sent to instance 0x7f7adda07690. [2022-2-21 14:29:5:19]handleExceptions 85行: callStackSymbols = ( 0 CoreFoundation 0x000000010f38cbb4 __exceptionPreprocess + 242 1 libobjc.A.dylib 0x000000010f240be7 objc_exception_throw + 48 2 CoreFoundation 0x000000010f39b821 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0 3 UIKitCore 0x000000011a57ff90 -[UIResponder doesNotRecognizeSelector:] + 264 4 CoreFoundation 0x000000010f3910bc forwarding + 1433 …… ######1.4 终究就是将上传服务器的内容下载下来就可以进行解析了.
二、freopen
2.1 原理论述:
-
freopen()函数用于文件流的的重定向,一般是将 stdin、stdout 和 stderr 重定向到文件.
-
所谓重定向,就是改动文件流的源头或目的地。stdout(规范输出流)的目的地是显示器,printf()是将流中的内容输出到显示器;可以通过freopen()将stdout 的目的地改为一个文件(如output.txt),再调用 printf(),就会将内容输出到这个文件里面,而不是显示器.
-
freopen()函数的原型为:
FILE *freopen(const char * __restrict, const char * __restrict,
FILE * __restrict) __DARWIN_ALIAS(freopen);
运用办法:
FILE *fp = freopen(“xx.txt”,“r”,stdin);//将规范输入流重定向到xx.txt。即从xx.txt中获取读入。
第二个参数(形式):
“r” 翻开一个用于读取的文件。该文件有必要存在。
“w” 创立一个用于写入的空文件。假设文件称谓与已存在的文件相同,则会删去已有文件的内容,文件被视为一个新的空文件。
“a” 追加到一个文件。写操作向文件结尾追加数据。假设文件不存在,则创立文件。
“r+” 翻开一个用于更新的文件,可读取也可写入。该文件有必要存在。
“w+” 创立一个用于读写的空文件。
“a+” 翻开一个用于读取和追加的文件。
- 【参数】
@return 返回值为一个指向FILE类型的指针 @param 参数分别为重定向时的文件途径、文件拜访形式以及被重定向的流
2.2 相同针对某一用户是否打开日志搜集(release&后台打开):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self requestFileLogOnOrOffWithUseId:12345 complete:^(BOOL offOrOn) {
#ifdef DEBUG
#else
[self redirectNSlogToDocumentFolder];
#endif
}];
return YES;
}
#pragma mark - 日志搜集
- (void)redirectNSlogToDocumentFolder {
NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSDateFormatter *dateformat = [[NSDateFormatter alloc]init];
[dateformat setDateFormat:@"yyyy-MM-dd-HH-mm-ss"];
// 发动时间为文件称谓, 可自界说
NSString *fileName = [NSString stringWithFormat:@"LOG-%@.txt",[dateformat stringFromDate:[NSDate date]]];
NSString *logFilePath = [documentDirectory stringByAppendingPathComponent:fileName];
// 先删去现已存在的文件
NSFileManager *defaultManager = [NSFileManager defaultManager];
[defaultManager removeItemAtPath:logFilePath error:nil];
// 将log输入到文件
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
}
2.3 相同触发1.2Log写入, 日志内容为:
2022-02-21 15:03:42.446299+0800 LogDemo[3275:2248894] 调用了-[ViewController viewWillAppear:]办法 2022-02-21 15:03:42.560687+0800 LogDemo[3275:2248894] 调用了-[ViewController viewDidAppear:]办法 2022-02-21 15:05:10.752340+0800 LogDemo[3275:2248894] -[ViewController buttonClick]: unrecognized selector sent to instance 0x7f96b4107ba0 2022-02-21 15:05:10.760077+0800 LogDemo[3275:2248894] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[ViewController buttonClick]: unrecognized selector sent to instance 0x7f96b4107ba0’ *** First throw call stack: ( 0 CoreFoundation 0x0000000101d86bb4 __exceptionPreprocess + 242 1 libobjc.A.dylib 0x0000000101c3abe7 objc_exception_throw + 48 2 CoreFoundation 0x0000000101d95821 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0 3 UIKitCore 0x0000000107396f90 -[UIResponder doesNotRecognizeSelector:] + 264 4 CoreFoundation 0x0000000101d8b0bc forwarding + 1433 …… ######2.3 是否上传和下载就依据个人需求而定了.
三、异同点剖析
3.1 自界说log文件可以自界说打印格式和很多其他的拓展功用, 可是本身是根据宏界说来完结的, 所以对于组件化的工程不是很和睦, 入侵性较大. 3.2 freopen()函数写入呢, 原汁原味, 无依靠, 只需要判别好触发条件即可; 还有一个坑点就是磁盘内存的判别, 比较厌恶, 运用时注意下即可.
四、结语
路漫漫其修远兮,吾将上下而求索~
作者简书
作者
作者GitHub
.End