OSLogClient

实用工具类,轮询 OSLogStore 并将任何有效的日志发送到订阅的日志驱动程序。

为什么?(问题陈述)

OSLog 是 Apple 和 Swift 核心团队推荐的日志记录方法。一个全面且可靠的日志记录系统在软件开发中至关重要。然而,项目通常需要将日志发送给第三方供应商或其他服务。这就提出了一个挑战:如何在使用 OSLog 的同时,确保与外部日志解决方案的无缝和灵活集成。

OSLogClient 旨在弥合这一差距,充当中间媒介,将 OSLog 的优势和便利性引导到自定义日志机制中。它通过轮询底层的 OSLogStore,评估日志,然后将后处理的日志消息转发到注册的 LogDriver 实例来实现这一点。因此,注册的 LogDriver 可以接收到日志消息,同时保留所有基于 OSLog 的隐私、安全性和格式。驱动程序还将接收元数据,例如日期时间、日志级别、记录器子系统和记录器类别。

OSLog 的优势

随着对用户数据隐私和安全性的日益重视,Apple 的 OSLog 已成为开发者不可或缺的工具。OSLog 提供了许多优势:

隐私

OSLog 允许您格式化和编辑敏感数据,确保用户数据不会被意外泄露。

性能

OSLog 在设计时就考虑了效率,它最大限度地减少了对应用程序性能的影响。

诊断

OSLog 与系统的诊断框架无缝集成,使故障排除更加容易。您可以使用原生的“控制台”应用程序来更轻松地过滤和监控日志。

推荐方法

Apple 和 Swift 核心团队推荐使用 OSLog 而不是其他日志记录机制,因为它具有内置的功能。

通过将 OSLog 与我们的库集成,您可以在利用 OSLog 优势的同时,确保灵活的日志记录基础设施,并可以根据您的项目需求进行扩展。

基本用法

使用 OSLogClient 非常简单。以下是帮助您入门的简单指南:

// Import the library (OSLog is also included in the import)
import OSLogClient

// Initialize the OSLogClient
try OSLogClient.initialize(pollingInterval: .short)

// Register your custom log driver
let myDriver = MyLogDriver(id: "myLogDriver")
await OSLogClient.registerDriver(myDriver)

// Start polling
await OSLogClient.startPolling()

注意: 如果您尚未使用结构化并发(或仍在采用等),您将需要在任务中运行您的注册和调用。以下是一个人为设计的示例,您应该考虑任务生命周期和状态等。

try OSLogClient.initialize(pollingInterval: .short)

let setupTask = Task(priority: .userInitiated) {
    // Register your custom log driver
    let myDriver = MyLogDriver(id: "myLogDriver")
    await OSLogClient.registerDriver(myDriver)

    // Start polling
    await OSLogClient.startPolling()
}

只需这三个步骤,OSLogClient 就会开始监控来自 OSLog 的日志,并将它们转发到您注册的日志驱动程序,让您可以像往常一样使用 OSLog.Logger 实例。

let logger = Logger(subsystem: "com.company.AppName", category: "ui")

logger.info("Password '\(password, privacy: .private)' did not pass validation")

当您的驱动程序收到日志消息时,它将是经过处理的消息,确保已应用任何隐私和格式设置。例如,当未连接到调试器时,上述操作将使用以下内容调用:

管理您自己的 LogClient 实例

虽然该库的预期用法是使用 OSLogClient 入口点,但您可以初始化您自己的 LogClient 类型的实例来自行维护。

let logStore = OSLogStore(scope: .currentProcessIdentifier)
let client = try LogClient(pollingInterval: .medium, logStore: logStore)

注意: 这是为那些需要使用自己的实例的边缘情况提供的。请记住,OSLogClient 入口点在这些设置中仍然可用且功能齐全。

子类化 LogDriver

虽然基本的 LogDriver 类为处理 OS 日志提供了必要的基础,但您可以轻松地对其进行子类化以进行自定义处理,例如将日志写入文本文件。

import OSLogClient

class TextLogDriver: LogDriver {
	
    // MARK: - Properties
	
    var logFileUrl: URL
	
    // MARK: - Lifecycle
	
    required init(id: String, logFileUrl: URL, logFilters: [LogFilter] = []) {
        self.logFileUrl = logFileUrl
        super.init(id: id, logFilters: logFilters)
    }
    
    required init(id: String, logFilters: [LogFilter] = []) {
        fatalError("init(id:logFilters:) has not been implemented")
    }
	
    // MARK: - Overrides
	
    #if os(macOS)
    override func processLog(level: LogDriver.LogLevel, subsystem: String, category: String, date: Date, message: String, components: [OSLogMessageComponent]) {
        formatAndWriteMessage(level: level, category: category, date: date, message: message)
    }
    #else
    override func processLog(level: LogDriver.LogLevel, subsystem: String, category: String, date: Date, message: String) {
        formatAndWriteMessage(level: level, category: category, date: date, message: message)
    }
    #endif
	
    // MARK: - Helpers
	
    func formatAndWriteMessage(level: LogLevel, category: String, date: Date, message: String) {
      let message = "[\(category)-\(level.rawValue.uppercased())]: \(date): \(message)"
      var contents = (try? String(contentsOf: logFileUrl).trimmingCharacters(in: .whitespacesAndNewlines)) ?? ""
      contents += "\(contents.isEmpty ? "" : "\n")\(message)"
      try? contents.write(to: logFileUrl, atomically: true, encoding: .utf8)
    }
}

使用 LogFilter 过滤日志

除了仅在 processLog 方法中评估日志级别、日期和类别之外,您还可以通过指定有效的 LogFilter 条件来微调哪些日志应由 LogDriver 实例处理。

如果指定了日志过滤器(即,列表不为空),则它们用于评估传入的日志条目,确保存在匹配的过滤器。

有关支持的选项,请参阅 LogFilterLogCategoryFilter 文档。

例如,要配置日志驱动程序以仅接收 uiapi 日志条目:

let apiLogger = Logger(subsystem: "com.company.AppName", category: "api")
let uiLogger = Logger(subsystem: "com.company.AppName", category: "ui")
let storageLogger = Logger(subsystem: "com.vendor.AppName", category: "storage")

myLogDriver.addLogFilters([
  .subsystem("com.company.AppName", categories: "ui", "api")
])

通过此设置,记录器实例像往常一样工作,但驱动程序将仅捕获至少一个日志源验证的日志。

// Driver will capture these logs:
apiLogger.info("api info message")
uiLogger.info("button was tapped")

// Driver **won't** capture this log:
storageLogger.error("database error message")

这种方法有助于根据需要跨不同的驱动程序实例管理具有不同类别的记录器。

PollingInterval

PollingInterval 支持四种枚举:

.short // 10 second intervals
.medium // 30 second intervals
.long // 60 second intervals
.custom(TimeInterval) // Poll at the given duration (in seconds)

注意: 对于 custom 间隔选项,强制执行的最小值为 1 秒。

按需轮询

您还可以请求从给定时间点轮询日志。要轮询的日期是可选的,默认为最近轮询的日志的时间。

OSLogClient.pollImmediately() // Use last processed
OSLogClient.pollImmediately(from: customDate) // Custom point in time

安装

目前,OSLogClient 支持 Swift Package Manager (SPM)。

要将 OSLogClient 添加到您的项目,请将以下行添加到您的 Package.swift 文件中的 dependencies 中:

.package(url: "https://github.com/CheekyGhost-Labs/OSLogClient", from: "2.0.0")

然后,将 OSLogClient 添加为您的目标的依赖项:

.target(
    name: "YourTarget",
    dependencies: [
        // other dependencies
        .product(name: "OSLogClient", package: "OSLogClient")
    ]
),

许可证

OSLogClient 在 MIT 许可证下发布。有关更多信息,请参阅 LICENSE 文件。

贡献

欢迎为 OSLogClient 做出贡献!如果您有错误要报告,请随时通过打开新 issue 或提交 pull request 来提供帮助。

OSLogClient 非常严格地遵循标准的 git flow 流程。在大多数情况下,pull request 应该针对 develop 分支进行,以协调任何发布。这也提供了一种从 develop 分支在实际环境中进行测试的方法,以进一步测试待发布的版本。一旦发布准备就绪,它将被合并到 main,标记,并切出一个发布分支。

开始入门

  1. Fork 仓库:首先,fork 项目到您自己的 GitHub 帐户。

  2. Clone fork 的仓库:fork 后,将 fork 的仓库 clone 到您的本地计算机,以便您可以进行更改。

git clone https://github.com/CheekyGhost-Labs/OSLogClient.git
  1. 创建新分支:在进行更改之前,为您的功能或错误修复创建一个新分支。使用描述性名称,反映您更改的目的。
git checkout -b your-feature-branch
  1. 遵循 Swift 语言指南:确保您的代码符合 Swift 语言指南 的样式和语法约定。

  2. 进行更改:实施您的功能或错误修复,遵循项目的代码风格和最佳实践。不要忘记添加测试并根据需要更新文档。

  3. 提交您的更改:使用描述性和简洁的提交消息提交您的更改。使用祈使语气,并解释您的提交做了什么,而不是您做了什么。

# Feature
git commit -m "Feature: Adding convenience method of awesomeness"


# Bug
git commit -m "Bug: Fixing issue where awesome thing was not including awesome"
  1. 从上游拉取最新更改:在提交您的更改之前,请务必从上游仓库拉取最新更改,并将它们合并到您的分支中。这有助于避免任何潜在的合并冲突。
git pull origin develop
  1. 推送您的更改:将您的更改推送到您在 GitHub 上的 fork 仓库。
git push origin your-feature-branch
  1. 提交 pull request:最后,从您的 fork 仓库向原始仓库创建一个 pull request,目标分支为 develop 分支。填写 pull request 模板的必要详细信息,并等待项目维护人员审查您的贡献。

单元测试

请确保为您所做的任何更改添加单元测试。目标不是 100% 覆盖率,而是有意义的测试覆盖率,以确保您的更改按预期行为,而不会对现有行为产生负面影响。

请注意,项目维护人员可能会要求您对您的贡献进行更改或提供其他信息。对反馈持开放态度,并愿意根据需要进行调整。一旦您的 pull request 获得批准并合并,您的更改将成为项目的一部分!

其他资源

要深入了解 Apple 的 OSLog,请参阅以下文档: