针对线上问题或者用户运用流程的追寻, 自界说日志是很不错的解决问题的方案,首要思路就是:

iOS APP日志写入文件 & 上传服务器

本文首要介绍两个方案, 第一种方案是自界说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