OSLog and Unified logging: Optimised

Shobhit Gupta
5 min readOct 28, 2020

OSLog as a replacement of print and NSLog is the recommended way of logging by Apple. It’s a bit harder to write, but it comes with some nice advantages compared to it’s better-known friends.

By writing a small extension you make it fairly easy to replace your print statements. Using the Console app in combination with your logs can help you debug issues in a more efficient way. OSLog has a low-performance overhead and is archived on the device for later retrieval. These are two of the advantages of using OSLog instead of print statements.

Setting up OSLog

OSLog makes it possible to log per category, which can be used to filter logs using the Console app. By defining a small extension you can easily adopt multiple categories.

Note: if you’re using iOS 14 and up, there are new APIs available explained further down this post.

import os.log

extension OSLog {
private static var subsystem = Bundle.main.bundleIdentifier!

/// Logs the view cycles like viewDidLoad.
static let viewCycle = OSLog(subsystem: subsystem, category: "viewcycle")
}

This extension uses the bundle identifier of the app and creates a static instance for each category. In this case, we have a view cycle category, which we can use to log in our app:

override func viewDidLoad() {
super.viewDidLoad()
os_log("View did load!", log: OSLog.viewCycle, type: .info)
}

Log levels

The OSLog API requires to pass in an OSLogType which can be used to automatically send messages at the appropriate level. A log type controls the conditions under which a message should be logged and is another way of filtering in the Console app.

  • default (notice): The default log level, which is not really telling anything about the logging. It’s better to be specific by using the other log levels.
  • info: Call this function to capture information that may be helpful, but isn’t essential, for troubleshooting.
  • debug: Debug-level messages are intended for use in a development environment while actively debugging.
  • error: Error-level messages are intended for reporting critical errors and failures.
  • fault: Fault-level messages are intended for capturing system-level or multi-process errors only.

You can pass in a log level as a type parameter:

/// We're logging an .error type here as data failed to load.
os_log("Failed loading the data", log: OSLog.data, type: .error)

Logging parameters

Parameters can be logged in two ways depending on the privacy level of the log. Private data can be logged using %{private}@ and public data with %{public}@.

In the following example, we’re logging the username in both public and private to show the differences.

override func viewDidLoad() {
super.viewDidLoad()
os_log("User %{public}@ logged in", log: OSLog.userFlow, type: .info, username)
os_log("User %{private}@ logged in", log: OSLog.userFlow, type: .info, username)
}

The Xcode console and the Console.app will show the data as normal when a debugger is attached.

LogExample[7784:105423] [viewcycle] User shobhitlogged in
LogExample[7784:105423] [viewcycle] User shobhit logged in

However, opening the app while no debugger is attached will show the following output in the Console.app.

debug   18:58:40.532132 +0100   LogExample  User shobhit logged in
debug 18:58:40.532201 +0100 LogExample User <private> logged in

The username is logged as <private> instead which prevents your data from being readable by anyone inside the logs.

Reading logs with the Console.app

Using the Console.app in combination with OSLog is recommended to get the most out of this way of logging.

Start by selecting your device on the left in the devices menu. Simulators and connected devices will show up in this list.

After selecting your device you can start entering a keyword in the search field, after which an option appears as any inside a drop-down menu.

This is the place in which you can filter on your category:

We could go even further if this isn’t enough filtering by passing in the subsystem:

Make sure to include info and debug messages by enabling them from the action menu, so all your messages show up:

This should be enough to get you started with reading logs inside the Console.app.

Improved APIs in iOS 14 and up

WWDC 2020 introduced improved APIs that make it even easier to work with OSLog. The APIs look much more similar to popular frameworks like CocoaLumberjack and are better aligned with other Swift APIs.

All previous covered explanations in this blogpost still apply and the code examples are still working on iOS 14. However, if you’re supporting iOS 14 and up you might want to go with the improved APIs as they look nicer and come with a few new features.

Using a Logger instance

One of the differences is using the newly introduced Logger instance. The initialiser matches the one from OSLog:

extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!
/// Logs the view cycles like viewDidLoad.
static let viewCycle = Logger(subsystem: subsystem, category: "viewcycle")
}

The differences are visible when trying to log messages as you now have to use methods like info(_:) and debug(_:):

Logger.viewCycle.info("View did load!")

Removed restriction of static strings

A big improvements is the support for string interpolation and string literals. With the old API, it isn’t possible to use string interpolation which makes it harder to log values. With the new APIs you can log as you’re used to with print(_:)statements:

Logger.viewCycle.debug("User \(username) logged in")

Setting the right privacy level

When I used the old API it often happened that I forgot about the %{public}@syntax. In fact, I’ve been often writing it wrong with uppercase PUBLIC, for example.

With the new API we can make use of a better discoverable enum to set the right privacy level:

Logger.viewCycle.debug("User \(username, privacy: .private) logged in")

The string interpolation support is very useful here as we can decide the privacy level per logged value.

Further reading

WWDC often includes dedicated sessions to logging, including performance logging APIs. You can watch the sessions here:

--

--

Shobhit Gupta

Sr iOS Engineer, Bharti Airtel Limited, Technology Lover