我正在尝试更新我的应用程序以使用 OSLog(记录器)。 我当前使用的系统允许我使用简单的字符串插值,我期望 OSLog 也能进行相同的操作,但我在一个简单的测试中看到了所有类型的错误:
import SwiftUI
import OSLog
extension Logger {
static let statistics = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "NS")
}
struct MyCustom: CustomStringConvertible {
let description = "My Custom description"
}
struct MyDebug: CustomDebugStringConvertible {
let debugDescription = "My Debug description"
}
struct NoneOfTheAbove {
var defaultValue = false
}
struct Person: Identifiable {
let id = UUID()
let index: Int
let name: String
let age: Int
static let maxNameLength = 15
}
@main
struct OggTstApp: App {
let myCustom = MyCustom()
let myDebug = MyDebug()
let noneOfTheAbove = NoneOfTheAbove()
var optionalCustom: MyCustom?
var optionalDebug: MyDebug? = MyDebug()
init() {
print("init")
Logger.statistics.debug("debug init")
}
var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
testLogs()
}
}
}
func testLogs() {
print("structs")
Logger.statistics.error("\(myCustom)")
// Logger.statistics.error("This is a test: \(myDebug)") // Type of expression is ambiguous without a type annotation
let string = "\(myDebug)"
Logger.statistics.error("\(string)")
// Logger.statistics.error(noneOfTheAbove) // Cannot convert value of type 'NoneOfTheAbove' to expected argument type 'OSLogMessage'
// Logger.statistics.error("\(noneOfTheAbove)") // Type of expression is ambiguous without a type annotation
let noneOTA = "\(noneOfTheAbove)"
// Logger.statistics.error(noneOTA) // Cannot convert value of type 'String' to expected argument type 'OSLogMessage'
Logger.statistics.error("\(noneOTA)")
// Logger.statistics.warning(optionalCustom) // Cannot convert value of type 'MyCustom?' to expected argument type 'OSLogMessage'
let optCust = "\(optionalCustom)" // Warning
Logger.statistics.warning("\(optCust)")
// Logger.statistics.log("Optional not nil: \(optionalDebug)") // No exact matches in call to instance method 'appendInterpolation'
let optNotNil = "\(optionalDebug)" // Warning
Logger.statistics.log("\(optNotNil)")
let aPerson = Person(index: 2, name: "George", age: 21)
let people = [aPerson]
people.forEach {
testLog($0)
}
}
func testLog(_ person: Person) {
Logger.statistics.debug("\(person.index) \(person.name) \(person.id) \(person.age)")
// Logger.statistics.debug("\(person.index) \(person.name, align: .left(columns: Person.maxNameLength)) \(person.id) \(person.age, format: .fixed(precision: 2))") // No exact matches in call to instance method 'appendInterpolation'
}
}
Dong 那种双字符串插值使其工作的感觉真的很痛苦。 这些警告是预料之中的,尽管我希望我可以编写一些扩展来使它们消失,但现在我的重点是错误。
我做错了什么吗?这有什么技巧吗? 顺便说一句,我只在控制台中使用这些日志,我不太关心是否能够检索它们(我可以将插值字符串值保留为私有,以防万一)。
两个观察:
Logger
中的 OSLogMessage
中的字符串插值需要 CustomStringConvertible
一致性。 (见下文。)因此,我们通常只会扩展我们想要记录的任何类型以符合 CustomStringConvertible
并完成它。这样您就无需为日志记录目的创建临时字符串。
Person
示例的问题有点不同:您使用的是非浮点类型的OSLogFloatFormatting
选项(precision
参数)。
关于
CustomStringConvertible
一致性要求,请参阅带有 OSLogInterpolation
的插值定义:
extension OSLogInterpolation {
/// Defines interpolation for values conforming to CustomStringConvertible. The values
/// are displayed using the description methods on them.
///
/// Do not call this function directly. It will be called automatically when interpolating
/// a value conforming to CustomStringConvertible in the string interpolations passed
/// to the log APIs.
///
/// - Parameters:
/// - value: The interpolated expression conforming to CustomStringConvertible.
/// - align: Left or right alignment with the minimum number of columns as
/// defined by the type `OSLogStringAlignment`.
/// - privacy: A privacy qualifier which is either private or public.
/// It is auto-inferred by default.
public mutating func appendInterpolation<T>(
_ value: @autoclosure @escaping () -> T,
align: OSLogStringAlignment = .none,
privacy: OSLogPrivacy = .auto
) where T : CustomStringConvertible
…
}
您的
MyCustom
示例的成功(这是唯一符合 CustomStringConvertible
的示例)说明了这一点。此外,这个CustomStringConvertible
一致性要求在 2020 年 WWDC 视频探索 Swift 中的日志记录中进行了讨论。但不支持CustomDebugStringConvertible
。
现在看来,优雅的解决方案是扩展
OSLogInterpolation
以支持其他类型的插值(例如 CustomDebugStringConvertible
)。但是,尝试过之后,出现了一条编译器错误消息,表明他们已选择明确禁止这样做:
无效的日志消息;不支持扩展 os 模块中定义的类型
话虽如此,您可以编写一个
Logger
扩展来接受其他值/字符串,明确将 privacy
设置为 .private
:
import os.log
extension Logger {
public func error<T: CustomStringConvertible>(value: T) {
error("\(value, privacy: .private)")
}
public func error<T: CustomDebugStringConvertible>(value: T) {
error("\(value.debugDescription, privacy: .private)")
}
public func error(string: String) {
error("\(string, privacy: .private)")
}
…
}
您可以对
warning
、log
等重复此模式。
无论如何,你可以做这样的事情:
Logger.statistics.error(value: myCustom)
Logger.statistics.error(value: myDebug)
Logger.statistics.error(string: "My debug: \(myDebug)")
Logger.statistics.error(string: "\(noneOfTheAbove)")
Logger.statistics.error(value: optionalCustom)
Logger.statistics.error(value: optionalDebug)
话虽如此,我承认这不是我愿意采用的模式。有两个问题:
OSLogMessage
(而不是让这些方法采用String
参数)的整个激励思想是能够在更广泛的Logger
消息中为各个值指定隐私设置。您建议您对此表示同意,但不必要地丢失日志消息的这方面是一种耻辱。
Xcode 15 中我最喜欢的功能之一是能够control单击(或右键单击)Xcode 日志消息并选择“跳转到源代码”。一旦开始使用此功能,它就成为调试过程中非常宝贵的部分。 (例如,“嘿,Xcode 向我显示了一些我没有预料到的随机错误;让我跳到该代码并找出问题所在。”)
如果您调用
Logger
的内置方法,它将带您到代码中的适当位置。但是,如果您调用上述扩展方法之一,Xcode 会将您带到 Logger
扩展,而不是实际报告错误的位置。