如何使用 Unified Logging, Swift版
作者 Jared Sinclair 翻译者 Guanglei Liu
原文出处: https://www.bignerdranch.com/blog/migrating-to-unified-logging-swift-edition/
你是否正在考虑把所有的iOS 或者 macOS app 里面的 NSLog 或者 print 用最新的 Unified Logging system 替换掉呢?那么请你继续读下去,本文会给你一些相关的建议。
如果你还不太确定是否应该使用Unified Logging, 那么以下列出了一些使用他的优点。
1. 它是最新的规格。 Unified Logging system 是苹果平台最近几年最大的改善。它奠定了未来苹果平台使用 Logging 的具体方向。
2. 在没有牺牲任何功能的情况下提高了app的性能。最新的 Logging system 是一个全新设计的系统,用来减少传统的 Logging 系统所产生的 observer effect 影响。它在保持了原有的功能的基础提高了系统的性能。
3. .能够让你的Debugging流程更加方便。 使用最新的API,可以通过Console.app自定义subsystem并且分类, 利用 search filters 来显示或者隐藏 信息。 针对于 debug 很多个model 或者 process 的程序会带来很大的方便。
Unified Logging system 跟传统的 Logging 相比之下的四大优点
以下并没有全部列出每一个Unified Logging的改善,但是通过几个具体的例子来体现它跟之前的系统有什么本质上的不同。
1. OSLog不是function, 而是一个 data type
如果你把OSLog当成是一个function那你就错了。正如NSLog一样, 类似os_log 这样的function都是在<os/log.h>里面定义的。多数的function都是把 os_log_t ( 在 swift 里面就是 OSLog ) 作为一个argument. 在Console 软件里面,OSLog 和其他一些相关的信息结合在一起使用,来实现在搜索跟过滤的功能。
2. 必须使用Console 软件
当使用Unified Logging的时候输出结果并不是可读的,他们都是以不可读的格式写在硬盘里面的,只能通过以log archive的形式 在Console 软件里面打开(后文会介绍如何生成一个log archive)。 Console可以把log archive转化成一种方便搜索跟过滤的形式。 数据格式的不透明性可能是跟以往的Logging system 最大区别。苹果公司花了很大的心思去优化之前传统Logging system对系统的性能跟内存空间所造成的影响。
3. 把 StaticString 当成 log message
以下的Swift代码是无法编译的
let foo = "Something happened."
os_log(foo)
// Error: Cannot convert value of type 'String' to expected argument type 'StaticString'
这是因为os_log函数需要一个StaticString,而不是一个String
let foo: StaticString = "Something happened."
os_log(foo)
或者直接把变量去掉
os_log("Something happened.")
常量的String也可以使用C-style的格式
os_log("We bolster %ld husk nuts to each girdle jerry.", 12)
但是 Swift 形式的String转换是不支持的
let count = 12
os_log("We bolster \(count) husk nuts to each girdle jerry.")
// Error: Cannot convert value of type 'String' to expected argument type 'StaticString'
这可能是你从 NSLog 转换到 Unified Logging 会遇到的跟Swift代码最不同的地方。接下来需要做的就是用os_log来替换所有的NSLog和print。
其实是可以log 一个String的,但是必须是以常数参数的形式:
let count = 12
let string = "We bolster \(count) husk nuts to each girdle jerry."
os_log("%@", string)
但是这个解决方法会有一个很大的问题,我们接下来会看到。
4. 除非例外,否则参数的格式应该是
在默认的情况下,应该用以下的格式log一个String
os_log("What is %@?", "threeve")
结果会在Console.app中以以下的形式呈现
Process | Message |
---|---|
MyApp | What is < redacted > ? |
要是想在log中显示完整的消息,必须要把参数的格式标记为{public}
os_log("What is %{public}@?", "threeve")
结果会在Console.app中以以下的形式呈现
Process | Message |
---|---|
MyApp | What is threeve? |
或者,如果不使用 {public} 的话,也可以通过在运行程序之前执行一下任一操作也可以达到想通的效果。
- 把设备链接到Xcode debugger
- 安装一个特殊的 logging profile
有一些参数类型就不需要这些变通方法。 当用作格式参数时,它将默认为隐含的公共范围。如果你想让输出值是保密的,也可以将标量参数标记为{private}。
os_log("My secret ID is %{private}ld.", user.secretId)
使用Unified Logging应该注意的规则
以下是我认为最好的一些使用方法·,没有特别的顺序
注意log levels (type)
在 Unified Logging 里面一共有5种 log types, 并且根据不同的使用用途而分成了不同的 levels. 这里有简短的摘要,直接来自官方文档:
default
: 用来捕获可能导致系统失败的信息info
: 用来获取有用的信息,单对于排除错误又不是必要的信息debug
: 用来在开发期间排除运行故障时候针对某些特定问题的信息error
: 用来获取运行当中 process-level 的错误信息fault
: 用来获取运行当中 system-level 或者 multi-process 相关的错误信息
请根据具体情况来选择最适合的类型, 因为每种 types 的处理方式都不同。这个WWDC 视频里有更多有用的信息。
不要在最后的产品中使用 OSLog.default
你不需要自己初始化 OSLog,因为 OSLog.default 是系统默认的,并且它包含了一些类似 os_log 的函都需要 OSLog 作为其中的一个参数。但是当你使用 OSLog.default 的时候,过滤log信息的能力是会受到限制的,因为它没有给 subsystem 跟 category 提供值。
当你初始化了一个自己的 OSLog 的时候,你就可以使用 subsystem 跟 category 的功能了。这样 Console 软件会很轻松的过滤出log 信息
在命名自己 subsystem 跟 categories 的时候 要保持统一
坚持在整个应用系统里面保持命名一直性。非常值得花一些时间来研究Apple 是如何为自己的程序选择命名的,这对你选择自己系统命名的时候会有很大的帮助。以下是我从通过研究 Apple patterns 之后的一些建议。
- 当你命名subsystem的时候,使用一个跟自己相关的域名。所有 Apple 的log system 都是用 com.apple 作为命名 subsystem 的前缀,例如 com.apple.Siri 还有 com.apple.coredata.
- 加入你的代码以后会放在 frameworks 里面,最好是使用 bundle ID 作为subsystem 的名字。例如如果是application-level logs的话就使用 com.company.MyApp,framework-level logs 就使用 com.company.MyApp.SomeFramework
- 不要给category起跟域名相关的名字。要起一个简单易懂的名字例如”Web Service”
- 当给category起名字的时候尽量选取一个有助于缩小范围的名字。 或者是可以关联到相关文件或者subsystem的名字。例如,你的log是针对于 Authenticator class 并且是在一个framework里面的,那你可以把 category 命名为 Authenticator。 假如你有很多 authentication 的代码分散在好多个class 或者是 framework 里,你可以让所有的的 catagory 都使用Authentication的命名。 这可以让你在整个系统里面观察所有关于 Authentication 的任务
不要把log隐藏在conditionals里面
因为logs都是在Console里面生成的,所以没有必要去programmatically 通过 任何condition去过滤。 使用适当的类型简单直接地记录所有内容,让系统负责其余部分。
尝试去收集sysdiagnose
尝试从硬件中获取sysdiagnose信息。这里有完整的说明,但是有几个重点:
- 按住或者放开按钮
- 等待十分钟
- 进入到Setting.app 里面的一个界面
- 点击压缩的sysdiagnose和AirDrop它到Mac(大小约为300MB)
一点你得到了Mac 上面的sysdiagnose之后,你可以在Console.app中打开它包含的.logarchive文件,并查看该设备上所有Log。如果您已经注意到我对categories和subsystems的建议,您应该能够在短时间内过滤所需的信息。
有的时候需要花10分钟的时间才会收集到sysdiagnose。 正是因为这种延迟,sysdiagnoses才不应该作为日常调试的一部分。 相反,sysdiagnoses在以下情况下很有用:
- 当一个用户报告了一个bug的时候,可以通过sysdiagnose的步骤,找到一种方法让他们把report发给你
- 你或者其他人遇到了一个bug 但是又不在电脑前面。可以使用sysdiagnose,然后回到办公室之后从设备中提取它
不要把所有的argument 都当成是public
如果你已经习惯了以文本格式阅读所有的log信息的话,那么Unified Logging system’s private-by-default policy可能会对你带来一些不适应。不要把所有的格式都转化成公开文本 {public}。 这不仅会有泄露客户私人信息的危险,而且还有暴露你自己公司机密的可能。不难想象,log 信息可能会不小心泄露你们公司OAuth credentials
观看WWDC的视频
下面的两个视频都是跟Unified Logging相关的学习材料
结语
- 理论上讲,你确实是可以使用 Xcode 的 console 来观察输出结果的,但是必须是在你的 app 运行的时候才可以使用。Xcode 的 console 比 Console.app 的功能少了很多,所以很难用它来深度的解析app的log。