smithco
smithco

Reputation: 729

Passing variadic args in Swift 4 for os_log

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

Answers (4)

Kappe
Kappe

Reputation: 9495

Your idea contains a couple of issues:

  1. 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.

  2. 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

smithco
smithco

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

Julio Bailon
Julio Bailon

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

Leszek Zarna
Leszek Zarna

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

Related Questions