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)

Privacy

The unified logging system considers dynamic strings and complex dynamic objects to be private, and does not collect them automatically. To ensure the privacy of users, it is recommended that log messages consist strictly of static strings and numbers. In situations where it is necessary to capture a dynamic string, you may explicitly declare the string public using the keyword public. For example, %{public}s.

Formatting Log Messages

To format a log message, use a standard NSString or printf format string, as shown in Listing 4. See String Format Specifiers for formatting rules.

Listing 4

Logging a message using a format string

os_log(OS_LOG_DEFAULT, "Downloaded a file. Size: %zd", fileSize);

In addition to standard format string specifiers, such as %@ and %d, the logging system supports custom decoding of values by denoting value types inline in the format %{value_type}d. In addition, the specifier %.*P can be used to decode arbitrary binary data. The system includes a number of built-in value type decoders, shown in Table 1.

Table 1

Builtin value type decoders

Value type

Custom specifier

Example output

time_t

%{time_t}d

2016-01-12 19:41:37

timeval

%{timeval}.*P

2016-01-12 19:41:37.774236

timespec

%{timespec}.*P

2016-01-12 19:41:37.2382382823

errno

%{errno}d

Broken pipe

iec-bytes

%{iec-bytes}d

2.64 MiB

bitrate

%{bitrate}d

123 kbps

iec-bitrate

%{iec-bitrate}d

118 Kibps

uuid_t

%{uuid_t}.*16P

%{uuid_t}.*P

10742E39-0657-41F8-AB99-878C5EC2DCAA

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 5, which shows how to enable debug-level logging for a subsystem.

Listing 5

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 6.

Listing 6

Checking the log level of a subsystem

$ sudo log config --status --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 7.

Listing 7

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 8 shows an example of a Level subdictionary that enables info-level logging that inherits the persistence behavior of a subsystem or the system.

Listing 8

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 9 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 9

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 symbol 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) -> Bool

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.

Debugging CPU Performance

func os_signpost(OSSignpostType, dso: UnsafeRawPointer, log: OSLog, name: StaticString, signpostID: OSSignpostID)

Marks a point of interest in your code as a time interval or as an event for debugging performance in Instruments.

func os_signpost(OSSignpostType, dso: UnsafeRawPointer, log: OSLog, name: StaticString, signpostID: OSSignpostID, StaticString, CVarArg...)

Marks a point of interest in your code as a time interval or as an event for debugging performance in Instruments, and includes a detailed message.

struct OSSignpostID

An identifier you use to distinguish signposts that have the same name and that log to the same OSLog.

struct OSSignpostType

The values that determine the role of a signpost.

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.

See Also

Logs

Activity Tracing

Log trace messages to an auto-generated ring buffer while associating them with the originating user action.