OSLog与NSLog,OSLog的实践
一、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
统一日志系统使用了几个日志级别,它们对应于应用程序可能需要捕获的不同类型的消息,并定义消息何时保存到数据存储中,以及消息保存多长时间。系统为每个级别实现标准行为。可以使用日志命令行工具或自定义配置文件覆盖此行为(请参阅调试时自定义日志行为)。
-
default
默认级别的消息最初存储在内存缓冲区中。在不更改配置的情况下,它们将被压缩并随着内存缓冲区的填充移动到数据存储区。它们会一直保留到超过存储配额,此时,最古老的消息将被清除。使用此级别捕获可能导致失败的信息。 -
info
信息级消息最初存储在内存缓冲区中。如果不进行配置更改,则不会将它们移动到数据存储区,并在内存缓冲区填充时清除它们。但是,当发生错误或错误时,它们会在数据存储中捕获。当信息级别的消息被添加到数据存储中时,它们将一直保留在那里,直到超过存储配额,此时,最古老的消息将被清除。使用此级别捕获对故障排除可能有帮助但不是必需的信息。 -
debug
调试级别的消息只在通过配置更改启用调试日志记录时在内存中捕获。根据配置的持久性设置清除它们。此级别记录的消息包含在开发期间或排除特定问题时可能有用的信息。调试日志记录用于开发环境,而不是发布软件。 -
error
错误级别的消息总是保存在数据存储中。它们会一直保留到超过存储配额,此时,最古老的消息将被清除。错误级消息用于报告流程级错误。如果存在活动对象,则此级别的日志记录将捕获整个流程链的信息。 -
fault
故障级消息总是保存在数据存储中。它们会一直保留到超过存储配额,此时,最古老的消息将被清除。故障级消息仅用于捕获系统级或多进程错误。如果存在活动对象,则此级别的日志记录将捕获整个流程链的信息。
保护隐私功能,格式化信息
要格式化日志消息,请使用标准的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.png3.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.png4.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