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

时间:2018-10-27 19:48:08

标签: swift oslog

我正尝试将函数参数记录到os_log中,

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

但是出现错误:

  

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

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

5 个答案:

答案 0 :(得分:8)

请参见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期望在变量参数列表上有一个指针,即 不提供。这是未定义的行为,可能导致崩溃 或意外的输出。

答案 1 :(得分:4)

在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)")

在消息字符串中,如您所见,快速字符串内插是合法的。允许使用符合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类型的格式说明符。


iOS 13及更高版本的旧答案:

Martin R的回答扩展了两点:

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

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

此外,log:的值不必为.default。通常,可以在前面创建一个或多个OSLog对象,以用作log:参数的参数。这样做的好处是,您可以为OSLog对象指定Subsystem和Category,然后依次使用它们在Xcode控制台或Console应用程序中对结果进行过滤。


另外,关于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语句中进行了这种格式化,那么我就无法想象这会带来很大的危害。

答案 2 :(得分:1)

我因为无法在"\(variable)"中使用os_log 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"中,他们说:

避免在其他功能中包装操作系统日志API。

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

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

因此,如果您担心其他收集的信息,那么这可能不是最佳解决方案。尽管即使使用库存os_log仍可能无法获得该信息:How to find source file and line number from os_log()

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

答案 3 :(得分:0)

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用作os框架的一部分:

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

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

    • 新的API与旧版API相比,性能得到了显着改善。

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

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

答案 4 :(得分:0)

这是我的方法:

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