Spring 2024 | Vin Bui

As developers, we are often faced with bugs that require us to debug our code. The most common way to do this (and the way it is taught in many CS courses at Cornell) is to use print statements. Although print statements are a very powerful tool to help debug our code, we can go beyond that and use a library called OSLog.

What is OSLog?

OSLog is Apple’s recommended library for logging and is a replacement for print statements and NSLog. OSLog allows us to mark different logging levels such as “warning” and “error” as well as structuring our app’s logging into categories that we can create. Additionally, it works perfectly with Xcode 15’s new logging console, enriching our debugging experience even further.

Getting Started with OSLog

To get started, let’s create a file in our project directory called Logger.swift. Inside of the file, add the following code:

import OSLog

extension Logger {

    /// The logger's subsystem.
    private static var subsystem = Bundle.main.bundleIdentifier!

    // NOTE: Replace the categories below with your own choosing.

    /// All logs related to data such as decoding error, parsing issues, etc.
    static let data = Logger(subsystem: subsystem, category: "data")

    /// All logs related to services such as network calls, location, etc.
    static let services = Logger(subsystem: subsystem, category: "services")

    /// All logs related to tracking and analytics.
    static let statistics = Logger(subsystem: subsystem, category: "statistics")

  1. We first import this library with import OSLog in order to use all functionality provided by it.

  2. We then create an extension of the Logger class which is already defined in OSLog.

  3. Every Logger instance requires two things: a subsystem and category.

    1. We define a static variable representing our subsystem which should be unique. The best way to unsure uniqueness is to use our bundle identifier.

    2. We can then create our Logger instances with a category of our choice. The categories shown above are some common ones.

Writing Logs

To write a log, we can use the following syntax:

Logger.services.info("Location Restricted")

We use the static variable services that we created earlier (which is a Logger object), followed by a log level (in this case info). There are many log levels that we can choose from, each being displayed differently in the Xcode console.

  • default (notice): The default log level which should be avoided. Our logs should be more specific.

  • info: Log information that may be helpful but is not necessary for debugging.

  • debug: We use this level during development while actively debugging.

  • trace: Same as debug but for tracing the program flow of our code.

  • warning: Warning-level messages that do not cause any failure or critical errors.

  • error: Error-level messages for critical errors and failures.

  • fault: Fault-level messages for system-level or multi-process errors.

  • critical: Same as fault.

We also need to make sure that our console has the proper Metadata selected to display the logs properly:

Data Privacy

When logging, we need to consider data privacy. For example, if we wanted to log a user’s birthday, we can use the following code:

let birthday = "February 6, 2004"
Logger.data.info("User's birthday is \(birthday, privacy: .private)")

We use string interpolation to output the value of the birthday constant and mark it as private. We want to do this to prevent external apps such as Console to be able to view sensitive information in our logs.

Console App

For more advanced logging, we can use the Console app to read our logs. The Console app supports optimized logging structures with alignments which improves readability. Additionally, we can filter logs from multiple devices, categories, and log levels. Learn more about logging in the Apple Documentation.

Last updated