
概述
在上篇 Swift 中更现代化的调试日志系统趣谈(一) 博文中,我们初步讨论了如何利用 Swift OSLog 框架中的 Logger 日志记录器替代"简陋"的 print 方法来记录我们的调试消息。

而接下来我们会再接再厉,继续调试日志系统的进一步介绍。
在本篇博文中,您将学到如下内容:
- 在 Xcode 外部访问日志
- 原汤化原食:App 自身访问日志
- 日志与隐私
相信本篇博文中能为本系列文章画上一个圆满的句号,也会让小伙伴们未来的调试之路前程似锦、妙趣横生!
那还等什么呢?让我们来继续 Swift 现代化调试之旅吧!
Let's go!!!;)
4. 在 Xcode 外部访问日志
我们有很多方法在不运行 Xcode 的情况下读取日志消息,其中一个最常用的就是使用控制台(Console)App。
通过在 Mac 上运行控制台应用,我们可以连接 iPhone 等移动设备查看实时的调试日志消息。这自然也包括我们自己开发 App 发出的消息:

如上图所示,控制台应用提供了海量过滤选项来确保我们仅专注自己感兴趣的 App。
在调试涉及后台启动和下载的代码时,Console 应用程序的日志记录是非常可贵的。因为我们将会关闭自己的 App,并强制其内存不足(同时分离 Detach 调试器),以便可以查看是否在正确时间使用预期值调用了所有的代理方法。
除此之外,能够将手机连接 Mac 同时打开控制台并浏览应用程序的日志也非常有用。在秃头码农邋遢的卧室里,这使得我们可以在其它设备上进行一些粗略的预调试,而不必劳神费时地从 Xcode 直接构建 App 并传送到这些设备上启动运行。

如上图所示:使用控制台应用我们还能快速的选择监控日志源头的不同设备,并且在内置的各种日志种类里分门别类的仔细探查所需的全部调试消息。
5. 原汤化原食:App 自身访问日志
如果大家希望能够接收来自用户的日志,以便让调试具有日志消息完全的访问权限,则可以在自己的 App 中实现日志查看器。
我们可以从 OSLog 的存储中检索日志记录,并使用 OSLogStore 类来获取日志消息。
例如,下面是一个简单的 SwiftUI 视图,它能够获取属于我们自己 App 创建日志子系统的所有消息:
swift
import Foundation
import OSLog
import SwiftUI
struct LogsViewer: View {
let logs: [OSLogEntryLog]
init() {
let logStore = try! OSLogStore(scope: .currentProcessIdentifier)
self.logs = try! logStore.getEntries().compactMap { entry in
guard let logEntry = entry as? OSLogEntryLog,
logEntry.subsystem.starts(with: "com.donnywals") == true else {
return nil
}
return logEntry
}
}
var body: some View {
List(logs, id: \.self) { log in
VStack(alignment: .leading) {
Text(log.composedMessage)
HStack {
Text(log.subsystem)
Text(log.date, format: .dateTime)
}.bold()
}
}
}
}
从上面的代码可以看到,虽然这是一个非常简单的视图,但它确实可以帮助我们很 Easy 地获取存储的日志消息。
我们可以将这样的视图添加到应用程序中,并使用一个选项来扩展它以导出包含所有日志的 JSON 文件(基于我们自己的Codable 模型),这样就可以轻松地从用户那里获取所需的日志了。
6. 日志与隐私
何曾几时,小伙伴们可能会记录被认为是隐私敏感的信息,即使我们实际并不需要此信息来调试应用程序。比如当我们正在收集设备上非必要且敏感的个人登录信息时,混淆这些信息的内容是一个绝好的主意。
默认情况下,当我们将变量插入字符串时这些变量将被视为应该"涂抹(redacted)"的数据。下面是一个示例:
swift
appLogger.log(level: .default, "Hello, world! \(accessToken)")
在上面的代码中,我们试图在日志消息中显示访问令牌。当我们挂载调试器运行 App 时所有日志内容都会以明文显示,这意味着上述访问令牌会在众目睽睽之下"原形毕露"。
然而,当我们撤除(disconnect)附着的调试器运行应用时,我们控制台中日志的显示就会像下面这个样子:
swift
Hello, world! <private>
已添加到日志中的变量将被涂抹以保护用户隐私。如果我们认为被插入的信息是非隐私敏感信息,则可以显式将其标记为 public,就像下面这样:
swift
appLogger.log(level: .default, "Background status: \(newStatus, privacy: .public)")
值得注意的是,在未附着调试器时是否记录日志消息取决于我们使用的日志级别。
默认日志级别的消息将被留存下来(持久化),并且当非调试状态时在 Console 应用程序中是可见的。然而,调试(debug)和信息(info)日志级别仅在附加调试器时才会显示。
当大家希望确保即使未连接调试器也可以看到日志消息时,其它两个非常有用的日志级别是错误(error)和故障(fault)。
如果我们仅仅希望能够判断隐私敏感信息(privacy sensitive information)在整个应用程序运行中是否保持不变,则可以要求日志记录器为隐私敏感值创建哈希。这允许我们确保数据的一致性,且无需实际知道正在记录的内容。
我们可以按以下方式执行此操作:
swift
appLogger.log(level: .default, "Hello, world! \(accessToken, privacy: .private(mask: .hash))")
这样一来,我们就可以通过访问令牌的 hash 值来确定它是否发生过改变,且无需知道访问令牌实际的值了。是不是很实用呢?棒棒哒!💯
总结
能够调试和评测(profile)应用程序对于 App 的成功至关重要。日志记录是一种"价值连城"的神兵利器,大家可以在开发应用程序时使用它们来取代标准的 print 调用。
我们强烈建议小伙伴们现在就开始尝试日志记录,将 print 语句替换为调试(debug)级别的日志记录,以便能够在 Xcode 和 macOS 控制台中使用更好的过滤和搜索。
不要忘记了,我们可以为应用程序的不同部分创建多个 Logger 对象,并按子系统和类别对它们进行标明,这会使调试和跟踪日志变得更加小菜一碟!
感谢观赏,再会!8-)