12

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)

smithco
  • 679
  • 5
  • 17
  • Without actually knowing the os_log API, you're probably looking for `withVaList`? – jtbandes Sep 29 '17 at 23:40
  • Tried withVaList and failed :-( For reference, the os_log signature is `func os_log(_ message: StaticString, dso: UnsafeRawPointer? = #dsohandle, log: OSLog = default, type: OSLogType = default, _ args: CVarArg...)` – smithco Sep 29 '17 at 23:41
  • 2
    There is no magic syntax for passing a varargs to another variadic call. – Lily Ballard Sep 30 '17 at 00:24
  • I’m not sure you can pass an array to a `...` param. In any case, your function’s arg type would need to match (namely using CVarArg instead of Any) – jtbandes Sep 30 '17 at 00:27
  • After a couple more hours of searching, I suspect @KevinBallard is correct. Though, I'm still hoping someone has a magic bit of syntax for me by Monday so I can move on to my next task. – smithco Sep 30 '17 at 01:12

4 Answers4

9

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)
}
Leszek Zarna
  • 3,253
  • 26
  • 26
4

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 first answer here: Why does wrapping os_log() cause doubles to not be logged correctly?

pkamb
  • 33,281
  • 23
  • 160
  • 191
Kappe
  • 9,217
  • 2
  • 29
  • 41
3

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/

Julio Bailon
  • 3,735
  • 2
  • 33
  • 34
  • Do you consider that as workings solution ? I don't see this working with more than one variadic argument. – Lifely Jan 25 '19 at 18:42
1

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.

pkamb
  • 33,281
  • 23
  • 160
  • 191
smithco
  • 679
  • 5
  • 17
  • 8
    In Swift, `os_log` is a function implemented in the Swift framework overlays. It works very differently than the C macro. In C, calls to `os_log` actually have the format string parsed at compile-time. In Swift, it's parsed at runtime (in the framework overlay). Not only does this mean the parsing is different and therefore can behave differently (e.g. in Swift 3.0 it didn't respect the `public` modifier properly), but it also means it's slower. – Lily Ballard Oct 03 '17 at 17:22
  • Good info. I’m still new to Swift, so these low-level issues can be rather mysterious and frustrating. I assumed that it was a direct mapping to the C macros, but that obviously can’t be the case if it’s parsed at runtime. – smithco Oct 03 '17 at 17:25