Clear and searchable logging in Swift with OSLog

Exploring Apple’s currently recommended logging approach

Andrew Lord
Jan 14, 2019 · 6 min read

When it comes to logging in Swift and iOS applications in particular, the APIs that first come to mind might be print and NSLog. More recently, however, Apple has introduced a new standard for logging in the form of unified logging, accessed via OSLog. It is the current recommended way of logging, providing an efficient way to capture information across our applications.

Unified logging provides a number of improvements over previous techniques and also some differences to what we are used to.

  1. Each message can be logged at an appropriate level, including: default, error, debug and info. They affect how messages are displayed to us and persisted.
  2. Messages are grouped within subsystems and categories to enable efficient searching and filtering.
  3. There is no need to wrap log statements in conditionals, due to the system being designed for performance and logs being rendered only when read.
  4. User privacy is carefully respected, with dynamic string content needing to be explicitly marked as public, else they are redacted in any logs.

Before we continue: Please check out the article on my blog, Lord Codes, you will find code snippets with themed syntax highlighting and much more, it is definitely my preferred way to read it! 👍

Let’s get logging

Using unified logging from Swift is as simple as using the os_log function, which we will quickly notice takes a StaticString as an argument rather than a regular String. The easiest way to log messages, is to place the String constant directly in the function call. Extracting the message to a property is possible, however, we will need to define its type as StaticString.

Due to the StaticString requirement, instead of using string interpolation we will need to use format arguments. This may feel jarring initially, however, it isn’t too hard to adapt to and it provides benefits to user privacy that we will discuss a bit later on. We have access to all of the standard format arguments, as well as a number of extra value type decoders. The idea is that the logging system handles as much formatting for you as possible, to make the system even more efficient.

It’s good to be organised

Calls to os_log can specify an OSLog to use, containing a specific subsystem and category. This information is invaluable when filtering, searching and trying to understand our logs later. When the log argument isn’t specified a default one is used, which has no subsystem or category configured.

The subsystem groups all the logs for a particular app or module, allowing us to filter for all of our own logs. From evaluating Apple’s logs, the convention for subsystem is a reverse domain style, such as the Bundle Identifier of the app or framework itself. If the app is modularised into frameworks, it is a good idea to use the Bundle Identifier of the framework to split logs into their corresponding components.

Categories are used to group logs into related areas, to help us narrow down the scope of log messages. The convention for categories is to use human-readable names like UI or User. We could group logs into layers across multiple subsystems or features, such as Network or Contacts. Alternatively, we could group all the logs for a particular class, such as Contacts Repository. It would be perfectly acceptable to combine both approaches in the same project, we should simply use the most appropriate categories to allow us to understand the context of the project’s log messages.

We can add our different categories and subsystems as an extension of OSLog, making them easily accessible across the app. Storing them in one place avoids creating OSLog instances all over the codebase and helps keep the different categories in use nicely organised.

Logging levels

The unified logging system employs a set of different logging levels at which we can target different types of messages. The levels control how messages are displayed to us, how and when they are persisted and whether they are captured in different environments. How the system handles each level can even be customised through the command-line on our machine. It is a good idea to use the most appropriate logging level for each message, to get the most we can out of the logging system.

Default: To capture anything that might result in a failure and essentially a fall-back if no other level seems appropriate. Unless changed, messages are stored in memory buffers and persisted when they fill up.

Info: To capture anything that may be useful, but is not directly used to diagnose or troubleshoot errors. Unless changed, no persistence is used, messages are just stored in memory buffers and purged as they fill up.

Debug: To capture information during development to diagnose a particular issue, whilst actively debugging. They aren’t captured unless enabled by a configuration change.

Error: To capture application errors and failures, in particular anything critical. Messages are always persisted, to ensure they are no lost.

Fault: To capture system-level or multi-process errors only, likely not of use in our app code. As with the error level, messages are always persisted.

Logging at each level is as simple as specifying the corresponding OSLogType as an argument to the os_log call.

User privacy

To ensure private user data is not accidentally persisted to application logs, which may be shared with other people, the unified logging system has a public and private argument process. By default, only scalar (boolean, integer) values are collected and dynamic strings or complex dynamic objects are redacted. If it is necessary, dynamic string arguments may be declared public and scalar arguments could also be declared private.

It is important we resist making all arguments public, as it could easily result in private company or user data being exposed within device logs.

Reading logs

Whilst the debugger is attached, log messages will be shown in the Xcode console. The best way to read our logs, however, is with the Console MacOS application. Here we will be able to sort, filter and search our logs, as well as view them more easily.

  • Display logs in a table, making each piece of data easy to read
  • Search and filter by subsystem and category
  • Show and hide different fields for each log message
  • Turn on and off debug and info level messages
  • Save search patterns to access them more easily in the future

Wrap up

Unified logging is a promising and powerful logging solution, especially when it comes to performance and filtering your log messages. Initially it might seem like it has quirks or differences to what you may be used to from NSLog and print. After looking a bit more and providing os_log with subsystems, categories and making good use of logging levels, you will find it makes working with logs significantly easier. If you want more log coverage with better performance, you can ditch NSLog and print statements and start integrating os_log into your apps.

OSLog is Apple’s current recommended logging approach

There is much more to OSLog than we have explored here, such as signposts to monitor app performance and so it is likely we come back to this topic in another article in the future.

Have you started using OSLog, if so what do you think of it? If you aren’t going to use it, what are your reasons for making that decision? If you have any suggestions for other developers trying to use OSLog I would love to hear about them. Please feel free to reach out to me on Twitter @lordcodes with any questions or thoughts you have, or about anything else.

If you like what you have read, please don’t hesitate to share the article and subscribe to my feed if you are interested.

Thanks for reading and happy coding! 🙏

Swift Programming

The Swift Programming Language

Andrew Lord

Written by

Lead Mobile Developer @GetBusyHQ. Builder of Android and iOS apps. Blogs at . Coder, avid gamer and music fan.

Swift Programming

The Swift Programming Language

Andrew Lord

Written by

Lead Mobile Developer @GetBusyHQ. Builder of Android and iOS apps. Blogs at . Coder, avid gamer and music fan.

Swift Programming

The Swift Programming Language

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store