OSLog and Unified logging as recommended by Apple

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 around it you make it fairly easy to replace your print statements with it.

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

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() {
    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: 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() {
    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 Antoine logged in
LogExample[7784:105423] [viewcycle] User Antoine 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 Antoine 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.

Devices menu in the Console.app
Devices menu in the Console.app

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:

Category filtering
Category filtering

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

Adding a subsystem as a filter
Adding a subsystem as a filter

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

Including info and debug messages to make them show up
Including info and debug messages to make them show up

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

An example of the logs inside the Console.app
An example of the logs inside the Console.app

Saving search patterns

To make your workflow faster, you can save your most common search patterns. They will end up in the subheader to quickly filter out logs and start debugging efficiently.

Saved search patterns in the sub-header
Saved search patterns in the sub-header to quickly filter your OSLog implementation

Further reading

WWDC this year included a dedicated session to logging, including performance logging APIs. You can watch the session here: Measuring Performance Using Logging.

For more in-depth documentation, check out the Apple docs on logging