Logging

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

Overview

About the Logging System

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, as shown in Table 1. These levels 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).

Table 1

System log levels

Level

Constant

Behavior

Default

OS_LOG_TYPE_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

OS_LOG_TYPE_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

OS_LOG_TYPE_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

OS_LOG_TYPE_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

OS_LOG_TYPE_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 an os_log function corresponding to the desired log level. Table 2 shows the main os_log functions. Provide a log object—the OS_LOG_DEFAULT constant or a custom log object created by calling the os_log_create function—and a constant string or format string representing the message. The OS_LOG_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. Calling an os_log function doesn’t always guarantee that a message is captured. This is governed by the current logging configuration for the level of message being sent.

Table 2

Primary Logging functions

Function

Description

os_log

Sends a default-level message to the logging system. See Listing 1.

os_log_info

Sends an info-level message to the logging system. See Listing 2.

os_log_debug

Sends a debug-level message to the logging system. See Listing 3. Note that debug-level messages are only captured when debug logging has been explicitly enabled via the log command-line tool or a custom logging profile. See Customizing Logging Behavior While Debugging.

os_log_error

Sends an error-level message to the logging system.

os_log_fault

Logs a fault-level message to the logging system.

Listing 1

Logging a default-level message

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

Logging an info-level message

os_log_info(OS_LOG_DEFAULT, "This is additional info that may be helpful for troubleshooting.");
Listing 3

Logging a debug-level message for a specific subsystem

os_log_t customLogObject;
customLogObject = os_log_create("com.your_company.your_subsystem_name.plist", "your_category_name");
if (customLogObject) {
    os_log_debug(customLogObject, "This is info that may be helpful during development or debugging.");
}

Messages can also be sent to the logging system by calling the os_log_with_type function and providing a log object, a log type constant (see os_log_type_t), and a message. In Listing 4, the os_log_with_type function sends an info-level message to the logging system.

Listing 4

Logging a message at a specified level

os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_INFO, "This is another way to log an info-level message.");

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 5. See String Format Specifiers and printf for formatting rules.

Listing 5

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

Table 3

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.

Viewing Log Messages in Console

Console, found in /Applications/Utilities/, includes a number of features designed to make viewing and interpreting log content efficient.

  • View all available logs in memory.

  • View a live stream of logging data.

  • View log data from connected devices.

  • View log files generated by legacy logging systems, such as ASL and the Syslog APIs.

  • Use the search field in the toolbar to filter for specific messages by activity, process, message content, subsystem, and more. Advanced search options let you configure search tokens and fine tune filtering behavior to produce more accurate results. See Figure 1.

  • Save commonly performed searches to the toolbar for quick access in the future.

  • Enable an activity view to see connections between related messages. See Figure 2.

  • View the contents of .logarchive files generated by the log command line tool’s collect argument.

Figure 1

Filtering log messages in Console

Figure 2

Viewing related log messages in Console

Viewing Log Messages from the Command Line

The log command-line tool provides options for viewing and filtering log data. Use the command’s show argument and specify the desired filtering options. Listing 11, for example, filters for messages logged by a specific process name. Listing 12 filters for messages logged by a specific subsystem. Listing 13 filters for messages logged by a specific category within a specific subsystem.

Listing 11

Filtering existing processes for a specific process name

$ log show --predicate 'process == "your_process_name"'
Listing 12

Filtering for a specific subsystem

$ log show --predicate 'subsystem == "com.your_company.your_subsystem_name"'
Listing 13

Filtering for a specific category within a specific subsystem

$ log show --predicate 'subsystem == "com.your_company.your_subsystem_name" and category == "your_category_name"'

For a complete list of the log command’s parameters, options, and use case examples, call man log from the command-line.

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

Listing 6

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

Listing 7

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

Listing 8

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

Listing 9

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

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.

Included Headers

  • <log.h>

Topics

Creating a Custom Log Object

os_log_create

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

Generating Log Messages

os_log

Sends a default-level message to the logging system.

os_log_info

Sends an info-level message to the logging system.

os_log_debug

Sends a debug-level message to the logging system.

os_log_error

Sends an error-level message to the logging system.

os_log_fault

Sends a fault-level message to the logging system.

os_log_with_type

Sends a message at a specific logging level, such as default, info, debug, error, or fault, to the logging system.

Determining Whether Logging is Enabled

os_log_info_enabled

Returns a Boolean value indicating whether info-level logging is enabled for a specified log object.

os_log_debug_enabled

Returns a Boolean value indicating whether debug-level logging is enabled for a specified log object.

os_log_type_enabled

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.

Constants

Log Objects

System-provided log objects that may be used when a custom log object isn’t needed.

os_log_type_t

Logging levels supported by the system.

Data Types

Enumerations

os_log_type_t

Logging levels supported by the system.