使用 os_log 记录函数参数或其他动态数据

Using os_log to log function arguments, or other dynamic data

我正在尝试将函数参数记录到 os_log 中,如下所示:

func foo(x: String, y: [String:String]) {
    //...
    os_log("foo: \(x) \(y.description)", log: OSLog.default, type: .debug)
}

但是出现错误:

Cannot convert value of type 'String' to expected argument type 'StaticString'

那么如何记录函数参数或任何其他动态数据?

参见Logging

Formatting Log Messages

To format a log message, use a standard NSString or printf format string, ...

String Format Specifiers 用于标准格式字符串说明符,例如 %@%d.

你的情况:

os_log("foo: %@ %@", log: .default, type: .debug, x, y.description)

格式字符串限制为静态字符串以防止 (无意)扩展格式字符串说明符。这是一个演示 问题,使用 NSLog() 因为那不限制格式 常量字符串:

let s = "50%"
NSLog("\(s)percent")
// Output: 500x0ercent

%p 需要变量参数列表上的指针,即 不提供。这是未定义的行为,可能导致崩溃 或意外输出。

我对无法在 os_log.

中使用 "\(variable)" Swift 字符串插值感到恼火

我写了一个小扩展来解决这个问题:

import os.log

extension OSLog {
    
    static func log(_ message: String, log: OSLog = .default, type: OSLogType = .default) {
        os_log("%@", log: log, type: type, message)
    }
    
}

这确实会导致“私人”日志记录,这是预期的。

App Name <private>

在 Console.app 中,我如何才能揭示 <private> 标签实际指的是什么?


在 Apple 的 WWDC 2016 演示文稿中 "Unified Logging and Activity Tracing" 他们说:

Avoid wrapping os log APIs in other functions.

If you wrap it in another function you then lose our ability to collect the file and line number for you.

If you absolutely have to wrap our APIs, then wrap them in macros and not in functions.

因此,如果您担心额外收集的信息,这可能不是最佳解决方案。尽管即使使用股票 os_log 也可能无法获得该信息:

如果有人想写一个允许 "\(variable)" 替换的“宏”替代方案,欢迎使用。

在Xcode12/Swift5.3/iOS14中,根本不用直接调用os_log。相反,将您对 OSLog class 的使用替换为新的 Logger class(当您 import os 时可用)。这是一个例子:

let myLog = Logger(subsystem: "testing", category: "exploring")

然后您可以直接在您的 Logger 对象上调用方法以使用该子系统和类别进行记录:

myLog.log("logging at \(#function)")

要在默认级别以外的级别登录,请使用该级别作为方法的名称:

myLog.debug("logging at \(#function)")

消息字符串中,可以看到,Swift字符串插值是合法的。 Int、Double、具有 description 的 Objective-C 对象和符合 CustomStringConvertible 的 Swift 对象是允许的。

此处 Swift 字符串插值的合法性令人惊讶,因为 os_log 格式说明符的要点是 推迟 参数的评估,推动它从您的应用程序中退出(这样您的应用程序就不会因日志记录而变慢)并进入日志记录机制本身。嗯,惊喜!由于 Swift 5 中引入的自定义 Swift 字符串插值挂钩,插值 推迟。

使用自定义字符串插值在这里还有两个好处。首先,自定义字符串插值机制允许插值伴随指定其行为的附加参数。这就是防止值被编辑的方法:

myLog.log("logging at \(#function, privacy: .public)")

您还可以使用其他参数来执行各种字符串格式设置,否则您将不得不使用 NSLog 格式说明符执行这些操作,例如指示小数点后的位数和其他类型的填充和对齐:

myLog.log("the number is \(i, format: .decimal(minDigits: 5))") // e.g. 00001

因此您再也不需要直接调用 os_log,也不必再使用 NSLog 类型的格式说明符。


iOS 13 岁及之前的旧答案:

Martin R 的回答有两点扩展:

os_log("foo: %@ %@", log: .default, type: .debug, x, y.description)

可以省略type:参数,但不能省略log:参数;你必须有它,包括 log: 标签,否则 os_log 会误解你的意图。

此外,log: 值不必是 .default。通常预先创建一个或多个 OSLog 对象,用作 log: 参数的参数。此处的优点是您可以为 OSLog 对象指定子系统和类别,这些又允许您在 Xcode 控制台或控制台应用程序中过滤结果。


此外,关于 pkamb 的回答,如果我们知道我们的消息总是一个字符串,我们可以像这样编写 OSLog 扩展(利用新的 Swift 5.2 callAsFunction方法):

extension OSLog {
    func callAsFunction(_ s: String) {
        os_log("%{public}s", log: self, s)
    }
}

结果是我们现在可以将我们的 OSLog 对象 myLog 本身视为一个函数:

myLog("The main view's bounds are \(self.view.bounds)")

这很好,因为它就像基本的 print 语句一样简单。我很欣赏 WWDC 2016 警告不要进行这种预格式化,但如果这是你已经在 print 声明中做的事情,我无法想象它是那么有害。

macOS 11 Big Sur 发行说明指出 os_log 现在可以通过 Swift 字符串插值:

https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11-beta-release-notes

Logging

New Features

  • New APIs are available for using os_log from Swift as part of the os framework:

    • A new type Logger can be instantiated using a subsystem and category and provides methods for logging at different levels ( debug(_:) , error(_:) , fault(_:) ).

    • The Logger APIs support specifying most formatting and privacy options supported by legacy os_log APIs.

    • The new APIs provide significant performance improvements over the legacy APIs.

    • You can now pass Swift string interpolation to the os_log function.

Note: The new APIs can't be back deployed; however, the existing os_log API remains available for back deployment. (22539144)

这是我的方法:

import Foundation
import os.log

struct Log {
    enum LogLevel: String {
        case error = "⛔️"
        case warning = "⚠️"
        case debug = ""
    }

    static func debug(_ info: String, level: LogLevel = .debug, file: String = #file, function: String = #function, line: Int = #line) {
        os_log("%@ %@:%d %@: %@", type: .default, level.rawValue, (file as NSString).lastPathComponent, line, function, info)
    }

    static func warning(_ info: String, level: LogLevel = .warning, file: String = #file, function: String = #function, line: Int = #line) {
        os_log("%@ %@:%d %@: %@", type: .default, level.rawValue, (file as NSString).lastPathComponent, line, function, info)
    }

    static func error(_ error: NSError, level: LogLevel = .error, file: String = #file, function: String = #function, line: Int = #line) {
        os_log("%@ %@:%d %@: %@", type: .default, level.rawValue, (file as NSString).lastPathComponent, line, function, "\(error)")
    }
}

用法:

Log.debug("MyLog")

输出示例:

AppDelegate.swift:26 application(_:didFinishLaunchingWithOptions:): MyLog