Wrap os Logger to programmatically enable / disable the output

Hello,

I am trying to wrap os Logger to programmatically enable / disable the output.

This works:

class MyLogger {
    private let logger = Logger(
        subsystem: "MyLogger",
        category: “MyCategory"
    )
    private var enabled: Bool = true
    func enable() { enabled = true }
    func disable() { enabled = false }
    
    @inline(__always) 
    func trace(_ s: String) { if enabled { logger.trace("\(s)") } }
}

However, it breaks Jump To Source functionality in the Xcode’s output window; it, obviously, navigates to my wrapper, NOT to the source.

As you can see, I’ve already tried @inline. It works in Release build, so it’s 50% of success :)

Is there any way to force inlining in Debug?

I briefly looked at macros, but got scared away :)

I'd question if wrapping OSLog is really a good idea. That circumvents all performance advantages OSLog offers and also, I think if you'd log something with logger.trace("\(s)") OSLogs privacy level is private by default, meaning that your log message would be entirely redacted.

Doesn't answer the question though.

2 Likes

You won‘t have much luck wrapping Logger (and you probably also shouldn’t) but luckily you don‘t have to. You can get a disabled logger instance like this: Logger(.disabled).
disabled | Apple Developer Documentation.

2 Likes

You could conditionally create a Logger that uses OSLog.disabled:

extension Logger {
    init(category: String) {
#if LOGGING
        self.init(subsystem: Bundle.main.bundleIdentifier!, category: category)
#else
        self.init(.disabled)
#endif
    }
}

And when a source file wants its own category for logging, you can do something like:

let logger = Logger(category: "ViewController")

That way you can define a “Other Swift Flags” build setting of -D LOGGING when you want the build to do logging, and in the absence of that, it won’t.

I'm not sure why Logger, itself, doesn't have a static disabled (like OSLog and OSSignposter do), but this is a work-around that I've used.

4 Likes

As others have mentioned, wrapping Logger is a bad idea because it defeats the compiler integration that gives the system log its runtime efficiency. One potential option here is to create a macro that wraps the API. I’ve seen folks try to tackle that but I’ve not seen anyone pull it off. If I had infinite time on my hands…

Anyway, another drawback to wrapping this is that you lose the private data support.

As to your high-level goal, why are you trying to enable and disable logging at this level? The general intention of the system log API — and the reason why efficiency is so important — is that you should leave your logs enabled in production code. Then select a log level whose default disposition matches the level of logging you expect. Finally, you can use outside mechanism to enable the more intense logging if necessary.

I have a bunch more info about this in Your Friend the System Log on DevForums.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

I have found it useful to sometimes disable logs in unit tests. I use a similar technique to what Robert posted above, but with a dependency injection framework around it.

1 Like

Another use case where it is desired to disable most logging is when developing a closed source SDK. The internal details contained in log messages should not be spilled to SDK users, and only select log messages should be produced.

1 Like

you can make the logger optional resolve the problem.

extension Logger {
    
    private static let _logger: Logger = .init(subsystem: "System", category: "category")
    static var logger: Logger? {
        guard isEnabled else { return nil }
        return _logger
    }
}

Logger.logger?.log("message")

Thank you @eskimo for your input!
I am happy to share my use case with you: I have an elaborate processing of a single piece of data, and use extensive logging in it.
Occasionally, I need to use it in a batch processing, for millions of records. Even using Filter in Xcode's output, the digging through those records is not really practical, so I wanted to disable logging for that batch.
I got everyone's point about wrapping, and I no longer do that.

That looks very elegant, thank you!

Thank you @robert.ryan , again, for your help!
I like this idea the best for its flexibility. Will use a Bool parameter to control enablement so that I can make this decision at run time.
Would you have a suggestion on how to expand this approach to control log verbosity? Like only emit error and fault?

@VladFein – Personally, I would use the filter bar in the Xcode console (in the lower right corner of the Xcode 16 console) for that. For example, enter “type:fault”, hit return and then enter “type:error” and then change the default “ALL” option to “ANY”. That will show you:

Filter

Unfortunately, this filter bar only lets you choose “ANY” or “ALL”, but not compound expressions.

1 Like

@eskimo Do you have examples of macro wrapping that you have seen at all? This is such a tiresome thing. "Don't wrap logs because we couldn't think of a way that allows you to centralize your logging needs."

Do you have examples of macro wrapping that you have seen at all?

No. My only experience with this is that I’ve talked with someone who tried to get it to work and failed. I don’t think there’s any fundamental reason why it’s impossible, but I don’t have time to go down that rabbit hole myself.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Fact of the matter is, os_log is really only designed to be used by Apple, in Apple systems. It can't be wrapped because their performance requirements are so high due to the fact they use it for everything, everywhere. You'll be much better served by using your own logging package, as I doubt Apple will ever release a version that properly allows wrapping while losing a tiny bit of performance that doesn't matter to 90% of app devs.

1 Like

Given the number of WWDC sessions dedicated to how we use it in our own code (e.g., Explore logging in Swift, Debug with structured logging, etc.), it is a little strong to suggest that it is “only designed to be used by Apple.”

That having been said, it certainly is designed for a very specific use-cases and you may well want to use different logging solutions for a broader array of needs. Unified logging offers some unique features not available elsewhere, but those other logging solutions likewise have features that Apple’s unified logging cannot match, either.

2 Likes

Note that saying "only designed to be used by Apple, in Apple systems" doesn't say anything about Apple's intent or what they imagine the community needs or wants. It's simply a statement on the utility of the system as designed.