I am trying to wrap os_log
for logging in my iOS app like so:
import Foundation
import OSLog
enum LogCategory: String, CaseIterable {
case viewCycle
case tracking
case api
}
struct Log {
private static let logs = {
return LogCategory.allCases
.reduce(into: [LogCategory: OSLog]()) { dict, category in
dict[category] = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "BIMB", category: category.rawValue)
}
}()
static func debug(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .debug, args)
}
static func info(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .info, args)
}
static func notice(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .default, args)
}
static func warning(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .default, args)
}
static func error(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .error, args)
}
static func critical(category: LogCategory, message: StaticString, _ args: CVarArg...) {
logImpl(category: category, message: message, type: .fault, args)
}
private static func logImpl(category: LogCategory, message: StaticString, type: OSLogType, _ args: CVarArg...) {
guard let log = logs[category] else {
return
}
os_log(message, log: log, type: type, args)
}
}
The problem is if I did this:
Log.debug(category: .tracking,
message: "Device ID: %s.",
UIDevice.current.identifierForVendor?.uuidString ?? "unknown")
it always crashed with this error:
2023-12-13 12:33:35.173798+0700 bimb-authenticate-ios[62740:928633] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Swift.__SwiftDeferredNSArray UTF8String]: unrecognized selector sent to instance 0x600000dcbbc0'
But if I just do it with os_log
like this:
os_log("Device ID: %s.",
log: OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "tracking"),
type: .debug,
UIDevice.current.identifierForVendor?.uuidString ?? "unknown")
it worked fine. Also if I changed %@
in my wrapper instead, it didn't crash, but the idfv is shown inside a pair of brackets like this:
Device ID: (
"C0F906C8-CD73-44F6-86A1-A587248680D3"
).`
But with os_log
it is shown normally like this: Device ID: C0F906C8-CD73-44F6-86A1-A587248680D3.
Can you tell me what's wrong here? And how do I fix this?
Thanks.
NOTE: This is using os_log
since the minimum version is iOS 11. I don't know why people advising me with using Logger
instead.
Your code is failing due to limitations in Swift’s ability to work with C varags functions. There’s simply no way to do the equivalent of what C does with the macros in <stdarg.h>
.
I don’t think your top-level goal is achievable. Both of our system log APIs, the os_log(…)
functions and the Logger
type, relying on special case handling within the compiler.
IMO the best path forward is to raise your deployment target and switch to Logger
. If you can’t do that, you should stick with calling os_log
directly. If you want to improve its ergonomics, I suggest you explore Swift macros.
Using Swift macros for this also presents significant challenges but it has the advantage that, if you can get the macro to expand properly, it’ll behave correctly at runtime.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"