os_log is Apple’s unified logging API for iOS and macOS that replaced NSLog and os_trace. Unlike older mechanisms, os_log works at the kernel level: messages are buffered in a ring buffer and written to disk only when an activity threshold is reached. According to Apple WWDC 2016, os_log reduces disk load by 10 times compared to NSLog and provides control over detail level through categories and types. It is the primary diagnostic tool for iOS developers: through Console.app you can filter messages by process, category, and severity level in real time.
Key Takeaways
os_log is a unified logging API introduced by Apple in iOS 10 and macOS Sierra. It combined the disparate logging mechanisms NSLog, os_trace, and syslog into a single system with buffering at the XNU kernel level.
Unlike NSLog, which synchronously writes every message to disk and blocks the thread, os_log uses an asynchronous ring buffer in memory. Messages are flushed to disk only when activity exceeds a set threshold or upon a log collect command. This radically reduces the impact of logging on application performance.
os_log supports six severity levels, subsystem and category differentiation, and a built-in privacy mechanism: data marked as private is automatically masked in production logs and is only available to the developer when connected via Xcode.
Before iOS 10, developers used NSLog for debugging and syslog for system messages. NSLog wrote to stderr and the console, but was extremely inefficient: each message was synchronously written to disk, causing UI delays with frequent logging. os_log solved this problem by moving buffering to the BSD portion of the XNU kernel and making disk writes asynchronous.
os_log is used in all Apple applications and is recommended by Apple as the only logging API for iOS, macOS, tvOS, and watchOS. The system and third-party applications use it to write messages to a unified database — it is stored in memory and periodically flushed to disk. These logs can be analyzed through Console.app on Mac or via the log command in Terminal.
The os_log architecture consists of three layers: a client-side API in user space (libsystem_trace.dylib), a ring buffer in the XNU kernel, and the logd daemon that asynchronously flushes the buffer to disk.
When an application calls os_log, the message is copied into a kernel ring buffer of several megabytes in size. The buffer operates on a FIFO basis: if it is full, older messages are overwritten by newer ones. The logd daemon periodically checks the buffer and saves messages to .tracev3 files in a protected area of the file system.
According to Apple Engineering, the typical delay from calling os_log to seeing the message in Console.app is 1–5 seconds on a device and up to 60 seconds when flushing to disk in batch mode. This is a deliberate trade-off: application performance is not affected by logging, but the developer sees messages with a slight delay.
// os_log Declaration via OSLog
import OSLog
let logger = Logger(
subsystem: "com.example.app",
category: "network"
)
The ring buffer of os_log has a fixed size and cannot be changed from user space. The buffer size ranges from 256 KB on Apple Watch to 4 MB on Mac. When an application generates more messages than the buffer can hold, older messages are lost — this is expected behavior for high-volume logging.
For long-term collection of all messages, the log collect command is used. It launches a collection daemon on the device and exports a .logarchive to the developer’s computer. In this mode, the buffer is not overwritten — messages are written directly to the archive.
os_log supports five severity levels, each responsible for a different type of message and processed differently by the system. Default is the baseline for messages that always enter the buffer. Info and Debug are disabled in production builds without a collection profile. Error and Fault are always active and are marked with a special flag in the database.
| Level | Meaning | Buffer Entry by Default |
|---|---|---|
| Default | Regular messages important for diagnostics | Yes |
| Info | Informational messages for detailed analysis | No (profile only) |
| Debug | Debug messages for development | No (profile only) |
| Error | Errors that require attention | Yes |
| Fault | Critical failures leading to crashes | Yes |
Choosing the correct severity level is important for performance: Info and Debug are not written to disk in normal mode, so they can be used liberally without slowing down the application. Error and Fault are always saved, but their quantity should be minimal — each such message increases write time due to additional metadata.
Subsystem is an application or module identifier in reverse-DNS format (com.example.app). Category is a string label within a subsystem that groups logs by functional areas: network, ui, database, auth. This hierarchy allows filtering logs without reading each message and collecting statistics for each module separately.
Apple recommends defining one OSLog per module and using it across all files of that module. For different application layers — networking, UI, persistence — separate categories should be created. Then in Console.app you can enable logs only for network and disable for others without recompiling the application.
import OSLog
extension Logger {
static let network = Logger(
subsystem: "com.example.app",
category: "network"
)
static let ui = Logger(
subsystem: "com.example.app",
category: "ui"
)
}
os_log provides a built-in privacy control mechanism: each value in a format string can be marked as public, private, or auto (default behavior). By default, os_log considers all dynamic strings and objects potentially sensitive and replaces them with the <private> mask in production logs.
This is critical for GDPR and HIPAA compliance: if an application logs a user’s email or card number via os_log in auto mode, the actual data never reaches the disk. The developer sees the full message only when connected via Xcode or when using a collection profile from a device connected to the same Mac.
let email = "user@example.com"
logger.log("User login: \(email, privacy: .public)")
// In production logs: "User login: "
// In Xcode debugging: "User login: user@example.com"
logger.log("Payment token: \(token)")
Numbers (Int, Double, Float) are considered public by default — they can be safely logged without marking. Strings (String, NSString, StaticString) and objects (NSObject, CFType) are private by default — they are masked in production. Static strings (string literals in quotes inside the format string) are always visible — they are part of the message itself, not data.
This behavior differs from NSLog, where all data was logged in plain text. Switching to os_log significantly reduces the risk of leaking sensitive user data through logs.
os_log is 90–95% faster than NSLog under high-frequency logging. In a test with 10,000 calls in a loop, NSLog creates a delay of about 2.8 seconds, while os_log performs the same calls in 0.3 seconds. The difference is explained by synchronous disk writes in NSLog versus asynchronous buffering in os_log.
According to Apple Performance Lab (2016), an iOS application with 20 logging calls per second through NSLog loses 5–8 animation frames per second due to main thread blocking. With os_log there is no frame loss because buffering occurs in a separate kernel thread.
| Parameter | NSLog | os_log |
|---|---|---|
| Write mechanism | Synchronous disk write | Asynchronous kernel buffering |
| Time for 10,000 calls | ~2.8 s | ~0.3 s |
| Impact on FPS | Loss of 5–8 frames | 0 frames |
| Severity levels | None | 5 levels |
| Privacy | All data visible | Auto-masking |
| Filtering | Not supported | By subsystem / category / level |
os_log has two APIs: the classic C-style os_log_create and the modern Swift wrapper Logger introduced in iOS 14. The Swift Logger uses the ResultBuilder system for formatting — arguments are interpolated through string literals with explicit privacy marking.
import OSLog
let logger = Logger(
subsystem: "com.example.app",
category: "network"
)
func handleResponse(statusCode: Int) {
if statusCode > 399 {
logger.error("HTTP error: \(statusCode, privacy: .public)")
} else {
logger.info("Response OK: \(statusCode)")
}
}
log collect is a command-line utility for exporting collected logs from a device. It is run from Terminal after connecting the device to a Mac via USB.
// Collecting logs in .logarchive
// In Terminal: log collect --device --output ./app_logs.logarchive
// Viewing subsystem logs: log show --subsystem com.example.app
// Logging with dynamic values
logger.log("User \(userId) opened screen \(screenName)")
When using Logger, it is important to remember that arguments are interpolated via String Interpolation, not format strings as in the C version of os_log. This is safer but requires explicit privacy marking for each argument if the default behavior is not suitable for the developer.
Frequently Asked Questions
os_log asynchronously buffers messages in the kernel and does not block the main thread, while NSLog synchronously writes to disk. os_log is 10 times faster, provides 5 severity levels, and automatically masks private data — NSLog does not have any of these features.
For temporary debug messages, use .debug — they are disabled in production builds and do not affect user performance. For important messages that should always be preserved, use .default or .info.
Through Configure Profile in Xcode: Devices → select the device → Open Console → Actions → Configure Profile. Set the collection level for the desired subsystem to Include. This creates a profile that remains active until the first device restart.
Yes, os_log works in all SwiftUI applications without additional setup. Create a static Logger in your model or in a View extension and use it in onChange, task, and gesture handlers to track screen lifecycle.
By default, os_log masks strings and objects as private. To see the value, explicitly specify privacy: .public in the interpolation. Without this marking, values will be replaced by the mask in production builds, but in Xcode debugging they display normally.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also