探索 Swift 中的日志系统
Posted 老司机技术周报
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了探索 Swift 中的日志系统相关的知识,希望对你有一定的参考价值。
Session:https://developer.apple.com/videos/play/wwdc2020/10168/
概括
这个 session 介绍了 Swift 统一 logging API 的最新版本。在这个视频中,你将学会:
-
在记录应用中的事件和错误的同时,保持日志信息的隐私性。
-
在不牺牲性能的情况下,使用功能强大又提供较好可读性的格式化数据选项。
-
收集和处理日志信息,以理解和调试应用中的意外行为。
在实际的开发任务中,我们难免会遇到一些难以复现的 bug。使用 log API 能帮助我们理解及处理这类疑难杂症。我们可以在日志记录的众多线索中理解 bug 产生的原因。
来看新版 log API
我们可以利用 log API 在应用运行时记录下重要的事件。记录下的日志会被操作系统存档,以方便我们之后读取这些日志。由于新版 log API 十分高效,它们可以在应用中广泛地使用,且不会拖慢应用的运行速度。仅需三步,就能在应用中使用 log API:
// 第一步:导入 os 模块
import os
// 第二步:创建 Logger 的实例
// subsystem 一般为 bundle identifier,以便从众多日志中辨别出:哪些是来自该应用的信息
// category 一般表示应用的某个模块,以便进一步辨别出:是来自该应用哪一模块的信息
let logger = Logger(subsystem: "com.example.Fruta", category: "giftcards")
func beginTask(url: URL, handler: (Data) -> Void) {
launchTask(with: url) {
handler($0)
}
// 第三步:使用 log 函数
logger.log("Started a task (taskId)")
}
调用 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 时使用
日志消息持久化
当我们要在应用运行之后读取日志时,只有那些可以持久化的日志消息才会被读取到。日志消息是否持久保存取决于日志记录等级。
经过我自己的测试,如果在 Console App 的 Action 菜单里勾选 Include Debug Messages 选项,在 .logarchive 文件里也能阅读到 debug 等级的日志信息,也就是说:在这种条件下,debug 等级的日志信息也是可以持久化保存的。
日志记录等级各自的性能也有差异:
处于 Debug 的日志记录等级的运行速度是很快的,所以在此等级的 log 函数内调用比较耗时的函数是安全的。
格式化日志信息
在 log API 里,我们往往会添加一些应用运行时产生的原始数据。而直接阅读这些原始数据是比较困难的,所以我们可以利用 log API 里的数据格式化功能来提高数据的可读性,且格式化功能对应用的运行时没有影响。
视频中展示的范例使用 log API 记录了 taskID
、giftCardID
、serverID
和 seconds
这些原始数据。下图是未对原始数据进行格式化的日志信息:
可以看出,日志信息的可读性不佳:giftCardID
数据显示的宽度应当统一,这个宽度应为最长的 giftCardID
的长度;seconds
的精度应四舍五入至两位小数,以便比较。
对数据使用格式化选项之后,让我们再来看看:
格式化后的日志信息可读性大大提高,甚至可以直接拷贝数据到 Numbers 里,对数据作可视化分析:
除了上述提到的几个数据格式化的选项以外,还有很多其他的选项:
保护日志信息安全
当应用下载安装到用户手机上时,日志记录仍会进行。只要把有安装该应用的设备使用线缆连接到电脑上,任何人都可以查看日志信息,所以有关个人信息的日志绝对不能被标记为 .public
。
但是当我们需要比较两条非公开的信息是否一样时,这时候该怎么办呢?log API 还提供一种 equality-preserving hash 方法。在经过处理之后,不会显示真正的数据,但也能让我们判断这两条信息是否一样。
API 可用性
最新版的 log 函数可以使用字符串插值,而旧版只能使用格式占位符:
let userName = "Jack"
let userID = "15030225"
let balance = 123
override func viewDidLoad() {
super.viewDidLoad()
// 旧版的写法
os_log("balance: %{private}d", log: .demo, type: .info, balance)
os_log("user: %{private}@", log: .demo, type: .info, userName)
}
extension OSLog {
static let demo = OSLog(subsystem: "demo", category: "sunset")
}
结语
-
使用新版 log API 有助于帮助开发者理解难以复现的 bug。
-
新版 log API 既有不俗的性能,又有丰富的数据格式化选项。
-
手中的 print() 函数突然不香了!
推荐阅读
关注我们
我们是「老司机技术周报」,每周会发布一份关于 ios 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。
支持作者
WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。
以上是关于探索 Swift 中的日志系统的主要内容,如果未能解决你的问题,请参考以下文章