OSLog与NSLog,OSLog的实践

2022-10-29  本文已影响0人  _清墨

一、OSLog与NSLog的区别

NSLog:

NSLog的文档,第一句话就说:Logs an error message to the Apple System Log facility.,所以首先,NSLog就不是设计作为普通的debug log的,而是error log;其次,NSLog也并非是printf的简单封装,而是Apple System Log(ASL)的封装。

ASL是啥?从官方手册上可以看到说明:

These routines provide an interface to the Apple System Log facility. They are intended to be a replacement for the syslog(3) API, which will continue to be supported for backwards compatibility.

意思就是ASL是个系统级别的log工具,syslog的替代版,提供了一系列强大的log功能。不过一般我们接触不到,NSLog就对它提供了高层次的封装,如这篇文档所提到的:

You can use two interfaces in OS X to log messages: ASL and Syslog. You can also use a number of higher-level approaches such as NSLog. However, because most daemons are not linked against Foundation or the Application Kit, the low-level APIs are often more appropriate

一些底层相关的守护进程(deamons)不会link如Foundation等高层框架,所以asl用在这儿正合适;而对于应用层的用NSLog。

OSLog:

苹果官方文档这样介绍:OSLog是一个统一的日志系统,在iOS 10中可用。macOS 10.12及以上版本,tvOS 10.0及以上版本,watchOS 3.0及以上版本。该系统将取代Apple system Logger (ASL)和Syslog api。

它相比以前的NSLog更加优越,苹果极力推荐使用新的日志系统。以前,日志消息被写到磁盘上的特定位置,比如/etc/system.log。统一日志系统将消息存储在内存和数据存储中,而不是写入基于文本的日志文件。

NSLog效率低的原因是NSLog做了两件事:

  • 1.-它将日志消息写入Apple System Logging (asl)设施。这允许日志消息显示在Console.app中。
  • 2.-它还检查应用程序的stderr流是否要去终端(比如当应用程序通过Xcode运行时)。如果是,它将日志消息写入stderr(这样它就会显示在Xcode控制台中)。

要向ASL设施发送日志消息,基本上需要打开到ASL守护进程的客户机连接并发送消息。BUT -每个线程必须使用单独的客户端连接。因此,为了线程安全,每次调用NSLog时,它都会打开一个新的asl客户端连接,发送消息,然后关闭连接。所以说,当这个过程出现N次时,消耗大量资源导致程序变慢也就不奇怪了。

OSLog相比NSlog的优点

1.新的日志系统,跨多个平台Mac,iOS,WachOS
2.相比以前的系统更加的高效
3.日志组织的更有条理。有了 Log levels(default info debug error fault)一些第三方的日志如也有类似功能, 日志有分类的功能
4.保护隐私功能,格式化信息
5.自动提供很多有用信息
6.可以通过控制台app查看日志
7.日志不是可读文本(用console 及相关命令行工具 log 可以查),但可以打包获取,分发
8.苹果提供了日志处理命令行工具
9.可以使用配置文件对日志进行配置

二、OSLog重要部分讲解

Log Levels

统一日志系统使用了几个日志级别,它们对应于应用程序可能需要捕获的不同类型的消息,并定义消息何时保存到数据存储中,以及消息保存多长时间。系统为每个级别实现标准行为。可以使用日志命令行工具或自定义配置文件覆盖此行为(请参阅调试时自定义日志行为)。

保护隐私功能,格式化信息

要格式化日志消息,请使用标准的NSString或printf格式字符串,如清单4所示。有关格式化规则,请参阅字符串格式说明符。

image.png
// 代码示例
os_log("now build-in %{time_t}d ",Int(Date().timeIntervalSince1970))
os_log("now %@ ", NSDate())
os_log("uuid_t %@ ",NSUUID())
os_log("self %@ ",self)
os_log("string %{public}s ","string")
os_log("iec-bytes %{iec-bytes}d ",1024)

// 结果
2022-10-29 15:09:07.724062+0800 SwiftDemo[5317:849193] now build-in 2022-10-29 15:09:07+0800
2022-10-29 15:09:07.724151+0800 SwiftDemo[5317:849193] now Sat Oct 29 15:09:07 2022
2022-10-29 15:09:07.724187+0800 SwiftDemo[5317:849193] uuid_t 7779AA5E-6C78-4D01-8C80-588E0191CDF0
2022-10-29 15:09:07.724216+0800 SwiftDemo[5317:849193] self <SwiftDemo.ViewController: 0x10550ba80>
2022-10-29 15:09:07.724257+0800 SwiftDemo[5317:849193] string string
2022-10-29 15:09:07.724270+0800 SwiftDemo[5317:849193] iec-bytes 1 KiB

三、OSLog的实践

1.基本用法

// 输出一个default-level 信息
os_log("This is a log message.")
// 输出一个info-level 信息
os_log("This is additional info that may be helpful for troubleshooting.", log: OSLog.default, type: .info)
// 输出一个自定义子系统,级别为debug-level 信息
let customLog = OSLog(subsystem: "com.your_company.your_subsystem_name.plist", category: "your_category_name")
os_log("This is info that may be helpful during development or debugging.", log: customLog, type: .debug)
// 示例与结果
let myLog : OSLog = OSLog(subsystem:"mySubsystem", category:"myCategory")
let date = Date()
os_log("测试default-date-%@", date as CVarArg)
os_log(.debug, log: myLog, "测试debug-date-%@", date as CVarArg)
os_log(.error, log: myLog, "测试error-date-%@", date as CVarArg)
os_log(.info, log: myLog, "测试info-date-%@", date as CVarArg)
os_log(.fault, log: myLog, "测试fault-date-%@", date as CVarArg)

// 结果:
2022-10-29 14:02:17.336726+0800 SwiftDemo[41978:4599820] 测试default-date-Sat Oct 29 14:02:17 2022
2022-10-29 14:02:17.336798+0800 SwiftDemo[41978:4599820] [myCategory] 测试debug-date-Sat Oct 29 14:02:17 2022
2022-10-29 14:02:17.336850+0800 SwiftDemo[41978:4599820] [myCategory] 测试error-date-Sat Oct 29 14:02:17 2022
2022-10-29 14:02:17.336911+0800 SwiftDemo[41978:4599820] [myCategory] 测试info-date-Sat Oct 29 14:02:17 2022
2022-10-29 14:02:17.337007+0800 SwiftDemo[41978:4599820] [myCategory] 测试fault-date-Sat Oct 29 14:02:17 2022

2.在控制台app中的展示

image.png

3.os_log与NSlog效率对比

// 单条线程测试时间对比
private func timeTest() {
        let startNSLog = CFAbsoluteTimeGetCurrent()
        for i in 0..<10000 {
            NSLog("nslog---%d", i)
        }
        let endNSLog = CFAbsoluteTimeGetCurrent()

        let startOSLog = CFAbsoluteTimeGetCurrent()
        for i in 0..<10000 {
            os_log("oslog---%d",i)
        }
        let endOSLog = CFAbsoluteTimeGetCurrent()

        print("NSLogTime:%f",endNSLog-startNSLog)
        print("OSLogTime:%f",endOSLog-startOSLog)
}

// 结果
NSLogTime:%f 0.7270380258560181
OSLogTime:%f 0.5310770273208618

NSLogTime:%f 0.6149619817733765
OSLogTime:%f 0.4749699831008911

NSLogTime:%f 0.7736960649490356
OSLogTime:%f 0.5272040367126465

// 多条线程测试时间对比
private func timeTest() {
        let queue = DispatchQueue(label: "myQueue", attributes: [.concurrent])
        let startNSLog = CFAbsoluteTimeGetCurrent()
        for i in 0..<5000 {
            NSLog("nslog---%d", i)
        }
        queue.async {
            for i in 0..<5000 {
                queue.sync {
                    NSLog("nslog---%d", i)
                }
            }
            let endNSLog = CFAbsoluteTimeGetCurrent()
            print("NSLogTime:%f \n",endNSLog-startNSLog)
        }

        let startOSLog = CFAbsoluteTimeGetCurrent()
        for i in 0..<5000 {
            os_log("oslog---%d",i)
        }
        queue.async {
            for i in 0..<5000 {
                DispatchQueue.global().sync {
                    os_log("oslog---%d",i)
                }
            }
            let endOSLog = CFAbsoluteTimeGetCurrent()
            print("OSLogTime:%f",endOSLog-startOSLog)
        }
}

// 结果
NSLogTime:%f 1.227236032485962
OSLogTime:%f 0.8558480739593506

NSLogTime:%f 1.2800310850143433
OSLogTime:%f 0.9496610164642334

NSLogTime:%f 1.2216260433197021
OSLogTime:%f 0.9060399532318115

四、第三方log工具:CocoaLumberjack

CocoaLumberjack是适用于Mac和iOS的快速,简单,功能强大且灵活的日志记录框架。

关于CocoaLumberjack,github参考https://github.com/CocoaLumberjack/CocoaLumberjack

1.Logger

CocoaLumberjack内置了以下几种Logger。
DDASLLogger:将日志写入到控制台.app中。在iOS10开始过时
DDTTYLogger:将日志写入到Xcode控制台。
DDFileLogger:很容易理解,是将log写入到文件中。
DDOSLogger:在iOS10开始使用,在将Log输出到 控制台.app 和 Xcode控制台。跟NSLog的输出方式一致。当然,经过处理之后,性能会比直接使用NSLog要好。

而我们常用的NSLog会将日志写入到控制台.app和Xcode控制台。 所以,想要替换NSLog,官方推荐的做法是:

在iOS10及以上系统版本,使用DDOSLogger。
在iOS10以下版本,使用DDASLLogger+DDTTYLogger。

2.代码实践

private func ddLogTest() {
//        DDLog.add(DDOSLogger.sharedInstance, with: .info)
        DDLog.add(DDOSLogger.sharedInstance)

        DDLogError("测试-error")
        DDLogWarn("测试-warning")
        DDLogInfo("测试-info")
        DDLogDebug("测试-debug")
        DDLogVerbose("测试-verbose")
    }

// 结果
2022-10-29 15:45:42.128931+0800 SwiftDemo[5368:859965] 测试-error
2022-10-29 15:45:42.129108+0800 SwiftDemo[5368:859966] 测试-warning
2022-10-29 15:45:42.129137+0800 SwiftDemo[5368:859966] 测试-info
2022-10-29 15:45:42.129158+0800 SwiftDemo[5368:859966] 测试-debug
2022-10-29 15:45:42.129176+0800 SwiftDemo[5368:859966] 测试-verbose

// 若打开 DDLog.add(DDOSLogger.sharedInstance, with: .info) 代码,结果如下,只会输出级别在info及以上的log
2022-10-29 15:46:45.208849+0800 SwiftDemo[5372:860630] 测试-error
2022-10-29 15:46:45.209026+0800 SwiftDemo[5372:860627] 测试-warning
2022-10-29 15:46:45.209051+0800 SwiftDemo[5372:860627] 测试-info

3.DDFileLogger写入日志文件

如果我们需要做日志文件的写入和读取,那么DDFileLogger是一个很好用的工具,只需添加DDFileLogger就可以将日志记录到文件里面了,跟添加DDOSLogger一样。

// 示例
private func ddLogTest() {
        DDLog.add(DDOSLogger.sharedInstance, with: .info)

        let fileLogger = DDFileLogger.init()
        fileLogger.rollingFrequency = 60 * 60
        DDLog.add(fileLogger)

        DDLogError("测试-error")
        DDLogWarn("测试-warning")
        DDLogInfo("测试-info")
        DDLogDebug("测试-debug")
        DDLogVerbose("测试-verbose")

        let path = fileLogger.currentLogFileInfo?.filePath ?? ""
        let readStr = try? String.init(contentsOfFile: path, encoding: .utf8)

        print("读取出来的log是:\n\(readStr ?? "")")
    }

// 结果
2022-10-29 16:09:24.470125+0800 SwiftDemo[45369:4723867] 测试-error
2022-10-29 16:09:24.509890+0800 SwiftDemo[45369:4723867] 测试-warning
2022-10-29 16:09:24.509973+0800 SwiftDemo[45369:4723864] 测试-info
读取出来的log是:
2022/10/29 08:09:24:470  测试-error
2022/10/29 08:09:24:510  测试-warning
2022/10/29 08:09:24:510  测试-info
2022/10/29 08:09:24:510  测试-debug
2022/10/29 08:09:24:510  测试-verbose

我们也能通过路径找到这个Log文件,文件是text可以直接点开查看

image.png

4.DDFileLogger扩展

log文件的属性自定义

根据项目需求,我们可能需要对log文件做很多的自定义设置。 默认情况下,log文件在多次启动的时候是会重用的,24小时内将log写入到同一个文件中,当文件大小超过1MB或者创建时间超过24小时,会新生成一个log文件,后面的log会写入到新的文件中。文件的个数超过5个的时候,会删除旧的文件。logs文件夹大小超过20M的时候,也会删除旧的文件以释放磁盘空间。

CocoaLumberjack本身提供了这些属性让我们自定义

    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
    //重用log文件,不要每次启动都创建新的log文件(默认值是NO)
    fileLogger.doNotReuseLogFiles = NO;
    //log文件在24小时内有效,超过时间创建新log文件(默认值是24小时)
    fileLogger.rollingFrequency = 60*60*24;
    //log文件的最大3M(默认值1M)
    fileLogger.maximumFileSize = 1024*1024*3;
    //最多保存7个log文件(默认值是5)
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
    //log文件夹最多保存10M(默认值是20M)
    fileLogger.logFileManager.logFilesDiskQuota = 1014*1024*20;

    //添加文件写入logger
    [DDLog addLogger:fileLogger];
Log文件夹与文件名的获取

有些时候我们想要获取到log文件所在的路径,用来进行一些操作。比如将log文件读取出来查看,或者将log文件上传到服务器去。这些路径可以从fileLogger对象里面能够获取到。

//logs文件夹路径
DDLogInfo(@"logsDirectory=%@",fileLogger.logFileManager.logsDirectory);
//logs文件夹的所有log文件路径
DDLogInfo(@"sortedLogFilePaths=%@",fileLogger.logFileManager.sortedLogFilePaths);
//当前活跃的log文件路径
DDLogInfo(@"currentFilePath=%@",fileLogger.currentLogFileInfo.filePath);
log文件存放目录自定义

默认的Log文件存放在沙盒的Library/Caches/Logs目录中,如果想自定义存放位置,可以在创建DDFileogger的时候可以进行设置。

//修改Logs文件夹的位置
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *baseDir = paths.firstObject;
    NSString *logsDirectory = [baseDir stringByAppendingPathComponent:@"SQLog/Logs"];
    DDLogFileManagerDefault *defaultManager = [[DDLogFileManagerDefault alloc] initWithLogsDirectory:logsDirectory];

    DDFileLogger *fileLogger = [[DDFileLogger alloc] initWithLogFileManager:defaultManager];
log文件名自定义

默认的文件名是这样的<bundle identifier><date><time>.log,例如liepin.SwiftDemo 2022-10-31--06-31-04-667.log,这里的时间是差了8小时的

修改日志文件名称官方也作了说明:

*If you wish to change default filename, you can override following two methods.
*- newLogFileName method would be called on new logfile creation.
*- isLogFile method would be called to filter log files from all other files in logsDirectory.
*You have to parse given filename and return YES if it is logFile.

意思就是如果想要改变文件名称需要重写newLogFileName和isLogFile这两个方法。新建一个子类继承DDLogFileManagerDefault,重写方法,然后使用时用这个子类即可(以下只当例子,具体自己定义):

class CustomClass: DDLogFileManagerDefault {
    
    override var newLogFileName: String {
        return "newName"
    }
    
    override func isLogFile(withName fileName: String) -> Bool {
        return true
    }
    
}


private func ddLogTest() {
        DDLog.add(DDOSLogger.sharedInstance, with: .info)
        
        let manager = CustomClass.init()
        let fileLogger = DDFileLogger.init(logFileManager: manager, completionQueue: nil)
        fileLogger.rollingFrequency = 60 * 60
        DDLog.add(fileLogger)
}

结果:


image.png

参考资料:

os.log模块介绍:https://cloud.tencent.com/developer/article/1993775

NSLog详解:<u>http://t.zoukankan.com/mumoozhu-p-4495260.html</u>

日志效率参考:<u>https://www.jianshu.com/p/3e3804d6d60d</u>

官方问题反馈:<u>https://developer.apple.com/forums/thread/82736?page=2</u>

CocoaLumberjack:https://github.com/CocoaLumberjack/CocoaLumberjack

上一篇下一篇

猜你喜欢

热点阅读