Reputation: 729
I am trying to write a convenience wrapper for os_log in Swift 4 / iOS 11, but I've run into an uphill battle with passing the variadic arguments.
Basically, I want to write a function that looks like the following.
static let logger = OSLog(subsystem: "com.example.foo", category: "foobar")
func logError(_ message: StaticString, _ args: Any...) {
os_log(message, log: logger, type: .error, args)
}
Unfortunately, I can't seem to figure out the magic syntax to get the arguments passed along and have gotten a bit lost in the quagmire of CVarArg discussions.
(... this makes me miss Python's splatting syntax)
Upvotes: 13
Views: 3662
Reputation: 9495
Your idea contains a couple of issues:
Apple discourages the wrapping of os_log
in another function, doing so results in losing some nice feature of the Unified Logging System, like having the code line, library, file etc in the logs automagically.
As soon as args
is passed to your own function that type pass from cvargs to [String] and in theory it's impossible re-build the list of args, you can find an amazing explanation in the answer here: https://stackoverflow.com/a/50942917/465916
Upvotes: 4
Reputation: 729
Did some more research on this. Turns out that os_log
is actually a C macro. This created all sorts of problems with how it maps in to Swifts variadic args.
However, that macro also captures other debugging info and is probably not safe to wrap up anyways.
Upvotes: 2
Reputation: 3823
This is what I am using to wrap os_log:
import Foundation
import os.log
protocol LogServicing: class {
func debug(_ message: StaticString, _ args: CVarArg...)
func info(_ message: StaticString, _ args: CVarArg...)
func error(_ message: StaticString, _ args: CVarArg...)
}
enum LogType {
case debug
case info
case error
case fault
}
class LogService: LogServicing {
private var osLog: OSLog?
let subsystem: String
let category: String
init(subsystem: String = Bundle.main.bundleIdentifier ?? "", category: String = "") {
if #available(iOS 10.0, *) {
let osLog = OSLog(subsystem: subsystem, category: category)
self.osLog = osLog
}
self.subsystem = subsystem
self.category = category
}
func log(type: LogType, message: StaticString) {
log(type: type, message: message, "")
}
func log(type: LogType, message: StaticString, _ args: CVarArg...) {
if #available(iOS 10.0, *) {
guard let osLog = osLog else { return }
let logType: OSLogType
switch type {
case .debug:
logType = .debug
case .error:
logType = .error
case .fault:
logType = .fault
case .info:
logType = .info
}
os_log(message, log: osLog, type: logType, args)
print(message, args)
} else {
NSLog(message.description, args)
}
}
func debug(_ message: StaticString, _ args: CVarArg...) {
log(type: .debug, message: message, args)
}
func info(_ message: StaticString, _ args: CVarArg...) {
log(type: .info, message: message, args)
}
func error(_ message: StaticString, _ args: CVarArg...) {
log(type: .error, message: message, args)
}
}
And I created it like this:
self.logService = LogService(subsystem: "com.softbolt.app", category: "network")
And use it like this:
self.logService.info("HttpResponse %{public}@", url)
If you want to know more about os_log and the benefits of private and public logging check this link:
https://www.testdevlab.com/blog/2018/04/how-to-create-categorize-and-filter-ios-logs/
Upvotes: 3
Reputation: 3317
I also haven't found solution yet so made this silly hack:
switch args.count {
case 0:
os_log(message, log: log!, type: type)
case 1:
os_log(message, log: log!, type: type, args[0])
case 2:
os_log(message, log: log!, type: type, args[0], args[1])
case 3:
os_log(message, log: log!, type: type, args[0], args[1], args[2])
default:
os_log(message, log: log!, type: type, args)
}
Upvotes: 10