有幸参与《WWDC 20 内参》的工作,欢迎 订阅 该专栏!
本次 Session 的思维导图:
概括
这个 session 介绍了 Swift 统一 logging API 的最新版本。在这个视频中,你将学会:
- 在记录应用中的事件和错误的同时,保持日志信息的隐私性。
- 在不牺牲性能的情况下,使用功能强大又提供较好可读性的格式化数据选项。
- 收集和处理日志信息,以理解和调试应用中的意外行为。
在实际的开发任务中,我们难免会遇到一些难以复现的 bug。使用 log API 能帮助我们理解及处理这类疑难杂症。我们可以在日志记录的众多线索中理解 bug 产生的原因。
来看新版 log API
我们可以利用 log API 在应用运行时记录下重要的事件。记录下的日志会被操作系统存档,以方便我们之后读取这些日志。由于新版 log API 十分高效,它们可以在应用中广泛地使用,且不会拖慢应用的运行速度。
仅需三步,就能在应用中使用 log API:
1 | // 第一步:导入 os 模块 |
调用 log 函数与调用 print 函数有点相似,但其实有着本质上的不同。因为转换成字符串会很慢,所以 log 信息不会完全转换为字符串。编译器和 logging library 合作对此进行了高度优化。在这种优化之下,只有当 log 信息真正显示的时候,才会付出将其转换为字符串的成本。
新版 log API 支持众多数据类型
log 函数支持很多数据类型,比如数字类型 Int
和 Double
、Objective-C 对象及所有遵循 CustomStringConvertible
协议的类型。当在 log 消息中加入非数字类型的数据时,非数字类型的数据默认会被系统编辑,这是为了确保应用在实机运行时,不会泄漏个人信息。
看到这里,你可能在心里发问:那当在模拟器上运行时是怎么样的呢?根据 Apple Developer Forums 上的 回答:在 Xcode console 上显示的 log 信息始终不会被系统编辑,也就是说:如果应用从 Xcode 中启动,即使这条 log 信息是非公开的,在 console 中也会完全显示出来,以便开发者调试。
举个例子,这里是在记录类型为字符串的 accountNumber
变量:
但在输出的 log 信息中,accountNumber 被编辑为 <private>
。
我们也可以手动控制 log 信息为公开的:
在终端中收集日志信息
我们可以使用命令行来收集 log 信息。先把设备连接至电脑,再在终端中输入以下命令:
使用 log help collect
可以获得该命令行的使用帮助。
在收集完之后,在当前路径下会产生一个以 .logarchive 结尾的文件。我们可以使用系统自带的 Console App 浏览该文件。
当在 Console App 中浏览这个文件时,常常会在搜索栏输入关键词来过滤无关的信息。这里分享一个小技巧:在搜索栏输入关键词,输入完毕后,点击右下角的保存按钮,可以将关键词保存起来。
下次搜索时,直接点击 log1 按钮,就自动填充搜索关键词,这样就可以免于重复输入搜索关键字。
在 Console App 中实时检视日志信息
使用 log collect
命令,我们可以在应用运行结束后,打开日志文件,阅读日志信息。在应用运行时,我们可以在 Xcode console 阅读日志信息。类似地,在应用运行时(无论是在真机上运行还是在模拟器上运行),我们也可以实时地在 Console App 里阅读日志信息!
只要在 Console App 内选中正确的设备即可(注:这里的设备包括真机和模拟器):
但在我自己动手尝试之后发现:即使在 Console App 的 Action 菜单里勾选 Include Debug Messages 选项,在 Xcode 中以模拟器的方式启动应用,Console App 里实时显示的日志信息竟然不包含 debug 等级的信息。然而,在 Xcode 中以真机的方式启动应用,Console App 里实时显示的日志信息却包含了 debug 等级的信息!
这可能是个 bug。我已经在 Apple Developer Forums 提交了 反馈。
日志等级
系统提供五种不同的日志记录等级:
- Debug level;在 debug 时使用
- Info level;在排查问题时辅助使用
- Notice level;也是默认日志记录等级,在排查问题时使用
- Error;在程序执行出错时使用
- Fault;在程序出现 bug 时使用
日志消息持久化
当我们要在应用运行之后读取日志时,只有那些可以持久化的日志消息才会被读取到。日志消息是否持久保存取决于日志记录等级。
日志记录等级各自的性能也有差异:
[10:50 - 12:11]
处于 Debug 记录等级的日志信息不作持久化存储的处理。而且,当此等级的日志信息不被串流记录时,它们会直接被忽略掉。在日志信息被忽略掉的情况下,Swift 编译器还能确保在此函数内调用的函数根本不会执行。
所以处于 Debug 等级的 log 函数的运行速度是很快的,且在其中调用比较耗时的函数也是安全的。
当真正需要收集 Debug 记录等级的日志时,才会付出对应的开销。更多的情况是,此等级的日志记录会被自动忽略,也就没有那些开销了。
那什么情况下算是串流记录日志呢?
- 设备连接上 Mac,打开 Console App 时,Console App 会开始串流记录日志。
- 在 Xcode 中启动应用时,Xcode debug console 会开始串流记录日志。
如果在 Console App 的 Action 菜单里勾选 Include Debug Messages 选项,在 .logarchive 文件里也能阅读到 debug 等级的日志信息,也就是说:在这种条件下,debug 等级的日志信息也是可以持久化保存的。
格式化日志信息
[12: 15]
在 log API 里,我们往往会添加一些应用运行时产生的原始数据。而直接阅读这些原始数据是比较困难的,所以我们可以利用 log API 里的数据格式化功能来提高数据的可读性,且格式化功能对应用的运行时没有影响。
视频中展示的范例使用 log API 记录了 taskID
、giftCardID
、serverID
和 seconds
这些原始数据。下图是未对原始数据进行格式化的日志信息:
可以看出,日志信息的可读性不佳:giftCardID
数据显示的宽度应当统一,这个宽度应为最长的 giftCardID
的长度;seconds
的精度应四舍五入至两位小数,以便比较。
对数据使用格式化选项之后,让我们再来看看:
格式化后的日志信息可读性大大提高,甚至可以直接拷贝数据到 Numbers 里,对数据作可视化分析:
除了上述提到的几个数据格式化的选项以外,还有很多其他的选项:
保护日志信息安全
当应用下载安装到用户手机上时,日志记录仍会进行。只要把有安装该应用的设备使用线缆连接到电脑上,任何人都可以查看日志信息,所以有关个人信息的日志绝对不能被标记为 .public
。
但是当我们需要比较两条非公开的信息是否一样时,这时候该怎么办呢?log API 还提供一种 equality-preserving hash 方法。在经过处理之后,不会显示真正的数据,但也能让我们判断这两条信息是否一样。
API 可用性
最新版的 log 函数可以使用字符串插值,而旧版只能使用格式占位符:
1 | let userName = "Jack" |
结语
- 使用新版 log API 有助于帮助开发者理解难以复现的 bug。
- 新版 log API 既有不俗的性能,又有丰富的数据格式化选项。
- 手中的 print() 函数突然不香了!