XCGLogger

badge-language badge-platforms badge-license

badge-swiftpm badge-cocoapods badge-carthage

badge-mastodon badge-twitter

badge-sponsors badge-patreon

tl;dr (太长不看,简而言之)

XCGLogger 是最初的调试日志模块,用于 Swift 项目。

Swift 不包含 C 预处理器,因此开发者无法使用他们在 Objective-C 中使用的调试日志 #define 宏。这意味着我们传统的生成良好调试日志的方式不再有效。仅仅使用简单的 print 调用意味着你将丢失很多有用的信息,或者需要编写更多的代码。

XCGLogger 允许你像使用 NSLog()print() 一样,将详细信息记录到控制台(以及可选的文件或其他自定义目标),但具有额外的的信息,例如日期、函数名称、文件名和行号。

从这里开始:

简单消息

到这里:

2014-06-09 06:44:43.600 [Debug] [AppDelegate.swift:40] application(_:didFinishLaunchingWithOptions:): 简单消息

示例

Example

交流 (感谢 AlamoFire)

安装

Git 子模块

执行

git submodule add https://github.com/DaveWoodCom/XCGLogger.git

在你的仓库文件夹中。

Carthage

将以下行添加到你的 Cartfile

github "DaveWoodCom/XCGLogger" ~> 7.1.5

然后运行 carthage update --no-use-binaries 或仅运行 carthage update。有关 Carthage 的安装和使用的详细信息,请访问 它的项目页面

在 Swift 中运行 5.0 及更高版本的开发者需要将 $(SRCROOT)/Carthage/Build/iOS/ObjcExceptionBridging.framework 添加到 Copy Carthage Frameworks Build Phase 的 Input Files 中。

CocoaPods

将类似于以下行的内容添加到你的 Podfile。 你可能需要根据你的平台、版本/分支等进行调整。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!

pod 'XCGLogger', '~> 7.1.5'

单独指定 pod XCGLogger 将包含核心框架。我们正在开始添加子规范,以允许你包含可选组件

pod 'XCGLogger/UserInfoHelpers', '~> 7.1.5':包含一些实验代码,以帮助处理使用 UserInfo 字典标记日志消息。

然后运行 pod install。 有关 CocoaPods 的安装和使用的详细信息,请访问 其官方网站

注意:在 CocoaPods 1.4.0 之前,不可能将多个 pods 与 Swift 版本的混合使用。 你可能需要确保每个 pod 都配置为正确的 Swift 版本(检查工作区 pod 项目中的目标)。 如果你手动调整项目的 Swift 版本,则下次运行 pod install 时会重置它。 你可以在你的 podfile 中添加一个 post_install 钩子来自动设置正确的 Swift 版本。 这在很大程度上未经测试,我不确定它是否是一个好的解决方案,但它似乎有效

post_install do |installer|
    installer.pods_project.targets.each do |target|
        if ['SomeTarget-iOS', 'SomeTarget-watchOS'].include? "#{target}"
            print "Setting #{target}'s SWIFT_VERSION to 4.2\n"
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '4.2'
            end
        else
            print "Setting #{target}'s SWIFT_VERSION to Undefined (Xcode will automatically resolve)\n"
            target.build_configurations.each do |config|
                config.build_settings.delete('SWIFT_VERSION')
            end
        end
    end

    print "Setting the default SWIFT_VERSION to 3.2\n"
    installer.pods_project.build_configurations.each do |config|
        config.build_settings['SWIFT_VERSION'] = '3.2'
    end
end

你当然可以根据自己的需要进行调整。

Swift Package Manager (Swift 包管理器)

将以下条目添加到你软件包的依赖项中

.Package(url: "https://github.com/DaveWoodCom/XCGLogger.git", majorVersion: 7)

向后兼容性

使用

基本用法(快速入门)

此快速入门方法旨在让你快速启动并运行记录器。但是,你应该使用 下面的高级用法,以充分利用此库。

将 XCGLogger 项目作为子项目添加到你的项目中,并将相应的库作为你目标(们)的依赖项添加。 在你的目标的 General 选项卡下,将 XCGLogger.frameworkObjcExceptionBridging.framework 添加到 Embedded Binaries 部分。

然后,在每个源文件中

import XCGLogger

在你的 AppDelegate(或其他全局文件)中,声明一个全局常量到默认的 XCGLogger 实例。

let log = XCGLogger.default

application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) // iOS, tvOS

或者

applicationDidFinishLaunching(_ notification: Notification) // macOS

函数中,配置你需要的选项

log.setup(level: .debug, showThreadName: true, showLevel: true, showFileNames: true, showLineNumbers: true, writeToFile: "path/to/file", fileLevel: .debug)

writeToFile: 的值可以是 StringURL。 如果该文件已经存在,则在使用它之前将清除它。 省略该参数或将其设置为 nil 将仅记录到控制台。 你可以选择使用 fileLevel: 参数为文件输出设置不同的日志级别。 将其设置为 nil 或省略它以使用与控制台相同的日志级别。

然后,每当你想要记录某些内容时,请使用以下便捷方法之一

log.verbose("A verbose message, usually useful when working on a specific problem")
log.debug("A debug message")
log.info("An info message, probably useful to power users looking in console.app")
log.notice("A notice message")
log.warning("A warning message, may indicate a possible error")
log.error("An error occurred, but it's recoverable, just info about what happened")
log.severe("A severe error occurred, we are likely about to crash now")
log.alert("An alert error occurred, a log destination could be made to email someone")
log.emergency("An emergency error occurred, a log destination could be made to text someone")

不同的方法设置消息的日志级别。 XCGLogger 将仅打印日志级别大于或等于其当前日志级别设置的消息。 因此,日志级别为 .error 的记录器将仅输出日志级别为 .error.severe.alert.emergency 的消息。

高级用法(推荐)

XCGLogger 旨在简单易用,并且只需 2 行以上的代码即可让你快速启动并运行。 但它允许更大的控制和灵活性。

可以将记录器配置为将日志消息传递到各种目标。 使用上面的基本设置,记录器会将日志消息输出到标准的 Xcode 调试控制台,如果提供了路径,则可以选择输出到文件。 你很可能希望将日志发送到更有趣的地方,例如 Apple 系统控制台,数据库,第三方服务器或其他应用程序,例如 NSLogger。 通过将目标添加到记录器来完成此操作。

这是一个配置记录器以输出到 Apple 系统日志以及文件的示例。

// Create a logger object with no destinations
let log = XCGLogger(identifier: "advancedLogger", includeDefaultDestinations: false)

// Create a destination for the system console log (via NSLog)
let systemDestination = AppleSystemLogDestination(identifier: "advancedLogger.systemDestination")

// Optionally set some configuration options
systemDestination.outputLevel = .debug
systemDestination.showLogIdentifier = false
systemDestination.showFunctionName = true
systemDestination.showThreadName = true
systemDestination.showLevel = true
systemDestination.showFileName = true
systemDestination.showLineNumber = true
systemDestination.showDate = true

// Add the destination to the logger
log.add(destination: systemDestination)

// Create a file log destination
let fileDestination = FileDestination(writeToFile: "/path/to/file", identifier: "advancedLogger.fileDestination")

// Optionally set some configuration options
fileDestination.outputLevel = .debug
fileDestination.showLogIdentifier = false
fileDestination.showFunctionName = true
fileDestination.showThreadName = true
fileDestination.showLevel = true
fileDestination.showFileName = true
fileDestination.showLineNumber = true
fileDestination.showDate = true

// Process this destination in the background
fileDestination.logQueue = XCGLogger.logQueue

// Add the destination to the logger
log.add(destination: fileDestination)

// Add basic app info, version info etc, to the start of the logs
log.logAppDetails()

你可以根据需要使用不同的选项配置每个日志目标。

另一种常见的用法模式是使用多个记录器,可能一个用于 UI 问题,一个用于网络,另一个用于数据问题。

每个日志目标都可以具有其自己的日志级别。 为了方便起见,你可以在日志对象本身上设置日志级别,它会将该级别传递到每个目标。 然后设置需要不同的目标。

注意:一个目标对象只能添加到一个记录器对象,将其添加到第二个记录器对象将从第一个记录器对象中删除它。

使用闭包初始化

或者,你可以使用闭包来初始化你的全局变量,以便所有初始化都在一个地方完成

let log: XCGLogger = {
    let log = XCGLogger(identifier: "advancedLogger", includeDefaultDestinations: false)

	// Customize as needed
    
    return log
}()

注意:这将延迟创建日志对象,这意味着直到实际需要时才创建它。 这会延迟应用信息详细信息的初始输出。 因此,我建议通过在你的 didFinishLaunching 方法的顶部添加行 let _ = log 来强制在应用程序启动时创建日志对象,如果你尚未在应用程序启动时记录任何内容。

记录任何内容

你可以记录字符串

log.debug("Hi there!")

或几乎任何你想要的内容

log.debug(true)
log.debug(CGPoint(x: 1.1, y: 2.2))
log.debug(MyEnum.Option)
log.debug((4, 2))
log.debug(["Device": "iPhone", "Version": 7])

过滤日志消息

XCGLogger 4 的新增功能,你现在可以创建过滤器以应用于你的记录器(或特定目标)。 创建和配置你的过滤器(下面的示例),然后通过将可选的 filters 属性设置为包含过滤器的数组,将其添加到记录器或目标对象。 过滤器按照它们在数组中存在的顺序应用。 在处理过程中,会询问每个过滤器是否应从日志中排除该日志消息。 如果任何过滤器排除该日志消息,则将其排除。 过滤器无法撤消另一个过滤器的排除。

如果目标的 filters 属性为 nil,则使用日志的 filters 属性。 为了使一个目标记录所有内容,同时让所有其他目标过滤某些内容,请将过滤器添加到日志对象,并将一个目标的 filters 属性设置为空数组 []

注意:与目标不同,你可以将相同的过滤器对象添加到多个记录器和/或多个目标。

按文件名过滤

要排除来自特定文件的所有日志消息,请创建一个如下所示的排除过滤器

log.filters = [FileNameFilter(excludeFrom: ["AppDelegate.swift"], excludePathWhenMatching: true)]

excludeFrom: 接受一个 Array<String>Set<String>,因此你可以同时指定多个文件。

excludePathWhenMatching: 默认为 true,因此你可以省略它,除非你也要匹配路径。

要仅包含特定文件集的日志消息,请使用 includeFrom: 初始化程序创建过滤器。 也可以只切换 inverse 属性以将排除过滤器翻转为包含过滤器。

按标签过滤

为了按标签过滤日志消息,你当然必须能够在日志消息上设置标签。 现在,每个日志消息都可以附加额外的用户定义数据,以供过滤器(和/或格式化程序等)使用。 这由 userInfo: Dictionary<String, Any> 对象处理。 字典键应为命名空间字符串,以避免与将来的添加发生冲突。 官方键将以 com.cerebralgardens.xcglogger 开头。 可以通过 XCGLogger.Constants.userInfoKeyTags 访问标签键。 你绝对不想输入它,因此可以随意创建一个全局快捷方式:let tags = XCGLogger.Constants.userInfoKeyTags。 现在你可以轻松地标记你的日志

let sensitiveTag = "Sensitive"
log.debug("A tagged log message", userInfo: [tags: sensitiveTag])

tags 的值可以是 Array<String>Set<String> 或仅是 String,具体取决于你的需求。 它们在过滤时的工作方式都相同。

根据你的工作流程和用法,你可能会创建更快的方法来设置 userInfo 字典。 有关其他可能的快捷方式,请参阅 下面

现在你已经标记了日志,你可以轻松过滤

log.filters = [TagFilter(excludeFrom: [sensitiveTag])]

就像 FileNameFilter 一样,你可以使用 includeFrom: 或切换 inverse 以仅包含具有指定标签的日志消息。

按开发者过滤

按开发者过滤与按标签过滤完全一样,只是使用 XCGLogger.Constants.userInfoKeyDevsuserInfo 键。 实际上,这两个过滤器都是 UserInfoFilter 类的子类,你可以使用它来创建其他过滤器。 请参阅下面的 扩展 XCGLogger

混合和匹配

在有多个开发者的大型项目中,您可能需要开始标记日志消息,并注明添加消息的开发者。

虽然 userInfo 字典非常灵活,但使用起来可能有点麻烦。 您可以使用几种可能的方法来简化操作。 我仍在测试这些方法,所以它们还不是库的正式组成部分(我很乐意听到反馈或其他建议)。

我创建了一些实验性代码,以帮助创建 UserInfo 字典。(如果使用 CocoaPods,请包含可选的 UserInfoHelpers 子规范)。查看 iOS Demo 应用程序以了解其用法。

有两个符合 UserInfoTaggingProtocol 协议的结构体:TagDev

您可以为每个结构体创建适合您项目的扩展。例如:

extension Tag {
    static let sensitive = Tag("sensitive")
    static let ui = Tag("ui")
    static let data = Tag("data")
}

extension Dev {
    static let dave = Dev("dave")
    static let sabby = Dev("sabby")
}

除了这些类型之外,还有一个重载运算符 |,可用于将它们合并到一个与日志调用的 UserInfo: 参数兼容的字典中。

然后,您可以像这样记录消息:

log.debug("A tagged log message", userInfo: Dev.dave | Tag.sensitive)

我看到这些 UserInfoHelpers 存在一些当前问题,这就是为什么我将其设置为可选/实验性的原因。 我很乐意听到有关改进的评论/建议。

  1. 重载运算符 | 合并字典,只要没有 Set。 如果其中一个字典包含 Set,它将使用其中一个,而不合并它们。 如果双方都为同一键设置了集合,则首选左侧的集合。
  2. 由于 userInfo: 参数需要字典,因此您无法传入单个 Dev 或 Tag 对象。 您需要至少使用两个带有 | 运算符的对象,才能使其自动转换为兼容的字典。 例如,如果您只想使用一个 Tag,则必须手动访问 .dictionary 参数:userInfo: Tag("Blah").dictionary

选择性地执行代码

所有日志方法都基于闭包运行。 使用与 Swift 的 assert() 函数相同的语法糖,这种方法确保我们不会浪费资源来构建无论如何都不会输出的日志消息,同时保持干净的调用点。

例如,如果调试日志级别被抑制,则以下日志语句不会浪费资源:

log.debug("The description of \(thisObject) is really expensive to create")

类似地,假设您必须迭代一个循环才能在记录结果之前进行一些计算。 在 Objective-C 中,您可以将该代码块放在 #if #endif 之间,以防止代码运行。 但是在 Swift 中,以前您仍然需要处理该循环,从而浪费资源。 使用 XCGLogger,它就像这样简单:

log.debug {
    var total = 0.0
    for receipt in receipts {
        total += receipt.total
    }

    return "Total of all receipts: \(total)"
}

如果您希望选择性地执行代码而不生成日志行,请返回 nil,或使用以下方法之一:verboseExecdebugExecinfoExecwarningExecerrorExecsevereExec

自定义日期格式

您可以创建自己的 DateFormatter 对象并将其分配给记录器。

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy hh:mma"
dateFormatter.locale = Locale.current
log.dateFormatter = dateFormatter

使用颜色增强日志消息

XCGLogger 支持向日志消息添加格式化代码,以在各种位置启用颜色。 最初的选择是使用 XcodeColors 插件。 但是,Xcode(截至版本 8)不再正式支持插件。 您仍然可以查看彩色日志,但目前无法在 Xcode 中查看。 您可以使用 ANSI 颜色支持将颜色添加到您的 fileDestination 对象,并通过终端窗口查看您的日志。 这为您提供了一些额外的选项,例如添加粗体、斜体或(请不要)闪烁!

启用后,每个日志级别都可以拥有自己的颜色。 这些颜色可以根据需要进行自定义。 如果使用多个记录器,您可以选择将每个记录器设置为自己的颜色。

设置 ANSI 格式化程序的示例

if let fileDestination: FileDestination = log.destination(withIdentifier: XCGLogger.Constants.fileDestinationIdentifier) as? FileDestination {
    let ansiColorLogFormatter: ANSIColorLogFormatter = ANSIColorLogFormatter()
    ansiColorLogFormatter.colorize(level: .verbose, with: .colorIndex(number: 244), options: [.faint])
    ansiColorLogFormatter.colorize(level: .debug, with: .black)
    ansiColorLogFormatter.colorize(level: .info, with: .blue, options: [.underline])
    ansiColorLogFormatter.colorize(level: .notice, with: .green, options: [.italic])
    ansiColorLogFormatter.colorize(level: .warning, with: .red, options: [.faint])
    ansiColorLogFormatter.colorize(level: .error, with: .red, options: [.bold])
    ansiColorLogFormatter.colorize(level: .severe, with: .white, on: .red)
    ansiColorLogFormatter.colorize(level: .alert, with: .white, on: .red, options: [.bold])
    ansiColorLogFormatter.colorize(level: .emergency, with: .white, on: .red, options: [.bold, .blink])
    fileDestination.formatters = [ansiColorLogFormatter]
}

与过滤器一样,您可以为多个记录器和/或多个目标使用相同的格式化程序对象。 如果目标的 formatters 属性为 nil,则将改用记录器的 formatters 属性。

有关创建自己的自定义格式化程序的信息,请参见下文的 扩展 XCGLogger

备用配置

通过使用 Swift 构建标志,可以在调试与暂存/生产中使用不同的日志级别。 转到 Build Settings -> Swift Compiler - Custom Flags -> Other Swift Flags 并将 -DDEBUG 添加到 Debug 条目。

#if DEBUG
    log.setup(level: .debug, showThreadName: true, showLevel: true, showFileNames: true, showLineNumbers: true)
#else
    log.setup(level: .severe, showThreadName: true, showLevel: true, showFileNames: true, showLineNumbers: true)
#endif

您可以类似地设置任意数量的选项。 有关基于选项使用不同日志目标的示例,请参见更新后的 iOSDemo 应用程序,搜索 USE_NSLOG

后台日志处理

默认情况下,提供的日志目标将在调用它们的线程上处理日志。 这是为了确保在调试应用程序时立即显示日志消息。 您可以在日志调用之后立即添加一个断点,并在断点命中断点时查看结果。

但是,如果您没有积极地调试应用程序,那么在当前线程上处理日志可能会导致性能下降。 现在,您可以指定目标在其选择的调度队列上处理其日志(甚至使用默认提供的队列)。

fileDestination.logQueue = XCGLogger.logQueue

或者甚至

fileDestination.logQueue = DispatchQueue.global(qos: .background)

当与上面的 备用配置 方法结合使用时,效果非常好。

#if DEBUG
    log.setup(level: .debug, showThreadName: true, showLevel: true, showFileNames: true, showLineNumbers: true)
#else
    log.setup(level: .severe, showThreadName: true, showLevel: true, showFileNames: true, showLineNumbers: true)
    if let consoleLog = log.logDestination(XCGLogger.Constants.baseConsoleDestinationIdentifier) as? ConsoleDestination {
        consoleLog.logQueue = XCGLogger.logQueue
    }
#endif

追加到现有日志文件

当使用记录器的高级配置时(请参见上面的 高级用法),您现在可以指定记录器追加到现有日志文件,而不是自动覆盖它。

初始化 FileDestination 对象时,添加可选的 shouldAppend: 参数。 您还可以添加 appendMarker: 参数,以将标记添加到日志文件,指示应用程序的新实例从何处开始追加。 默认情况下,如果省略该参数,我们将添加 -- ** ** ** --。 将其设置为 nil 以跳过追加标记。

let fileDestination = FileDestination(writeToFile: "/path/to/file", identifier: "advancedLogger.fileDestination", shouldAppend: true, appendMarker: "-- Relauched App --")

自动日志文件轮换

当记录到文件时,您可以选择自动将日志文件轮换到存档目标,并让记录器自动创建一个新的日志文件来代替旧的日志文件。

使用 AutoRotatingFileDestination 类创建一个目标并设置以下属性

targetMaxFileSize:文件大于此值时自动旋转

targetMaxTimeInterval:经过此秒数后自动旋转

targetMaxLogFiles:要保留的存档日志文件数,旧的日志文件会自动删除

这些都是记录器的指导原则,而不是硬性限制。

扩展 XCGLogger

您可以创建备用日志目标(除了内置目标之外)。 您的自定义日志目标必须实现 DestinationProtocol 协议。 实例化您的对象,配置它,然后使用 add(destination:) 将其添加到 XCGLogger 对象。 有两个基本目标类(BaseDestinationBaseQueuedDestination),您可以从中继承,以处理大部分过程,只需要您在自定义类中实现一个额外的方法。 查看 ConsoleDestinationFileDestination 示例。

您还可以创建自定义过滤器或格式化程序。 请查看提供的版本作为起点。 请注意,过滤器和格式化程序能够更改日志消息的处理方式。 这意味着您可以创建一个过滤器来删除密码、突出显示特定单词、加密消息等。

贡献

由于来自像您这样的社区的贡献,XCGLogger 是 Swift 可用的最佳记录器。 您可以通过多种方式帮助它继续变得更好。

  1. GitHub 上为该项目加星标。
  2. 报告您发现的问题/错误。
  3. 提出功能建议。
  4. 提交拉取请求。
  5. 下载并安装我的其中一个应用程序:https://www.cerebralgardens.com/apps/ 尝试我的最新应用程序:All the Rings
  6. 您可以访问我的 Patreon 并提供经济上的支持。

注意:在提交拉取请求时,请使用许多小的提交,而不是一个大的提交。 当有多个拉取请求需要合并以创建一个新版本时,这会使合并变得更加容易。

待办事项

更多

如果您觉得这个库有帮助,您一定会发现这个其他工具有帮助

Watchdog: https://watchdogforxcode.com/

此外,请查看我的其他一些项目

变更日志

变更日志现在位于其自身的文件中:CHANGELOG.md