Daily coverage of WWDC20.
A Swift by Sundell spin-off.

Meet the new Logger API

Published at 21:00 GMT, 25 Jun 2020
Written by: Gui Rambo

Logging can be an extremely helpful tool when debugging issues that arise while your app is running, without necessarily being connected to the debugger. Logs can also be useful in order to follow complex chains of actions across multiple subsystems to find out why an error occurred.

I’m a fan of using the os_log API to do just that, and I use it extensively in all of my projects. This year, with the introduction of Swift 5.3, Xcode 12 and iOS 14, Apple is improving that API significantly, especially if you’re using it in Swift.

This article covers some of what’s new in the Unified Logging APIs this year, but if you’d like to get an overview of the API itself, then check out this post from Ray Wenderlich.

The Logger type

When using the new API, instead of creating an OSLog object and then referencing it in calls to os_log, you can now use the new Logger type.

As an example, let’s say that I’m creating a component in my app that downloads a feed from the internet. When doing that, I might want to log some information about its functionality so that it’ll be easier for me to see what’s going on when debugging. For that, I could create a Logger, like this:

import Foundation
import os.log

final class FeedDownloader {
    private let logger = Logger(
        subsystem: "com.WWDCBySundell.app",
        category: "FeedDownloader"
    )
}

In general, I like to use the bundle identifier of the module where the code lives as the logging subsystem, and the name of the class as the category. To improve on that pattern, you could create a helper function that generate a logger by automatically configuring its properties based on the current bundle and class.

Now that a Logger instance is available within my class, I can use the methods corresponding to each of the different log levels to create log messages. In the method where the feed is downloaded, I’ll log an error when something went wrong, and the UUID of the loaded feed when it was fetched successfully:

func fetch() {
    downloadContents { [unowned self] result in
        switch result {
        case .failure(let error):
            self.logger.error("Error downloading feed contents: \(error, privacy: .public)")
        case .success(let contents):
            self.logger.info("Feed downloaded. Contents UUID is \(contents.uuid, privacy: .private(mask: .hash))")
            self.contents = contents
        }
    }
}

As you can see, when using the new Logger type, we can freely interpolate runtime values within our log messages, without having to use format strings. This is leveraging the custom string interpolation feature introduced in Swift 5. All of this is optimized by the compiler to ensure that our logging doesn’t end up having any noticeable impact on performance.

In my example, I’m indicating that the error message is .public using the privacy parameter. For the UUID, I’m using the new .private(mask:) option, which allows me to log a potentially sensitive value as a hash, so that I can check for equality when reading my logs without compromising the actual contents of the value in question.

Reading the logs

Just like logs produced by the old os_log API, you can read logs produced by the Logger type within Xcode’s console while debugging (in which case the logging system will ignore the privacy property and display everything as if it was public), or you can use the Console app, which has very powerful filtering options. In the example below, I’m filtering my logs based on the subsystem name that I defined earlier:

A screenshot of the Console with the search bar filtering for the subsystem com.WWDCBySundell.app

If you’d like to gather logs from a device, even when your app is not running anymore, you can use the log command on your Mac with the collect option:

sudo log collect --device --start "2020-06-25 16:10:00" --output myapp.logarchive

The logarchive file that you get can then be opened in the Console app and filtered just like live logs can.

Conclusion

I’ve always been a fan of using the Unified Logging API, and with this year’s introduction of the Logger type, it feels completely native in Swift. I hope this article has been helpful to you, and like always, reach out on Twitter for comments or questions.

Written by: Gui Rambo