Logging

Efficiently capture log messages to memory and disk. Manage logging behavior and persistence.

Overview

The unified logging system provides a single, efficient, performant API for capturing messaging across all levels of the system. This unified system centralizes the storage of log data in memory and in a data store on disk. The system implements global settings that govern logging behavior and persistence, while at the same time providing fine-grained control during debugging via the log command-line tool and through the use of custom logging configuration profiles. Log messages are viewed using the Console app in /Applications/Utilities/ and the log command-line tool. Logging and activity tracing are integrated to make problem diagnosis easier. If activity tracing is used while logging, related messages are automatically correlated.

Log Levels

There are several log levels employed by the unified logging system, which correspond to the different types of messages your app may need to capture, and define when messages are saved to the data store and how long they persist. The system implements standard behavior for each level. This behavior can be overridden using the log command-line tool or a custom configuration profile (see Customizing Logging Behavior While Debugging).

Default

Default-level messages are initially stored in memory buffers. Without a configuration change, they are compressed and moved to the data store as memory buffers fill. They remain there until a storage quota is exceeded, at which point, the oldest messages are purged. Use this level to capture information about things that might result a failure.

Info

Info-level messages are initially stored in memory buffers. Without a configuration change, they are not moved to the data store and are purged as memory buffers fill. They are, however, captured in the data store when faults and, optionally, errors occur. When info-level messages are added to the data store, they remain there until a storage quota is exceeded, at which point, the oldest messages are purged. Use this level to capture information that may be helpful, but isn’t essential, for troubleshooting errors.

Debug

Debug-level messages are only captured in memory when debug logging is enabled through a configuration change. They’re purged in accordance with the configuration’s persistence setting. Messages logged at this level contain information that may be useful during development or while troubleshooting a specific problem. Debug logging is intended for use in a development environment and not in shipping software.

Error

Error-level messages are always saved in the data store. They remain there until a storage quota is exceeded, at which point, the oldest messages are purged. Error-level messages are intended for reporting process-level errors. If an activity object exists, logging at this level captures information for the entire process chain.

Fault

Fault-level messages are always saved in the data store. They remain there until a storage quota is exceeded, at which point, the oldest messages are purged. Fault-level messages are intended for capturing system-level or multi-process errors only. If an activity object exists, logging at this level captures information for the entire process chain.

Performing Logging

To send a message to the logging system, call the os_log function and optionally pass a log object and a log level. Provide a log object—the default constant or a custom OSLog object—and a constant string or format string representing the message. The default constant causes logging to occur in accordance with the system’s standard behavior. A custom log object causes logging to occur according to settings contained within a logging profile for a specific subsystem.

Listing 1

Logging a default-level message

os_log("This is a log message.")
Listing 2

Logging an info-level message

os_log("This is additional info that may be helpful for troubleshooting.", log: OSLog.default, type: .info)
Listing 3

Logging a debug-level message for a specific subsystem

let customLog = OSLog(subsystem: "com.your_company.your_subsystem_name.plist", category: "your_category_name")
os_log("This is info that may be helpful during development or debugging.", log: customLog, type: .debug)

Viewing Log Messages

Use the Console app or the log command-line tool to view and filter log messages.

Customizing Logging Behavior While Debugging

Logging behavior is normally governed by the system. However, while debugging in macOS, you can enable different logging levels for a subsystem using the log command-line tool’s config argument while logged in as root. See Listing 4, which shows how to enable debug-level logging for a subsystem.

Listing 4

Enabling debug-level logging for a subsystem

$ sudo log config --mode "level:debug" --subsystem com.your_company.your_subsystem_name

Use the log tool’s status argument to check the current logging level of a subsystem. See Listing 5.

Listing 5

Checking the log level of a subsystem

$ sudo log config --status --subsystem --subsystem com.your_company.your_subsystem_name
Mode for 'com.your_company.your_subsystem_name'  DEBUG

You can also override the logging behavior of a specific subsystem by creating and installing a logging configuration profile property list file in the /Library/Preferences/Logging/Subsystems/ directory. Name the file using an identifier string, in reverse DNS notation, representing the subsystem. For example, com.your_company.your_subsystem_name.plist. Next, add one or more settings dictionaries to the top level of the file. A DEFAULT-OPTIONS settings dictionary defines global behavior settings for the entire subsystem. Category settings dictionaries define behavior for specific categories of messages within the subsystem. See Listing 6.

Listing 6

Top level structure of a logging profile

<dict>
    <key>DEFAULT-OPTIONS</key>
    <dict>
       <!-- GLOBAL SUBSYSTEM OR PROCESS SETTINGS -->
    </dict>
    <key>CategoryName</key>
    <dict>
       <!-- CATEGORY SETTINGS -->
    </dict>
</dict>

Each settings dictionary within a logging profile contains a Level subdictionary, which contains the following setting keys:

Key

Description

Enable

Enables a specific log level.

Persist

Controls whether messages are stored in memory and then saved to the data store, or stored in memory only.

The Enable key and Persist key both accept the following string values:

Value

Description

Inherit

Explicitly states that the subsystem or category inherits the behavior of its parent. In the case of a category, the parent is the subsystem. In the case of a subsystem, the parent is the system.

Default

Only default-level messages are captured.

Info

Default-level and info-level messages are captured.

Debug

Default-level, info-level, and debug-level messages are captured.

Listing 7 shows an example of a Level subdictionary that enables info-level logging that inherits the persistence behavior of a subsystem or the system.

Listing 7

Example of a Level subdictionary in a logging profile settings dictionary

<key>Level</key>
<dict>
    <key>Enable</key>
    <string>Info</string>
    <key>Persist</key>
    <string>Inherit</string>
</dict>

Listing 8 shows an example of a complete logging profile, which configures a subsystem to perform info-level logging and a server-connections category within the subsystem to perform debug-level logging.

Listing 8

Example of a complete logging profile

<dict>
    <key>DEFAULT-OPTIONS</key>
    <dict>
        <key>Level</key>
        <dict>
            <key>Enable</key>
            <string>Info</string>
            <key>Persist</key>
            <string>Inherit</string>
        </dict>
    </dict>
    <key>server-connections</key>
    <dict>
        <key>Level</key>
        <dict>
            <key>Enable</key>
            <string>Debug</string>
            <key>Persist</key>
            <string>Inherit</string>
        </dict>
    </dict>
</dict>

Logging Best Practices

Follow these guidelines to produce useful and efficient log messages.

  • Use format strings and specifiers whenever possible to automatically produce user-friendly log messages instead of trying to write custom formatting code. See Formatting Log Messages.

  • Don’t include symbolication information or source file line numbers in messages. The system automatically captures this information.

Topics

Creating a Custom Log Object

init(subsystem: String, category: String)

Creates a custom log object, to be passed to logging functions for sending messages to the logging system.

Generating Log Messages

func os_log(StaticString, dso: UnsafeRawPointer, log: OSLog, type: OSLogType, CVarArg...)

Sends a message to the logging system, optionally specifying a custom log object, log level, and any message format arguments.

Determining Whether Logging is Enabled

func isEnabled(type: OSLogType)

Returns a Boolean value indicating whether a specific type of logging, such as default, info, debug, error, or fault, is enabled for a specified log object.

Classes

class OSLog

A custom log object that can be passed to logging functions in order to send messages to the logging system.

Enumerations

struct OSLogType

Logging levels supported by the system.