SwiftLog

首先说明:这是一个社区驱动的开源项目的开端,积极寻求贡献,无论是代码、文档还是想法。除了为 SwiftLog 本身做贡献之外,目前还存在一个巨大的空白:SwiftLog 是一个 *API 包*,旨在建立一个通用的 API,供整个生态系统使用。为了使日志记录真正适用于实际工作负载,我们需要与 SwiftLog 兼容的 *日志记录后端*,然后将日志消息持久化到文件中,在终端上以更漂亮的颜色呈现它们,或者将它们发送到 Splunk 或 ELK。

SwiftLog 目前提供的功能可以在 API 文档中找到。

入门

如果您有一个服务器端 Swift 应用程序,或者一个跨平台(例如 Linux 和 macOS)的应用程序/库,并且您想进行日志记录,我们认为以这个日志记录 API 包为目标是一个好主意。您将在下面找到开始所需的所有信息。

添加依赖项

SwiftLog 专为 Swift 5.8 及更高版本设计。要依赖于日志记录 API 包,您需要在 Package.swift 中声明您的依赖项

.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),

并添加到您的应用程序/库目标,将 "Logging" 添加到您的 dependencies 中,例如像这样

.target(name: "BestExampleApp", dependencies: [
    .product(name: "Logging", package: "swift-log")
],

开始记录日志

// 1) let's import the logging API package
import Logging

// 2) we need to create a logger, the label works similarly to a DispatchQueue label
let logger = Logger(label: "com.example.BestExampleApp.main")

// 3) we're now ready to use it
logger.info("Hello World!")

输出

2019-03-13T15:46:38+0000 info: Hello World!

默认的 Logger 行为

SwiftLog 通过 StreamLogHandler 提供了非常基本的开箱即用的控制台日志记录。 可以像这样将默认输出切换到 stderr

LoggingSystem.bootstrap(StreamLogHandler.standardError)

StreamLogHandler 主要只是一种便利措施,不提供任何实质性的自定义。旨在构建自己的日志记录后端以进行集成和使用的库维护人员应直接实现 LogHandler 协议,如 “关于日志记录后端的实现”部分中所述。

有关更多信息,请查看 API 文档

可用于应用程序的日志记录后端

您可以从以下后端中选择一个来使用您的日志。如果您有兴趣实现一个,请参阅下面的“实现注意事项”部分,其中解释了如何操作。现有与 SwiftLog API 兼容的库列表

仓库 处理程序描述
Kitura/HeliumLogger 在 Kitura 生态系统中广泛使用的日志记录后端
ianpartridge/swift-log-syslog 一个 syslog 后端
Adorkable/swift-log-format-and-pipe 一个允许自定义输出格式和结果目标的后端
chrisaljoudi/swift-log-oslog 一个用于 Apple 平台的 OSLog 统一日志记录 后端。重要提示:我们建议直接使用 os_log,如 此处所述。通过使用此后端的 swift-log 使用 os_log 的效率会较低,并且还会阻止指定消息的隐私。该后端始终使用 %{public}@ 作为格式字符串,并急切地将所有字符串插值转换为字符串。这有两个缺点:1. 字符串插值的静态组件将被统一日志记录系统急切地复制,这将导致性能损失。2. 它使所有消息公开,这改变了 os_log 的默认隐私策略,并且不允许指定消息部分的细粒度隐私。在一个单独的正在进行的工作中,用于 os_log 的 Swift API 正在得到改进,并使其与 swift-log API 紧密对齐。参考资料:统一日志记录级别使 os_log 接受使用编译时解释的字符串插值
Brainfinance/StackdriverLogging 一个结构化的 JSON 日志记录后端,用于在 Google Cloud Platform 上与 Stackdriver 日志记录代理一起使用
DnV1eX/GoogleCloudLogging 一个客户端库,用于通过 REST API v2 在 Google Cloud 中记录应用程序事件。
vapor/console-kit 一个使用样式化(ANSI)输出记录到当前终端或 stdout 的记录器。 所有 Vapor 应用程序的默认记录器
neallester/swift-log-testing 提供对日志消息的访问权限,以用于断言(在测试目标中)
wlisac/swift-log-slack 一个将关键日志消息发送到 Slack 的日志记录后端
NSHipster/swift-log-github-actions 一个将日志消息转换为 GitHub Actions 的工作流程命令的日志记录后端。
stevapple/swift-log-telegram 一个将日志消息发送到任何 Telegram 聊天的日志记录后端(受到 wlisac/swift-log-slack 的启发并从中派生)
jagreenwood/swift-log-datadog 一个将日志消息发送到 Datadog 日志管理服务的日志记录后端
google/SwiftLogFireCloud 一个用于时间序列日志记录的日志记录后端,它将日志作为平面文件推送到 Firebase Cloud Storage。
crspybits/swift-log-file 一个简单的本地文件记录器(使用 Foundation FileManager
sushichop/Puppy 一个支持多种传输方式(控制台、文件、syslog 等)的日志记录后端,并具有格式化和文件日志轮换功能
ShivaHuang/swift-log-SwiftyBeaver 一个用于将彩色日志记录打印到 Xcode 控制台/文件,或将加密日志记录发送到 SwiftyBeaver 平台的日志记录后端。
Apodini/swift-log-elk 一个格式化、缓存并将日志数据发送到 elastic/logstash 的日志记录后端
binaryscraping/swift-log-supabase 一个将日志条目发送到 Supabase 的日志记录后端。
kiliankoe/swift-log-matrix 一个用于将日志直接发送到 Matrix 房间的日志记录后端
DiscordBM/DiscordLogger 一个 Discord 日志记录实现,用于以美观的方式将您的日志发送到 Discord 频道,并具有许多配置选项,包括仅发送几个重要日志级别(例如 warning/error/critical)的能力。
CocoaLumberjack 一个快速、简单、强大且灵活的 macOS、iOS、tvOS 和 watchOS 日志记录框架,其中包含 swift-log 的日志记录后端。
rwbutler/swift-log-ecs 一个用于以 ECS 日志格式记录日志的日志记录后端。与 Vapor 兼容,并允许链接多个 LogHandler。
ShipBook/swift-log-shipbook 一个将日志条目发送到 Shipbook 的日志记录后端 - Shipbook 使您能够在云中,按用户和会话,远程收集、搜索和分析您的用户日志和异常。
kasianov-mikhail/scout CloudKit 作为日志存储
PADL/AndroidLogging 用于 Android 内核日志缓冲区的日志记录后端
xtremekforever/swift-systemd 用于 systemd 日志的日志记录后端
您的库? 取得联系!

什么是 API 包?

很高兴您问到。我们认为,对于服务器端 Swift 生态系统,拥有一个可以被任何人采用的日志记录 API 至关重要,这样来自不同方的多个库都可以记录到共享目标。更具体地说,这意味着我们相信所有库的所有日志消息最终都会出现在同一个文件、数据库、Elastic Stack/Splunk 实例或您可能选择的任何内容中。

然而,在现实世界中,对于日志记录系统应该如何运行、日志消息应该如何格式化以及应该在哪里/如何持久化,存在太多的意见。我们认为,等待一个日志记录包支持特定部署所需的一切,同时仍然足够易于使用并保持高性能,是不可行的。这就是我们决定将问题分成两半的原因

  1. 一个日志记录 API
  2. 一个日志记录后端实现

此软件包仅提供日志记录 API 本身,因此 SwiftLog 是一个“日志记录 API 软件包”。可以使用 SwiftLog(使用 LoggingSystem.bootstrap)配置为选择任何兼容的日志记录后端实现。这样,软件包可以采用 API,并且 *应用程序* 可以选择任何兼容的日志记录后端实现,而无需任何库进行任何更改。

只是为了完整起见:此 API 包实际上确实包含一个过于简单且不可配置的日志记录后端实现,该实现只是将所有日志消息写入 stdout。包含此过于简单的日志记录后端实现的原因是为了改善首次使用体验。假设您启动一个项目并第一次尝试 SwiftLog,看到您记录的某些内容以简化的格式出现在 stdout 上比什么都不发生要好得多。对于任何实际应用程序,我们建议配置另一个以您喜欢的样式记录的日志记录后端实现。

核心概念

记录器

Logger 用于发出日志消息,因此是 SwiftLog 中最重要的类型,因此它们的使用应尽可能简单。最常见的是,它们用于以某个日志级别发出日志消息。例如

// logging an informational message
logger.info("Hello World!")

// ouch, something went wrong
logger.error("Houston, we have a problem: \(problem)")

日志级别

支持以下日志级别

可以更改给定记录器的日志级别,但更改只会影响您更改它的特定记录器。您可以说 Logger 在日志级别方面是一个 *值类型*。

日志记录元数据

日志记录元数据是可以附加到记录器的元数据,以添加在调试问题时至关重要的信息。在服务器中,常见的示例是将请求 UUID 附加到记录器,然后该 UUID 将出现在使用该记录器记录的所有日志消息中。例子

var logger = logger
logger[metadataKey: "request-uuid"] = "\(UUID())"
logger.info("hello world")

将打印

2019-03-13T18:30:02+0000 info: request-uuid=F8633013-3DD8-481C-9256-B296E43443ED hello world

使用 SwiftLog 附带的默认日志记录后端实现。 无需赘述,格式完全由您选择的日志记录后端定义。

关于日志记录后端的实现 (LogHandler)

注意:如果您不想实现自定义日志记录后端,则本节中的所有内容可能不是很有相关性,因此请随意跳过。

要成为所有 SwiftLog 使用者都可以使用的兼容日志记录后端,您需要做两件事:1) 实现一个实现 LogHandler 的类型(通常是 struct),这是 SwiftLog 提供的一个协议,以及 2) 指示 SwiftLog 使用您的日志记录后端实现。

LogHandler 或日志记录后端实现是任何符合以下协议的内容

public protocol LogHandler {
    func log(level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?, source: String, file: String, function: String, line: UInt)

    subscript(metadataKey _: String) -> Logger.Metadata.Value? { get set }

    var metadata: Logger.Metadata { get set }

    var logLevel: Logger.Level { get set }
}

指示 SwiftLog 使用您的日志记录后端作为整个应用程序(包括所有库)应该使用的后端非常简单

LoggingSystem.bootstrap(MyLogHandler.init)

实现注意事项

LogHandler 控制日志记录系统的大部分

LogHandler 的控制下

配置

LogHandler 控制着 Logger 配置的两个关键部分,即

然而,为了系统正常工作,重要的是 LogHandler 将配置视为*值类型*。 这意味着 LogHandler 应该是 struct,并且日志级别或日志元数据的更改应仅影响更改的 LogHandler 本身。

但是,在特殊情况下,允许 LogHandler 提供一些全局日志级别覆盖,这可能会影响所有已创建的 LogHandler

发送

不受 LogHandler 控制

LogHandler 不控制是否应记录消息。 如果 Logger 确定在给定配置的日志级别的情况下应发送日志消息,则 Logger 才会调用 LogHandlerlog 函数。

Source vs Label(来源 vs 标签)

一个 Logger 携带一个(不可变的)label(标签),每个日志消息携带一个 source(来源)参数(自 SwiftLog 1.3.0 起)。 Logger 的标签标识 Logger 的创建者。 如果您正在使用结构化日志记录,通过跨多个模块保留元数据,那么 Loggerlabel 不是识别日志消息来源的好方法,因为它标识了 Logger 的创建者,而创建者通常在库之间传递以保留元数据等。

如果您想过滤来自特定子系统的所有日志消息,请按 source 过滤,默认情况下 source 是发出日志消息的模块。

安全性

有关 SwiftLog 的安全流程,请参阅 SECURITY.md

设计

此日志记录 API 是与 Swift on Server 社区的贡献者共同设计的,并经 SSWG (Swift Server Work Group) 批准,达到 SSWG 的 孵化过程的“沙盒级别”。