探索 Swift 中的日志系统

Posted 老司机技术周报

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了探索 Swift 中的日志系统相关的知识,希望对你有一定的参考价值。


Session:https://developer.apple.com/videos/play/wwdc2020/10168/

概括

这个 session 介绍了 Swift 统一 logging API 的最新版本。在这个视频中,你将学会:

  1. 在记录应用中的事件和错误的同时,保持日志信息的隐私性。

  2. 在不牺牲性能的情况下,使用功能强大又提供较好可读性的格式化数据选项。

  3. 收集和处理日志信息,以理解和调试应用中的意外行为。

在实际的开发任务中,我们难免会遇到一些难以复现的 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 函数支持很多数据类型,比如数字类型 IntDouble 、Objective-C 对象及所有遵循 CustomStringConvertible 协议的类型。当在 log 消息中加入非数字类型的数据时,非数字类型的数据默认会被系统编辑,这是为了确保应用在实机运行时,不会泄漏个人信息。

看到这里,你可能在心里发问:那当在模拟器上运行时是怎么样的呢?根据 Apple Developer Forums 上的 回答:在 Xcode console 上显示的 log 信息始终不会被系统编辑,也就是说:如果应用从 Xcode 中启动,即使这条 log 信息是非公开的,在 console 中也会完全显示出来,以便开发者调试。

举个例子,这里是在记录类型为字符串的 accountNumber 变量:

但在输出的 log 信息中,accountNumber 被编辑为 <private>

我们也可以手动控制 log 信息为公开的:

探索 Swift 中的日志系统

在终端中收集日志信息

我们可以使用命令行来收集 log 信息。先把设备连接至电脑,再在终端中输入以下命令:

探索 Swift 中的日志系统

使用 log help collect  可以获得该命令行的使用帮助。

在收集完之后,在当前路径下会产生一个以 .logarchive 结尾的文件。我们可以使用系统自带的 Console App 浏览该文件。

当在 Console App 中浏览这个文件时,常常会在搜索栏输入关键词来过滤无关的信息。这里分享一个小技巧:在搜索栏输入关键词,输入完毕后,点击右下角的保存按钮,可以将关键词保存起来。

探索 Swift 中的日志系统
探索 Swift 中的日志系统
探索 Swift 中的日志系统

<<< 左右滑动见更多 >>>

下次搜索时,直接点击 log1 按钮,就自动填充搜索关键词,这样就可以免于重复输入搜索关键字。

在 Console App 中实时检视日志信息

使用 log collect 命令,我们可以在应用运行结束后,打开日志文件,阅读日志信息。在应用运行时,我们可以在 Xcode console 阅读日志信息。类似地,在应用运行时(无论是在真机上运行还是在模拟器上运行),我们也可以实时地在 Console App 里阅读日志信息!

只要在 Console App 内选中正确的设备即可(注:这里的设备包括真机和模拟器):

探索 Swift 中的日志系统

但在我自己动手尝试之后发现:即使在 Console App 的 Action 菜单里勾选 Include Debug Messages 选项,在 Xcode 中以模拟器的方式启动应用,Console App 里实时显示的日志信息竟然不包含 debug 等级的信息。然而,在 Xcode 中以真机的方式启动应用,Console App 里实时显示的日志信息却包含了 debug 等级的信息!

这可能是个 bug。我已经在 Apple Developer Forums 提交了 反馈。

日志等级

系统提供五种不同的日志记录等级:

  1. Debug level;在 debug 时使用

  2. Info level;在排查问题时辅助使用

  3. Notice level;也是默认日志记录等级,在排查问题时使用

  4. Error;在程序执行出错时使用

  5. Fault;在程序出现 bug 时使用

日志消息持久化

当我们要在应用运行之后读取日志时,只有那些可以持久化的日志消息才会被读取到。日志消息是否持久保存取决于日志记录等级。

探索 Swift 中的日志系统

经过我自己的测试,如果在 Console App 的 Action 菜单里勾选 Include Debug Messages 选项,在 .logarchive 文件里也能阅读到 debug 等级的日志信息,也就是说:在这种条件下,debug 等级的日志信息也是可以持久化保存的。

日志记录等级各自的性能也有差异:

探索 Swift 中的日志系统

处于 Debug 的日志记录等级的运行速度是很快的,所以在此等级的 log 函数内调用比较耗时的函数是安全的。

探索 Swift 中的日志系统

格式化日志信息

在 log API 里,我们往往会添加一些应用运行时产生的原始数据。而直接阅读这些原始数据是比较困难的,所以我们可以利用 log API 里的数据格式化功能来提高数据的可读性,且格式化功能对应用的运行时没有影响。

探索 Swift 中的日志系统

视频中展示的范例使用 log API 记录了 taskIDgiftCardIDserverIDseconds 这些原始数据。下图是未对原始数据进行格式化的日志信息:

探索 Swift 中的日志系统

可以看出,日志信息的可读性不佳:giftCardID 数据显示的宽度应当统一,这个宽度应为最长的 giftCardID 的长度;seconds 的精度应四舍五入至两位小数,以便比较。

探索 Swift 中的日志系统

对数据使用格式化选项之后,让我们再来看看:

探索 Swift 中的日志系统

格式化后的日志信息可读性大大提高,甚至可以直接拷贝数据到 Numbers 里,对数据作可视化分析:

探索 Swift 中的日志系统

除了上述提到的几个数据格式化的选项以外,还有很多其他的选项:

探索 Swift 中的日志系统

保护日志信息安全

当应用下载安装到用户手机上时,日志记录仍会进行。只要把有安装该应用的设备使用线缆连接到电脑上,任何人都可以查看日志信息,所以有关个人信息的日志绝对不能被标记为 .public

但是当我们需要比较两条非公开的信息是否一样时,这时候该怎么办呢?log API 还提供一种 equality-preserving hash 方法。在经过处理之后,不会显示真正的数据,但也能让我们判断这两条信息是否一样。

探索 Swift 中的日志系统

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 中的日志系统的主要内容,如果未能解决你的问题,请参考以下文章

探索Java的日志世界

swift常用代码片段

iOS Swift 中的 Android 片段模拟

swift 代码片段

如何将这个 Objective-C 代码片段写入 Swift?

如何使用 Swift 使用此代码片段为 iOS 应用程序初始化 SDK?