我正在尝试像这样记录函数参数到os_log
中:
func foo(x: String, y: [String:String]) {
//...
os_log("foo: \(x) \(y.description)", log: OSLog.default, type: .debug)
}
但是遇到错误:
无法将类型为 'String' 的值转换为预期的参数类型 'StaticString'
那么我该如何记录函数参数或任何其他动态数据呢?
请参考日志记录:
格式化日志消息
要格式化日志消息,请使用标准的NSString或printf格式字符串,...
并使用字符串格式说明符来指定标准格式字符串说明符,如%@
和%d
。
在您的情况下:
os_log("foo: %@ %@", log: .default, type: .debug, x, y.description)
为了防止格式字符串指示符(format string specifiers)的意外扩展,格式字符串必须限制在静态字符串中。以下是一个示例,演示了使用NSLog()
出现问题的情况,因为它不限制格式字符串只能是常量字符串:
The format string is restricted to static strings to prevent
(unintentional) expansion of format string specifiers. Here is an example demonstrating the
problem, using NSLog()
because that does not restrict the format
to constant strings:
let s = "50%"
NSLog("\(s)percent")
// Output: 500x0ercent
%p
期望可变参数列表上提供一个指针变量,但实际未提供。这是未定义的行为,可能会导致崩溃或意外输出。
在 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字符串插值是合法的。它允许使用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对象指定Subsystem和Category,从而允许您在Xcode控制台或Console应用程序中对结果进行过滤。
另外,关于pkamb的回答,如果我们知道我们的消息永远都是字符串,我们可以像这样编写OSLog扩展(利用新的Swift 5.2 callAsFunction
方法):
extension OSLog {
func callAsFunction(_ s: String) {
os_log("%{public}s", log: self, s)
}
}
结果是我们现在可以将我们的myLog
对象本身视为一个函数:
myLog("The main view's bounds are \(self.view.bounds)")
这很好,因为它就像基本的print
语句一样简单。我赞赏WWDC 2016对这种预格式化的警告,但如果这正是您在print
语句中已经在做的事情,我想它并不会有太大的危害。
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
"<private data> <private data>:<private data> <private data>: <private data>"
。 - timbre timbreos_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)
}
}
避免在其他函数中包装os log APIs。
如果您将其包装在另一个函数中,则会失去我们为您收集文件和行号的能力。
如果您确实需要包装我们的API,则请使用宏而不是函数进行包装。
因此,如果您担心额外收集的信息,则这可能不是最佳解决方案。即使使用标准的os_log
,该信息仍然可能无法获得:How to find source file and line number from os_log()
如果有人想编写允许"\(variable)"
替换的“宏”替代方法,则可以考虑使用。
os_log
中也无法获得行号...?https://dev59.com/UZzha4cB1Zd3GeqPJr6z - pkambos_log
。
https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11-beta-release-notes
现在可以使用 Swift 作为 os 框架的一部分,使用新的 API os_log
进行日志记录:
使用子系统和类别可以实例化一个新类型的 Logger,并提供不同级别日志记录的方法 (debug(_:)、error(_:)、fault(_:))
。
Logger API 支持指定大多数格式化和隐私选项,这些选项受遗留的 os_log
API 支持。
新的 API 提供了比遗留 API 更显著的性能改进。
现在您可以将 Swift 字符串插值传递给 os_log
函数。
注意:新的 API 无法进行后向兼容;但是,现有的 os_log
API 可用于后向兼容。(22539144)
extension Logger {
static let myApp = Logger(subsystem: "com.mycompany", category: "MYAPP")
}
Logger.myApp.debug("foo: \(x) \(y.description)")