【问题标题】:Using os_log to log function arguments, or other dynamic data使用 os_log 记录函数参数或其他动态数据
【发布时间】:2018-10-27 19:48:08
【问题描述】:

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

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

但出现错误:

无法将“String”类型的值转换为预期的参数类型“StaticString”

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

【问题讨论】:

    标签: swift oslog


    【解决方案1】:

    Logging:

    格式化日志消息

    要格式化日志消息,请使用标准的 NSString 或 printf 格式字符串,...

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

    在你的情况下:

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

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

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

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

    【讨论】:

      【解决方案2】:

      在 Xcode 12 / Swift 5.3 / iOS 14 中,您根本不必直接调用 os_log。相反,将您对 OSLog 类的使用替换为新的 Logger 类(import os 时可用)。这是一个例子:

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

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

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

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

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

      在消息字符串中,如你所见,Swift 字符串插值是合法的。对于带有description 的 Int、Double、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-type 格式说明符。


      iOS 13 及之前版本的旧答案:

      马丁 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 声明中已经在做的事情,我无法想象它会那么有害。

      【讨论】:

      • 这非常有用。谢谢。
      【解决方案3】:

      这是我的方法:

      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 应用程序(_:didFinishLaunchingWithOptions:): MyLog

      【讨论】:

      • 坏主意:苹果强烈反对包装 os_log(因为性能问题)。此外,iOS 14+ Logger 允许在没有任何自定义覆盖的情况下执行此操作。
      • 如果您在 Mac 日志控制台上查看您的消息,它会如下所示:"<private data> <private data>:<private data> <private data>: <private data>"
      【解决方案4】:

      我对无法在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>

      In Console.app, how can I reveal to what <private> tags are actually referring?


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

      避免将 os log API 封装在其他函数中。

      如果您将其包装在另一个函数中,您将失去我们为您收集文件和行号的能力。

      如果您绝对必须封装我们的 API,则将它们封装在宏中而不是函数中。

      因此,如果您担心收集到的额外信息,这可能不是最佳解决方案。尽管即使使用股票os_log: How to find source file and line number from os_log()

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

      【讨论】:

      • Apple 明确建议不要这样做:“避免将 os log API 包装在其他函数中。如果您将其包装在另一个函数中,您将失去我们为您收集文件和行号的能力。如果您绝对必须包装我们的 API,然后将它们包装在宏而不是函数中。”见developer.apple.com/videos/play/wwdc2016/721
      • @KirilS。这是很好的信息。将编辑为答案;也许有人可以提交一个宏替代方案。
      • 虽然行号似乎没有现货os_log...? stackoverflow.com/questions/40757530/…
      • @pkamb 在这里查看我对 OSLog 扩展想法的重新表述:stackoverflow.com/a/62488271/341994
      • @matt 对如何编写“将它们包装在宏而不是函数中”版本的扩展有任何想法吗?
      【解决方案5】:

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

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

      记录

      新功能

      • 新的 API 可用于使用来自 Swift 的 os_log 作为操作系统框架的一部分:

        • 可以使用子系统和类别来实例化一种新类型的 Logger,并提供不同级别的日志记录方法( debug(_:) , error(_:) , fault(_:) )

        • Logger API 支持指定旧版 os_log API 支持的大多数格式和隐私选项。

        • 与旧版 API 相比,新 API 提供了显着的性能改进。

        • 您现在可以将 Swift 字符串插值传递给 os_log 函数。

      注意:新的 API 不能回部署;但是,现有的os_log API 仍可用于后部署。 (22539144)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-02-05
        • 2019-01-12
        • 1970-01-01
        • 1970-01-01
        • 2019-02-11
        • 1970-01-01
        • 1970-01-01
        • 2012-04-03
        相关资源
        最近更新 更多