iOS 查看及导出项目运行日志

avatar
奇舞团移动端团队 @奇舞团

级别:★☆☆☆☆
标签:「iOS 查看及导出项目运行日志」「iOS 查看日志」「iOS 查看崩溃日志」
作者: WYW
审校: QiShare团队


前文:
最近笔者在家远程办公的时候,在测试项目时,遇到了测试同学测试出了问题,但是笔者这边不能复现的情况。所以整理了一下 iOS 查看及导出项目运行日志。如大家有需要,可以继续查看详情。

笔者将分享iOS 查看及导出项目运行日志的内容,全文分为如下7个部分。

  1. 控制台查看日志;
  2. 重定向 NSLog 日志;
  3. 通过 Xcode下载 Container 查看日志;
  4. 通过 PAirSandbox:AirSandbox 查看 Documents 中的内容;
  5. 通过 文件 App 查看 Documents 中的日志文件;
  6. 自定义捕获普通日志及崩溃日志等;
  7. QiLogTool Demo地址、使用方式及效果演示。

一、控制台查看日志

iPhone 连接 Mac 的情况下使用控制台, 搜索项目名称,笔者这里的项目名称为 QiLogTool ,找出相应的日志。此时不管是否正在使用 Xcode 在运行项目,在控制台中都能查看到 iPhone 中的日志。

控制台查看日志

二、重定向NSLog日志

注:NSLog 重定向后,控制台就不会打印日志了。

使用如下代码可以把 NSLog 日志,重定向到指定的文件目录中。

+ (void)redirectNSLog {
    
    NSString *fileName = @"NSLog.log";
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentDirectory = paths.firstObject;
    NSString *saveFilePath = [documentDirectory stringByAppendingPathComponent:fileName];
    // 先删除已经存在的文件
    NSFileManager *defaultManager = [NSFileManager defaultManager];
    [defaultManager removeItemAtPath:saveFilePath error:nil];

    // 将log输入到文件
    freopen([saveFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
    freopen([saveFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
}

freopen是被包含于C标准库头文件<stdio.h>中的一个函数,用于重定向输入输出流。该函数可以在不改变代码原貌的情况下改变输入输出环境,但使用时应当保证流是可靠的。

引自360百科:freopen

FILE	*freopen(
    const char * __restrict,
    const char * __restrict, 
    FILE * __restrict)
    __DARWIN_ALIAS(freopen);

形参说明:
filename:需要重定向到的文件名或文件路径。
mode:代表文件访问权限的字符串。例如,"r"表示"只读访问""w"表示"只写访问""a"表示"追加写入"。
stream:需要被重定向的文件流。

下图是笔者把 NSLog 的内容重定向输出到 NSLog.log 之后的截图。

NSLog 重定向

三、通过 Xcode下载 Container 查看日志

通过Xcode 中的container 部分获取日志

下图是笔者在官方文档截图的沙盒目录。

沙盒文件示意

下方的截图依次是笔者通过 Xcode 获取安装包中的沙盒文件的截图。

Xcode 沙盒文件1

Xcode 沙盒文件2

Xcode 沙盒文件3

Xcode 沙盒文件4

Xcode 沙盒文件5

四、通过 PAirSandbox 查看 Documents 中的内容

使用 PAirSandbox:AirSandbox,从手机屏幕右侧边缘,左滑手势可以触发显示查看当前沙盒中的内容的window。并且可以通过三方软件把沙盒中的内容分享给其他人。点击右上角的 Close 关闭按钮即可关闭沙盒目录界面。

#ifdef DEBUG
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [[PAirSandbox sharedInstance] enableSwipe];
        });
    #endif

效果示意图如下:

PAirSandbox1

PAirSandbox2

PAirSandbox3

五、通过 文件 App 查看 Documents 中的日志文件

需要在 Info.plist 文件中配置如下内容,便可实时在文件 App 中查看沙盒中的文件内容。

设置 Application supports iTunes file sharing 为YES。 设置 Supports opening documents in place 为 YES。

设置的示意图如下:

File App1

通过文件 App 实时查看日志的效果示意图如下。

File App2

File App3

六、自定义捕获普通日志及崩溃日志等

1. 对可能出现崩溃的代码进行 try catch 处理

/**
	* 普通异常的捕获方式:
	* 2020-03-25 10:47:11.179085+0800 QiLogTool[18371:4064396] exception:***
	* -[__NSSingleObjectArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 0]
	* 2020-03-25 10:47:11.179310+0800 QiLogTool[18371:4064396] finally
*/
@try {
    NSArray *arr = @[@(1)];
    arr[2];
} @catch (NSException *exception) {
    NSLog(@"exception:%@", exception);
} @finally {
    NSLog(@"finally");
}

如果项目中可能出现异常的地方比较多,使用try catch的方式可能会比较繁琐。那么可以考虑使用捕获项目全局异常的方式。

2. 使用全局捕获异常的方式处理异常

下方的代码可以查看异常情况,并且记录下来日志,在用户侧,可实现可把崩溃日志上传到服务端,进行日志分析的操作。不过 App 遇到异常依然会闪退。

#ifdef DEBUG
	// 捕获异常 Summary Changes the top-level error handler.
	NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
#endif
#pragma mark - 捕获异常 不防崩
void UncaughtExceptionHandler(NSException *exception) {
    
    // 获取异常崩溃信息
    NSArray *callStack = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSString *crashDetail = [NSString stringWithFormat:@"========异常错误报告========\n name:%@\n reason:\n%@\n callStackSymbols:\n%@", name, reason, [callStack componentsJoinedByString:@"\n"]];
    NSLog(@"%@", crashDetail);
    [QiLogTool logFile:@"crash.log" content:crashDetail];
}

3. 出现崩溃后使用 RunLoop 保证 App 仍然继续运行一次

首先感谢 RunLoop总结:RunLoop的应用场景(五)

QiLogTool 中也有相关的代码。大家有兴趣的话,可以自行下载查看。

捕获异常部分的代码,和笔者在上文中第2步中提到的全局捕获异常的方式类似。可以多了解一下的还有,出现了异常的情况下,我们可以记录日志,并且使用 RunLoop 相关代码保持应用在第一次遇到异常的时候不崩溃。

RunLoop处理遇到异常,保持App 仍然可以继续运行一次的主要代码为:

    // 获取到当前线程的的CFRunLoop对象及 获取包含特定CFRunLoop对象的Modes数组 在指定的Modes中运行当前线程的CFRunLoop对象
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
    
    while (!ignore) {
        for (NSString *mode in (__bridge NSArray *)allModes) {
            // Runs the current thread’s CFRunLoop object in a particular mode.
            CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
        }
    }
    
    CFRelease(allModes);

4. 其他查看 Crash 日志的方法

4.1 Mac 路径下查看

通过如下方式查看崩溃日志不准确。

~/Library/Logs/CrashReporter/MobileDevice

otherSearchCrashStyle1

4.2 使用 Xcode 查看崩溃日志的其他方式

上边的红色箭头的1,2可以用于查看设备端通过 Xcode 安装的项目的日志;

下边的蓝色箭头的1,2可以用于查看上传到 AppStore 项目的 Crash 的日志。

otherSearchCrashStyle2

4.3 使用其他的三方查看线上崩溃日志

七、QiLogTool Demo地址、使用方式及效果演示

1. QiLogTool Demo地址

QiLogTool

2. QiLogTool 使用方式:

把 CrashHandler 和 AirSandBox 及 QiLogTool 的文件夹中的文件都添加到自己的项目中。 在应用启动的时候调用如下代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    if (@available(iOS 13.0, *)) {
        
    } else {
        self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        self.window.backgroundColor = [UIColor whiteColor];
        self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[ViewController new]];
        [self.window makeKeyAndVisible];
    }
    // 是否要直接访问沙盒中的内容
    #ifdef DEBUG
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [[PAirSandbox sharedInstance] enableSwipe];
        });
    #endif
    
    // 重定向NSLog内容 注意:把NSLog的内容重定向到其他文件后 控制台就不会再输出内容
    [QiLogTool redirectNSLog];
    
    #ifdef DEBUG
        // 捕获异常 Changes the top-level error handler.
         NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
    #endif
    
    // #ifdef DEBUG
    #ifdef RELEASE
        // 捕获异常并且有一次应用遇到异常后 不会闪退的处理
        [CrashHandler sharedInstance];
    #endif
    return YES;
}

#pragma mark - 捕获异常 不防崩
void UncaughtExceptionHandler(NSException *exception) {
    
    // 获取异常崩溃信息
    NSArray *callStack = [exception callStackSymbols];
    NSString *reason = [exception reason];
    NSString *name = [exception name];
    NSString *crashDetail = [NSString stringWithFormat:@"========异常错误报告========\n name:%@\n reason:\n%@\n callStackSymbols:\n%@", name, reason, [callStack componentsJoinedByString:@"\n"]];
    NSLog(@"%@", crashDetail);
    [QiLogTool logFile:@"crash.log" content:crashDetail];
    // 记录日志后 可以选择合适的时机把日志上传到服务端 上传成功后 把相应的日志删除即可
}

3. QiLogTool 使用效果演示:

笔者下边的演示在应用启动时调用的代码为:

// 是否要直接访问沙盒中的内容
    #ifdef DEBUG
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [[PAirSandbox sharedInstance] enableSwipe];
        });
    #endif
    
    // 重定向NSLog内容 注意:把NSLog的内容重定向到其他文件后 控制台就不会再输出内容
    [QiLogTool redirectNSLog];
    
    #ifdef DEBUG
    // #ifdef RELEASE
        // 捕获异常并且有一次应用遇到异常后 不会闪退的处理
        [CrashHandler sharedInstance];
    #endif

下图中点击屏幕中间的测试日志按钮后,会调用的方法如下:

- (void)logTest {
    
    NSLog(@"NSLog日志内容");
    // 测试日志工具
    [QiLogTool logFile:@"logfile.log" content:[NSString stringWithFormat:@"时间:%@\n内容:%@\n", [[NSDate date] dateByAddingTimeInterval:8.0 * 60 * 60], @"logContent"]];
    
    // 测试崩溃 记录日志效果
    NSArray *arr = @[@(1)];
    NSLog(@"arr[2]:%@", arr[2]);
}

因笔者录制的 gif 图较大,直接上传受限。如需查看使用过程中的效果图可点击下方链接 QiLogTool使用效果图

参考学习网址

File System Programming Guide
iOS 将NSLog日志重定向输出到文件中保存
iOS崩溃异常的处理
RunLoop总结:RunLoop的应用场景(五)


了解更多iOS及相关新技术,请关注我们的公众号:

可添加如下小编微信,并备注加入QiShare技术交流群,小编会邀请你加入《QiShare技术交流群》。

小编微信

关注我们的途径有:
QiShare(简书)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公众号)

推荐文章:
Flutter Platform Channel 使用与源码分析
开发没切图怎么办?矢量图标(iconFont)上手指南
DarkMode、WKWebView、苹果登录是否必须适配?
iOS 接入 Google、Facebook 登录(二)
iOS 接入 Google、Facebook 登录(一)
Nginx 入门实战 iOS中的3D变换(二)
iOS中的3D变换(一)
WebSocket 双端实践(iOS/ Golang)
今天我们来聊一聊WebSocket(iOS/Golang)
奇舞团安卓团队——aTaller
奇舞周刊