CleanroomLogger 提供了一个可扩展的、基于 Swift 的日志 API,它简单、轻量且性能出色。
CleanroomLogger 提供的 API 设计得很容易被熟悉诸如 CocoaLumberjack 和 log4j 之类软件包的人理解。
CleanroomLogger 是来自 Gilt Tech 的 Cleanroom Project 的一部分。
这是 master
分支。它使用 Swift 4.1 并且 需要 Xcode 9.3 才能编译。
分支 | 构建状态 |
---|---|
master |
你无需在流畅滚动和收集有意义的日志信息之间做出选择。CleanroomLogger 在调用线程上做的工作非常少,因此它可以尽快回到正轨。
CleanroomLogger 利用了 Apple 新的 统一日志系统 (又名 “OSLog” 或 “os_log”),当运行在 iOS 10.0、macOS 10.12、tvOS 10.0、watchOS 3.0 或更高版本上时。
在 OSLog 不可用的系统上,CleanroomLogger 会优雅地自动回退到其他标准输出机制。
良好的文档对于任何开源框架的实用性都至关重要。除了你在下面找到的广泛的高级文档之外,CleanroomLogger API 本身也是 100% 文档化的。
消息被分配为五个 严重性级别 之一:最严重的是 error,其次是 warning、info、debug 和 verbose,这是最不严重的。 了解消息的严重性可以让你执行额外的过滤; 例如,为了最大限度地减少在 App Store 二进制文件中记录日志的开销,你可以选择仅记录发布版本中的警告和错误。
在 Xcode 控制台中快速发现运行时的问题,日志消息会按严重程度进行颜色编码
◽️ Verbose messages are tagged with a small gray square — easy to ignore
◾️ Debug messages have a black square; easier to spot, but still de-emphasized
🔷 Info messages add a splash of color in the form of a blue diamond
🔶 Warnings are highlighted with a fire-orange diamond
❌ Error messages stand out with a big red X — hard to miss!
内置了对标准 UNIX 输出流的支持。 使用 StandardOutputLogRecorder
和 StandardErrorLogRecorder
将输出定向到 stdout
和 stderr
,分别。
或者,使用 StandardStreamsLogRecorder
将 verbose、debug 和 info 消息发送到 stdout
,同时将 warnings 和 errors 发送到 stderr
。
当 Xcode 8 被引入时,控制台窗格变得更加冗长。 这是由于 ASL facility 被 OSLog 替换所致。 为了消除额外的冗余,开发者发现 将 OS_ACTIVITY_MODE
环境变量设置为 “disable
” 会恢复到旧的日志记录行为。 事实证明,这完全静默了 OSLog,因此不会将任何输出发送到控制台窗格。 CleanroomLogger 会注意到该设置何时存在,并在通过 os_log()
函数记录消息之外,还会将消息回显到 stdout
或 stderr
。
如果你只是在到处使用 print()
或 NSLog()
,有时很难弄清楚哪个代码负责哪些日志消息。 默认情况下,CleanroomLogger 会输出负责发出每个日志消息的源文件和行号,因此你可以直接找到源头
🔶 AppleTart.framework didn’t load due to running on iOS 8 (AppleTartShim.swift:19)
◾️ Uploaded tapstream batch (TapstreamTracker.swift:166)
◽️ Presenting AccountNavigationController from SaleListingController (BackstopDeepLinkNavigator.swift:174)
🔷 Successfully navigated to .account for URL: gilt://account (DeepLinkConsoleOutput.swift:104)
❌ Unrecognized URL: CountrySelector (GiltOnTheGoDeepLinkRouter.swift:100)
CleanroomLogger 提供了 简单的基于文件的日志记录 支持,以及 自我修剪的旋转日志目录 实现。
开发者经常使用日志记录来执行跟踪。 无需编写大量不同的日志消息来弄清楚你的程序在运行时正在做什么,只需在你的源代码中散布 Log.debug?.trace()
和 Log.verbose?.trace()
调用,你就可以准确地看到你的代码命中了哪些行,何时命中,以及在哪个线程上命中,以及正在执行的函数的签名
2017-01-05 13:46:16.681 -05:00 | 0001AEC4 ◾️ —> StoreDataTransaction.swift:42 - executeTransaction()
2017-01-05 13:46:16.683 -05:00 | 00071095 ◾️ —> LegacyStoresDeepLinking.swift:210 - viewControllerForRouter(_:destination:)
2017-01-05 13:46:16.683 -05:00 | 0001AEC4 ◽️ —> StoreDataTransaction.swift:97 - executeTransaction(completion:)
2017-01-05 13:46:16.684 -05:00 | 00071095 ◾️ —> ContainerViewController.swift:132 - setContentViewController(_:animated:completion:)
2017-01-05 13:46:16.684 -05:00 | 00071095 ◾️ —> DefaultBackstopDeepLinkNavigator.swift:53 - navigate(to:via:using:viewController:displayOptions:completion:)
2017-01-05 13:46:16.687 -05:00 | 00071095 ◽️ —> ViewControllerBase.swift:79 - viewWillAppear
CleanroomLogger 附带了两个通用的日志格式化器:ReadableLogFormatter
对于人工阅读很方便,而 ParsableLogFormatter
对于机器处理很有用。 两者都可以通过初始化程序进行自定义。
使用 ReadableLogFormatter()
构造的格式化器会产生如下所示的日志输出
2017-01-06 02:06:53.679 -05:00 | Debug | 001BEF88 | DeepLinkRouterImpl.swift:132 - displayOptions(for:via:displaying:)
2017-01-06 02:06:53.682 -05:00 | Verbose | 001BEF88 | UIWindowViewControllerExtension.swift:133 - rootTabBarController: nil
2017-01-06 02:06:53.683 -05:00 | Info | 001BEF88 | DeepLinkConsoleOutput.swift:104 - Successfully navigated to storeSale for URL: gilt://sale/women/winter-skin-rescue
2017-01-06 02:07:01.761 -05:00 | Error | 001BEF88 | Checkout.swift:302 - The user transaction failed
2017-01-06 02:07:02.397 -05:00 | Warning | 001BEF88 | MemoryCache.swift:233 - Caching is temporarily disabled due to a recent memory warning
当相同的日志消息由使用 ParsableLogFormatter()
构造的格式化器处理时,时间戳以 UNIX 格式输出,制表符用作字段分隔符,并且严重性以数字方式指示
1483686413.67946 2 001BEF88 DeepLinkRouterImpl.swift:132 - displayOptions(for:via:displaying:)
1483686413.68170 1 001BEF88 UIWindowViewControllerExtension.swift:133 - rootTabBarController: nil
1483686413.68342 3 001BEF88 DeepLinkConsoleOutput.swift:104 - Successfully navigated to storeSale for URL: gilt://sale/women/winter-skin-rescue
1483686421.76101 5 001BEF88 Checkout.swift:302 - The user transaction failed
1483686422.39651 4 001BEF88 MemoryCache.swift:233 - Caching is temporarily disabled due to a recent memory warning
如果内置格式化器不符合要求,你可以使用 FieldBasedLogFormatter
来组装几乎任何可能的日志格式。
假设你想要一个日志格式化器,其时间戳采用 ISO 8601 日期格式,一个制表符,调用站点的源文件和行号,然后是严重性,以大写字符串显示,并在 8 个字符的字段中右对齐,然后是一个冒号和一个空格,最后是日志条目的有效负载。 你可以通过如下构造 FieldBasedLogFormatter
来做到这一点
FieldBasedLogFormatter(fields: [
.timestamp(.custom("yyyy-MM-dd'T'HH:mm:ss.SSSZ")),
.delimiter(.tab),
.callSite,
.severity(.custom(textRepresentation: .uppercase, truncateAtWidth: nil, padToWidth: 8, rightAlign: true)),
.literal(": "),
.payload])
生成的输出将如下所示
2017-01-08T12:55:17.905-0500 DeepLinkRouterImpl.swift:207 DEBUG: destinationForURL
2017-01-08T12:55:20.716-0500 DefaultDeepLinkRouter.swift:95 INFO: Attempting navigation to storeSale
2017-01-08T12:55:21.995-0500 LegacyUserEnvironment.swift:109 ERROR: Can’t fetch user profile without user guid
2017-01-08T12:55:25.960-0500 DeepLinkConsoleOutput.swift:104 WARNING: Can’t find storeProduct for URL
2017-01-08T12:55:33.457-0500 ProductViewController.swift:92 VERBOSE: deinit
CleanroomLogger 公开了三个主要扩展点,用于实现你自己的自定义逻辑
LogRecorder
s 用于记录格式化的日志消息。 通常,这涉及将消息写入某种流或数据存储。 你可以提供你自己的 LogRecorder
实现来利用 CleanroomLogger 本机不支持的功能:例如,将消息存储在数据库表中或将其发送到远程 HTTP 端点。LogFormatter
s 用于生成要记录的每个 LogEntry
的文本表示。LogFilter
s 有机会在 LogEntry
传递给 LogRecorder
之前检查(并可能拒绝)它。CleanroomLogger 在 MIT 许可证 下分发。
CleanroomLogger 以“按原样”的方式免费提供给你使用。 我们不作任何保证、承诺或道歉。 Caveat developer.
集成 CleanroomLogger 最简单的方法是使用 Carthage 依赖管理工具。
首先,将此行添加到你的 Cartfile
github "emaloney/CleanroomLogger" ~> 6.0.0
然后,使用 carthage
命令 更新你的依赖项。
最后,你需要 将 CleanroomLogger 集成到你的项目中 才能使用它提供的 API。
成功集成后,只需将以下语句添加到要使用 CleanroomLogger 的任何 Swift 文件中
import CleanroomLogger
有关将 CleanroomLogger 集成到你的项目中的其他详细信息,请参阅 集成文档。
CleanroomLogger 的主要公共 API 由 Log
提供。
Log
维护五个静态只读 LogChannel
属性,它们对应于五个严重性级别之一,指示通过该通道发送的消息的重要性。 发送消息时,你将选择适合该消息的严重性,并使用相应的通道
Log.error
— 最高严重性; 出现问题,并且可能即将发生致命错误Log.warning
— 似乎有些不对劲,可能需要在出现更大的问题之前进行调查Log.info
— 发生了一些值得注意的事情,但没什么可担心的Log.debug
— 用于调试和诊断信息(不适用于生产代码)Log.verbose
- 最低严重性; 用于详细的或频繁发生的调试和诊断信息(不适用于生产代码)每个 LogChannel
都提供三个函数来记录日志消息
trace()
— 此函数记录一条带有程序执行跟踪信息的日志消息,包括源代码文件名、行号和调用函数的名称。message(String)
— 此函数记录传递给它的日志消息。value(Any?)
— 此函数尝试记录一条包含传递给它的可选 Any
值的字符串表示形式的日志消息。默认情况下,日志记录是禁用的,这意味着 Log
的所有通道都没有被填充。 因此,它们具有 nil
值,并且任何执行日志记录的尝试都将静默失败。
为了使用 CleanroomLogger,您必须显式启用日志记录,这可以通过调用 Log.enable()
函数之一来完成。
理想情况下,日志记录应在应用程序启动周期的第一个可能点启用。 否则,由于记录器尚未初始化,因此可能会在启动期间错过关键日志消息。
放置 Log.enable()
调用的最佳位置是在应用程序委托的 init()
的第一行。
如果您由于某些原因不想这样做,那么下一个最佳位置是在应用程序委托的 application(_:willFinishLaunchingWithOptions:)
函数中。 您会注意到我们特别推荐 will
函数,而不是典型的 did
,因为前者在应用程序启动周期中更早被调用。
注意: 在应用程序进程的运行生命周期中,只有第一次调用
Log.enable()
函数才会生效。 所有后续调用都会被静默忽略。 您还可以通过调用Log.neverEnable()
来完全阻止启用 CleanroomLogger。
要在日志中记录项目,只需选择适当的通道并调用适当的函数。
以下是一些示例
假设您的应用程序刚刚完成启动。 这是一个重要的事件,但它不是错误。 您可能还希望在生产应用程序日志中看到此信息。 因此,您确定适当的 LogSeverity
是 .info
,并且您选择相应的 LogChannel
,即 Log.info
。 然后,要记录消息,只需调用通道的 message()
函数
Log.info?.message("The application has finished launching.")
如果您正在处理一些代码,并且对执行顺序感到好奇,您可以散布一些 trace()
调用。
此函数输出文件名、行号和调用函数的名称。
例如,如果您将以下代码放在 ModularTable.swift 文件的第 364 行,该文件位于具有签名 tableView(_:cellForRowAt:)
的函数中
Log.debug?.trace()
假设为 .debug
严重性启用了日志记录,则在执行该行时将记录以下消息
ModularTable.swift:364 — tableView(_:cellForRowAt:)
value()
函数可用于输出有关特定值的信息。 该函数采用 Any?
类型的参数,旨在接受任何有效的运行时值。
例如,您可能想要输出传递给 UITableViewDataSource
的 tableView(_:cellForRowAt:)
函数的 IndexPath
值
Log.verbose?.value(indexPath)
这将导致如下所示的输出
= IndexPath: [0, 2]
该函数还处理可选值
var str: String?
Log.verbose?.value(str)
此函数的输出将是
= nil
本节深入探讨了配置和自定义 CleanroomLogger 以满足您需求的具体细节。
当调用 Log.enable()
函数变体之一时,将配置 CleanroomLogger。 配置最多可以在运行进程的生命周期内进行一次。 一旦设置,配置就无法更改;它是不可变的。
LogConfiguration
协议表示可用于配置 CleanroomLogger 的机制。 LogConfiguration
允许将相关设置和行为封装在单个实体中,并且可以使用多个 LogConfiguration
实例配置 CleanroomLogger 以允许组合行为。
每个 LogConfiguration
指定
minimumSeverity
,一个 LogSeverity
值,它确定要记录哪些日志条目。 任何 LogEntry
的 severity
小于配置的 mimimumSeverity
都不会传递给该配置指定的任何 LogRecorder
。LogFilter
的数组。 每个 LogFilter
都有机会导致给定的日志条目被忽略。synchronousMode
属性,它确定在处理给定配置的日志条目时是否应使用同步日志记录。 此功能旨在在调试期间使用,不建议用于生产代码。LogConfiguration
。 出于组织目的,每个 LogConfiguration
可以依次包含其他 LogConfiguration
。 但是,层次结构没有意义,并且在配置时会被展平。LogRecorder
的数组,这些记录器将用于将日志条目写入底层日志记录工具。 如果配置没有 LogRecorder
,则假定它仅是其他 LogConfiguration
的容器,并且在展平配置层次结构时会被忽略。当 CleanroomLogger 收到记录某些内容的请求时,会选择零个或多个 LogConfiguration
来处理该请求
LogEntry
的 severity
与每个 LogConfiguration
的 minimumSeverity
进行比较。 将选择任何 minimumSeverity
等于或小于 LogEntry
的 severity
的 LogConfiguration
以供进一步考虑。LogEntry
依次传递给每个 LogConfiguration
的 filters
的 shouldRecord(entry:)
函数。 如果任何 LogFilter
返回 false
,则不会选择关联的配置来记录该日志条目。XcodeLogConfiguration
非常适合在开发期间进行实时查看,它会检查运行时环境以优化 CleanroomLogger 在 Xcode 中的使用。
XcodeLogConfiguration
考虑了
新的统一日志记录系统(也称为“OSLog”)是否可用; 它仅在 iOS 10.0、macOS 10.12、tvOS 10.0 和 watchOS 3.0 中可用。 默认情况下,如果统一日志记录不可用,日志记录将回退到 stdout
和 stderr
。
OS_ACTIVITY_MODE
环境变量的值; 当它设置为“disable
”时,尝试通过 OSLog 记录日志将被静默忽略。 在这种情况下,日志输出会回显到 stdout
和 stderr
,以确保消息在 Xcode 中可见。
消息的 severity
。 为了实现 UNIX 友好的行为,.verbose
、.debug
和 .info
消息被定向到运行进程的 stdout
流,而 .warning
和 .error
消息被发送到 stderr
。
使用统一日志记录系统时,Xcode 控制台中的消息会带有如下所示的信息头前缀
2017-01-04 22:56:47.448224 Gilt[5031:89847] [CleanroomLogger]
2017-01-04 22:56:47.448718 Gilt[5031:89847] [CleanroomLogger]
2017-01-04 22:56:47.449487 Gilt[5031:89847] [CleanroomLogger]
2017-01-04 22:56:47.450127 Gilt[5031:89847] [CleanroomLogger]
2017-01-04 22:56:47.450722 Gilt[5031:89847] [CleanroomLogger]
此标头不是由 CleanroomLogger 添加的; 它是由于在 Xcode 中使用 OSLog 而添加的。 它显示日志条目的时间戳,后跟进程名称、进程 ID、调用线程 ID 和日志记录系统名称。
为了确保跨平台的一致输出,即使记录到 stdout
和 stderr
,XcodeLogConfiguration
也会模仿此标头。 您可以通过将 false
作为 mimicOSLogOutput
参数传递来禁用此行为。 禁用后,将使用更简洁的标头,仅显示时间戳和调用线程 ID
2017-01-04 23:46:17.225 -05:00 | 00071095
2017-01-04 23:46:17.227 -05:00 | 00071095
2017-01-04 23:46:17.227 -05:00 | 000716CA
2017-01-04 23:46:17.228 -05:00 | 000716CA
2017-01-04 23:46:17.258 -05:00 | 00071095
为了更容易在运行时快速识别重要的日志消息,XcodeLogConfiguration
使用了 XcodeLogFormatter
,它嵌入了每条消息严重性的颜色编码表示形式
◽️ Verbose messages are tagged with a small gray square — easy to ignore
◾️ Debug messages have a black square; easier to spot, but still de-emphasized
🔷 Info messages add a splash of color in the form of a blue diamond
🔶 Warnings are highlighted with a fire-orange diamond
❌ Error messages stand out with a big red X — hard to miss!
使用 XcodeLogConfiguration
启用 CleanroomLogger 的最简单方法是调用
Log.enable()
感谢默认参数值的魔力,这等效于以下 Log.enable()
调用
Log.enable(minimumSeverity: .info,
debugMode: false,
verboseDebugMode: false,
stdStreamsMode: .useAsFallback,
mimicOSLogOutput: true,
showCallSite: true,
filters: [])
这将使用带有 默认设置的 XcodeLogConfiguration
配置 CleanroomLogger。
注意: 如果
debugMode
或verboseDebugMode
为true
,则XcodeLogConfiguration
将在synchronousMode
中使用,不建议在生产代码中使用。
上面的调用也等同于
Log.enable(configuration: XcodeLogConfiguration())
可以使用 RotatingLogFileConfiguration
来维护一个每日轮换的日志文件目录。
警告: 由
RotatingLogFileConfiguration
创建的RotatingLogFileRecorder
假定对日志目录具有完全控制权。 任何未被识别为活动日志文件的文件将在自动清理过程中被删除,该过程可能随时发生。 这意味着如果您不注意传递的directoryPath
,您可能会丢失有价值的数据!
至少,RotatingLogFileConfiguration
要求您指定日志记录的 minimumSeverity
,保留日志文件的天数以及存储这些文件的目录
// logDir is a String holding the filesystem path to the log directory
let rotatingConf = RotatingLogFileConfiguration(minimumSeverity: .info,
daysToKeep: 7,
directoryPath: logDir)
Log.enable(configuration: rotatingConf)
上面的代码将记录严重性为 .info
或更高的任何日志条目,该文件将至少保留 7 天,然后进行清理。 此特定配置使用 ReadableLogFormatter
来格式化日志条目。
RotatingLogFileConfiguration
还可以用于指定 synchronousMode
,要应用的一组 LogFilter
以及一个或多个自定义 LogFormatter
。
CleanroomLogger 还支持多个配置,允许同时使用不同的日志记录行为。
每当记录消息时,都会分别查询每个 LogConfiguration
,并有机会处理该消息。 通过提供 minimumSeverity
和唯一的 LogFilter
集,每个配置都可以指定其自己的逻辑来筛选掉不需要的消息。 然后,将幸存的消息传递给配置的 LogFormatter
,依次传递,直到一个返回非 nil
字符串。 该字符串(格式化的日志消息)最终传递给一个或多个 LogRecorder
,以写入某些底层日志记录工具。
请注意,每个配置都是一个独立的实体。 给定
LogConfiguration
的任何设置、行为或操作都不会影响任何其他配置。
有关其工作方式的示例,请想象一下将调试模式 XcodeLogConfiguration
添加到上面声明的 rotatingConf
。 您可以通过编写以下代码来做到这一点
Log.enable(configuration: [XcodeLogConfiguration(debugMode: true), rotatingConf])
在此示例中,当每次进行日志记录调用时,都会查询 XcodeLogConfiguration
和 RotatingLogFileConfiguration
。 由于 XcodeLogConfiguration
是使用 debugMode: true
声明的,因此它将在 synchronousMode
中运行,而 rotatingConf
将异步运行。
此外,XcodeLogConfiguration
将导致消息通过统一日志系统(如果可用)和/或正在运行的进程的 stdout
和 stderr
流进行记录。 另一方面,RotatingLogFileConfiguration
导致消息写入文件。
最后,每个配置都会导致使用不同的消息格式。
虽然您可以提供 LogConfiguration
协议的自己的实现,但创建 BasicLogConfiguration
实例并将相关参数传递给 初始化程序可能会更简单。
如果您想进一步封装您的配置,您也可以继承 BasicLogConfiguration
。
假设您要配置 CleanroomLogger 以
.verbose
、.debug
和 .info
消息打印到 stdout
,同时将 .warning
和 .error
消息定向到 stderr
/tmp/CleanroomLogger
创建一个轮换的日志文件目录,以存储 .info
、.warning
和 .error
消息最多 15 天此外,您希望每个日志条目的格式都不同
stdout
和 stderr
的 XcodeLogFormatter
ReadableLogFormatter
ParsableLogFormatter
要配置 CleanroomLogger 来执行所有这些操作,您可以编写
var configs = [LogConfiguration]()
// create a recorder for logging to stdout & stderr
// and add a configuration that references it
let stderr = StandardStreamsLogRecorder(formatters: [XcodeLogFormatter()])
configs.append(BasicLogConfiguration(recorders: [stderr]))
// create a recorder for logging via OSLog (if possible)
// and add a configuration that references it
if let osLog = OSLogRecorder(formatters: [ReadableLogFormatter()]) {
// the OSLogRecorder initializer will fail if running on
// a platform that doesn’t support the os_log() function
configs.append(BasicLogConfiguration(recorders: [osLog]))
}
// create a configuration for a 15-day rotating log directory
let fileCfg = RotatingLogFileConfiguration(minimumSeverity: .info,
daysToKeep: 15,
directoryPath: "/tmp/CleanroomLogger",
formatters: [ParsableLogFormatter()])
// crash if the log directory doesn’t exist yet & can’t be created
try! fileCfg.createLogDirectory()
configs.append(fileCfg)
// enable logging using the LogRecorders created above
Log.enable(configuration: configs)
尝试将 LogEntry
转换为字符串时,会查询 LogFormatter
协议。
CleanroomLogger 附带了几个用于特定目的的高级 LogFormatter
实现
XcodeLogFormatter
— 针对在 Xcode 中实时查看日志流进行了优化。 默认情况下由 XcodeLogConfiguration
使用。ParsableLogFormatter
— 非常适合旨在由其他进程解析的日志。ReadableLogFormatter
— 非常适合旨在由人类读取的日志。后两个 LogFormatter
都是 StandardLogFormatter
的子类,它提供了一种用于自定义格式化行为的基本机制。
您还可以使用 FieldBasedLogFormatter
轻松组装完全自定义的格式化程序,该格式化程序允许您混合和匹配 Field
以推出自己的格式化程序。
有关使用 CleanroomLogger 的详细信息,请参见 API 文档。
应用程序开发人员应完全控制整个过程的日志记录。 与执行的任何代码一样,日志记录需要付出一定的代价,应用程序开发人员应该决定如何在收集日志的效用与以给定的详细程度收集日志的代价之间进行权衡。
CleanroomLogger 的配置只能在应用程序的生命周期内设置一次;之后,该配置将变为不可变的。 使用 CleanroomLogger 的任何第三方框架都将仅限于应用程序开发人员明确允许的内容。 因此,使用 CleanroomLogger 的嵌入式代码本质上是表现良好的。
我们非常坚信这种理念,以至于我们甚至为从不希望在其应用程序中使用 CleanroomLogger 的开发人员构建了一项功能。 是的,我们创建了一种让开发人员完全避免使用我们的项目的方法。 因此,如果您需要包含使用 CleanroomLogger 的第三方库,但您不想产生任何日志记录开销,只需调用 Log.neverEnable()
而不是 Log.enable()
。 CleanroomLogger 将被完全禁用。
尊重调用线程。 像 print()
和 NSLog()
这样的函数可以在调用线程上完成大量工作,并且当从主线程使用时,这可能会导致较低的帧速率和断断续续的滚动。
当 CleanroomLogger 被要求记录某些内容时,它会立即移交给异步后台队列以进行进一步分派,从而使调用线程可以尽快恢复工作。 每个 LogRecorder
还维护其自己的异步后台队列,该队列用于格式化日志消息并将其写入底层存储工具。 这种设计确保如果一个记录器陷入困境,它不会阻止任何其他记录器处理日志消息。
避免不必要的代码执行。 CleanroomLogger 提供的日志记录 API 利用 Swift 短路来避免在已知永远不会记录给定严重性的消息时执行代码。
例如,在以 .info
作为最小 LogSeverity
的生产代码中,严重性为 .verbose
或 .debug
的消息将始终被忽略。 在这种情况下,Log.debug
和 Log.verbose
将为 nil
,从而可以有效地对尝试使用这些非活动日志通道的任何代码进行短路。 像 Log.verbose?.trace()
和 Log.debug?.message("正在加载 URL: \(url)")
这样的代码实际上会在运行时变为空操作。 调试日志记录为您的生产版本增加了零开销,因此请不要害羞地利用它。
CleanroomLogger 旨在避免在调用线程上执行格式化或日志记录工作,而是利用 Grand Central Dispatch (GCD) 队列进行高效处理。
就执行线程而言,记录任何内容的每个请求都可能经历三个主要处理阶段
在调用线程上
调用者尝试通过调用由 Log
维护的相应 LogChannel
的日志记录函数(例如,message()
、trace()
或 value()
)来发出日志请求。 - 如果没有给定日志消息严重性的 LogChannel
(因为 CleanroomLogger 尚未 enabled()
或未配置为以该严重性记录日志),Swift 短路将阻止进一步执行。 这使得在发布生产代码时将调试日志记录调用保留在原位成为可能,而不会影响性能。
如果 LogChannel
确实存在,它将创建一个不可变的 LogEntry
结构来表示要记录的事物。
然后,将 LogEntry
传递给与 LogChannel
关联的 LogReceptacle
。
根据 LogEntry
的严重性,LogReceptacle
选择一个或多个 LogConfiguration
来用于记录消息。 除其他事项外,这些配置确定传递到底层 LogReceptacle
的 GCD 队列时,进一步的处理是同步进行还是异步进行。 (同步处理在调试期间很有用,但不建议用于常规生产代码。)
在 LogReceptacle
队列上
LogEntry
通过零个或多个 LogFilter
传递,这些 LogFilter
有机会阻止进一步处理 LogEntry
。 如果任何过滤器指示不应记录 LogEntry
,则处理停止。
LogConfiguration
用于确定将使用哪些 LogRecorder
(如果有)来记录 LogEntry
。
对于配置指定的每个 LogRecorder
实例,LogEntry
然后被分派到 LogRecorder
提供的 GCD 队列。
在每个 LogRecorder
队列上
LogEntry
会被顺序传递给 LogRecorder
提供的每一个 LogFormatter
,以便这些格式化器有机会为 LogEntry
创建格式化的消息。- 如果没有 LogFormatter
返回 LogEntry
的字符串表示,则进一步处理会停止,并且不会记录任何内容。- 如果任何 LogFormatter
返回一个非 nil
值来表示 LogEntry
的格式化消息,则该字符串会被传递给 LogRecorder
以进行最终的日志记录。
Cleanroom 项目最初是一个实验,旨在用一个没有历史包袱、基于 Swift 的版本重新构想 Gilt 的 iOS 代码库。
从那时起,我们将 Cleanroom 项目扩展到包含多平台支持。我们的大部分代码库现在除了支持 iOS 之外,还支持 tvOS,并且我们的底层代码也可以在 macOS 和 watchOS 上使用。
Cleanroom 项目代码是 Gilt on TV 的基础,我们的 tvOS 应用 在 Apple 发布新款 Apple TV 时被 Apple 重点展示。随着时间的推移,我们将用 Cleanroom 实现替换越来越多的现有 Objective-C 代码库。
与此同时,我们将跟踪 Swift 和 Xcode 的最新版本,并一路开源我们代码库的主要部分。
CleanroomLogger 正在积极开发中,我们欢迎您的贡献。
如果您想为此或任何其他 Cleanroom 项目仓库做出贡献,请阅读贡献指南。如果您有任何问题,请联系项目负责人 Paul Lee。
API 文档是使用 Realm 的 jazzy 项目生成的,该项目由 JP Simard 和 Samuel E. Giddins 维护。