美文网首页
iOS开发调试技巧之NSLog日志导出

iOS开发调试技巧之NSLog日志导出

作者: BrianWang | 来源:发表于2020-09-09 16:02 被阅读0次

开发过程中,调试必不可少,而日志则是一个重要的调试信息。当直接运行代码进行调试时,可以在Xcode控制台实时看到日志信息。然而当脱离了Xcode控制台,比如,安装到手机上时,这时我们该如何去查看日志呢?

其实可以把日志写入到一个文件中,然后通过文件查看日志信息。

把日志写入文件,主要是利用C语言的freopen()函数进行重定向,将写往stdoutstderr的内容重定向到我们指定的文件中去,代码如下:

// _logFilePath: 日志文件路径
freopen([_logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
freopen([_logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);

一旦执行了freopen()函数,那么之后的NSLog()函数输出的日志信息将不会再在Xcode控制台中显示,而是直接输出到日志文件中去了。

1. NSLog日志导出功能集成

根据上面使用freopen()进行重定向的原理,然后结合iOS系统的分享弹窗UIActivityViewController,即可完成日志文件导出的功能。

举例:当测试人员在某个页面发现一个错误或问题时,如果开发人员不能简单的通过他们描述的现象定位到问题的话,那么就需要开发人员自己去复现问题,然后分析代码以定位问题了。一般开发人员可以通过在代码中添加一些NSLog,把想要了解的信息通过Xcode控制台打印出来,然后去分析。

如果问题是必现的,那还好,复现问题然后就能进行分析、解决。

那如果问题是偶现的呢?开发人员不一定能复现问题,那就需要测试人员配合,进行多次测试了。测试人员如何在复现了问题时能及时的把日志给到开发人员呢?这时就可以在页面中添加日志导出功能了。添加该功能后,当测试人员再次复现问题时就可以立即导出日志文件发送给开发人员了。

若要在一个控制器中实现日志导出功能,则关键代码如下:

#import "ViewController.h"
#import "BWLogExportManager.h"

@interface ViewController () 

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 开始日志记录
    [BWLogExportManagerShared startLogRecord];
    
    [self setupUI];
}


#pragma mark - Events

/// 日志导出事件
- (void)logExport {
    [BWLogExportManagerShared exportLogFile];
}

2. 实现效果

点击右上角 “Log” 按钮,弹出系统分享弹窗。然后选择将日志文件通过Mail、QQ等方式分享或直接保存到手机中。

1. 系统分享弹窗 2. 导出的日志文件内容

3. BWLogExportManager 工具类的代码

不想拷贝下面代码的,去下载 👉👉👉👉: LogExportDemo

BWLogExportManager.h

//
//  BWLogExportManager.h
//  LogExportDemo
//
//  Created by wangzhi on 2020/9/9.
//  Copyright © 2020 BTStudio. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

#define BWLogExportManagerShared [BWLogExportManager shared]


NS_ASSUME_NONNULL_BEGIN

/// 日志导出管理器
@interface BWLogExportManager : NSObject

/// Log日志文件名称 (默认格式为: 2020-01-01_08-59-59.log)
///
/// - 文件名称可设置为: xxx.log / xxx.txt, 建议设置为: xxx.log
///
/// - 提示: 需要在 开始日志记录 前设置文件名称,否则无效
@property (nonatomic, copy) NSString *logFileName;


/// 单例
+ (instancetype)shared;


/// 开始日志记录
- (void)startLogRecord;

/// 通过系统的分享功能导出Log日志文件
/// @param viewController  弹出系统分享弹窗的控制器
- (void)exportLogFileInViewController:(UIViewController *)viewController;

/// 通过系统的分享功能导出Log日志文件
- (void)exportLogFile;

@end

NS_ASSUME_NONNULL_END

BWLogExportManager.m

//
//  BWLogExportManager.m
//  LogExportDemo
//
//  Created by wangzhi on 2020/9/9.
//  Copyright © 2020 BTStudio. All rights reserved.
//

#import "BWLogExportManager.h"

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
#import "SceneDelegate.h"
#else
#endif

@interface BWLogExportManager () {
    NSString *_defaultLogFilePath;
}

/// Log文件夹路径
@property (nonatomic, copy) NSString *logFolderPath;

/// Log日志文件路径
@property (nonatomic, copy, readonly) NSString *logFilePath;

@end

@implementation BWLogExportManager

/// 单例
+ (instancetype)shared {
    static BWLogExportManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}


- (instancetype)init {
    if (self = [super init]) {
        // 创建 BWLog 文件夹
        _logFolderPath = [BWLogExportManager createFolderAtPath:[BWLogExportManager getDocumentPath] folderName:@"BWLog"];
        
        // 设置Log日志文件的默认名称 (默认使用时间作为文件名称)
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss"];
        NSString *time = [dateFormatter stringFromDate:[NSDate date]];
        _logFileName = [NSString stringWithFormat:@"%@.log", time];
        
        // Log日志文件路径
        _defaultLogFilePath = [_logFolderPath stringByAppendingPathComponent:_logFileName];
        _logFilePath = _defaultLogFilePath;
    }
    return self;
}


#pragma mark - Setters

- (void)setLogFileName:(NSString *)logFileName {
    _logFileName = logFileName;
    
    // 拼接Log日志文件路径
    _logFilePath = [_logFolderPath stringByAppendingPathComponent:_logFileName];
}


#pragma mark - Public

/// 开始日志记录
- (void)startLogRecord {
    // 如果连接Xcode进行调试,则不输出到文件中
//    if (isatty(STDOUT_FILENO)) {
//        return;
//    }
    
    // 1. 判断Log日志文件是否已存在,若存在,则删除之前的文件
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    BOOL isDirectory = NO;
    BOOL exists = [fileManager fileExistsAtPath:_logFilePath isDirectory:&isDirectory];
    if (exists) {
        NSError *error = nil;
        [fileManager removeItemAtPath:_logFilePath error:nil];
        if (error) {
            NSLog(@"delete file error: %@", error.localizedDescription);
        }
    }
    
    // 2. 将日志信息写入到Log日志文件中
    freopen([_logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stdout);
    freopen([_logFilePath cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
}

/// 通过系统的分享功能导出Log日志文件
/// @param viewController  弹出系统分享弹窗的控制器
- (void)exportLogFileInViewController:(UIViewController *)viewController {
    // 1. 获取文件路径
    NSURL *fileURL = nil;
    // 判断文件是否存在
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = NO;
    BOOL exists = [fileManager fileExistsAtPath:self.logFilePath isDirectory:&isDirectory];
    if (exists) {
        fileURL = [NSURL fileURLWithPath:self.logFilePath];
    } else {
        fileURL = [NSURL fileURLWithPath:_defaultLogFilePath];
    }
    
    // 2. 弹出系统分享控制器以导出文件
    dispatch_async(dispatch_get_main_queue(), ^{
        NSArray *itemsArr = @[fileURL];
        UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:itemsArr applicationActivities:nil];
        
        // 适配iPad
        UIPopoverPresentationController *popVC = activityViewController.popoverPresentationController;
        popVC.sourceView = viewController.view;
        [viewController presentViewController:activityViewController animated:YES completion:nil];
    });
}

/// 通过系统的分享功能导出Log日志文件
- (void)exportLogFile {
    UIWindow *window = [BWLogExportManager getCurrentWindow];
    [self exportLogFileInViewController:window.rootViewController];
}


#pragma mark - Tool Methods

/// 获取 Document 路径
+ (NSString *)getDocumentPath {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    return [paths objectAtIndex:0];
}

/// 创建指定路径的文件夹
/// @param path 指定的路径
/// @param name 文件夹名称
+ (NSString *)createFolderAtPath:(NSString *)path folderName:(NSString *)name {
    // 1. 拼接文件夹路径
    NSString *folderPath = [NSString stringWithFormat:@"%@/%@", path, name];
    
    // 2. 判断文件夹是否存在,如果不存在,则创建文件夹
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDirectory = YES;
    BOOL exists = [fileManager fileExistsAtPath:folderPath isDirectory:&isDirectory];
    if (exists) { // 文件夹已存在,则直接返回文件夹路径
        return folderPath;
    }
    
    // 3. 文件夹不存在或者不是文件夹,则创建文件夹
    NSError *error = nil;
    BOOL success = [fileManager createDirectoryAtPath:folderPath withIntermediateDirectories:YES attributes:nil error:&error];
    if (!success || error) { // 创建文件夹失败
        NSLog(@"create folder error: %@", error.localizedDescription);
        return nil;
    }
    // 创建文件夹成功,返回文件夹路径
    return folderPath;
}

/// 获取应用程序当前窗口
+ (UIWindow *)getCurrentWindow {
    UIWindow *window = UIApplication.sharedApplication.keyWindow;
    
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
    if (@available(iOS 13.0, *)) {
        NSSet *scenes = UIApplication.sharedApplication.connectedScenes;
        for (UIScene *scene in scenes) {
            if (scene.activationState == UISceneActivationStateForegroundActive ||
                scene.activationState == UISceneActivationStateForegroundInactive) {
                SceneDelegate *delegate = (SceneDelegate *)scene.delegate;
                window = delegate.window;
                break;
            }
        }
    }
#else
    
#endif
    return window;
}

@end

相关文章

  • iOS开发调试技巧之NSLog日志导出

    开发过程中,调试必不可少,而日志则是一个重要的调试信息。当直接运行代码进行调试时,可以在Xcode控制台实时看到日...

  • iOS开发之Xcode常用调试技巧总结

    iOS开发之Xcode常用调试技巧总结 iOS开发之Xcode常用调试技巧总结

  • iOS开发调试技巧总结

    iOS开发调试技巧总结 iOS开发调试技巧总结

  • iOS学习笔记40-日志重定向

    一、日志重定向 我们在iOS开发过程中,我们时常会使用NSLog打印到控制台的日志信息进行代码调试,但这样调试的前...

  • Swift 常用快捷键

    iOS开发之Xcode常用调试技巧总结 Xcode 常用快捷键 lldb技巧:

  • iOS 调试

    iOS调试 - NSLog iOS调试 - 断点 iOS调试 - LLDB iOS调试 - EXC_BAD_ACC...

  • NSLog 和 print 源码阅读和捕获方案

    前言 NSLog 作为 iOS开发常用的调试和日志打印方法,大家都是很熟悉了,开源社区也为我们贡献了很多非常优秀的...

  • IOS开发中 NSLog的处理

    NSLog 函数打印信息是 IOS 开发中最简单的代码调试方法,但是 NSLog 本身的性能很差,程序发布后,不应...

  • iOS中的"NSLog"宏

    在开发iOS应用时我们使用Log(…)来输出日志信息,就可以在发布应用的时候,一次性将NSLog语句移除(在调试模...

  • LLDB调试器

    前言 在iOS开发中经常使用NSLog进行打印调试,简单方便。NSLog每次调用会打印大量的信息,时间、名称、进程...

网友评论

      本文标题:iOS开发调试技巧之NSLog日志导出

      本文链接:https://www.haomeiwen.com/subject/ovmeektx.html