Unified Logging: How os_log Replaced syslog on macOS
Why traditional plain-text syslog couldn't keep up with modern macOS's logging needs, and how the unified logging system's structured, activity-tracing design fixes the problems that made old-style logs so hard to use for real diagnosis.
macOS moved away from traditional plain-text syslog years ago in favor of the unified logging system (os_log), and the reasons behind that shift explain a lot about why modern macOS log output looks and behaves so differently from the flat text files a long-time Unix user might expect.
What was actually wrong with plain-text syslog
Traditional syslog writes human-readable text lines to flat files, which sounds simple and is simple — but that simplicity comes with real costs at the scale a modern operating system actually operates at. Every log message written as fully-formatted text has real, continuous performance overhead (string formatting happens whether or not anyone ever reads that particular line), log files grow without bound unless actively rotated and pruned, and — critically for actual diagnosis — plain text carries no structured metadata beyond whatever a given piece of software chose to embed in its message string, making it hard to reliably filter, correlate, or query logs from different subsystems in any consistent way.
What unified logging does differently
The unified logging system defers the expensive part — formatting a human-readable message string — until a log is actually being viewed or extracted, rather than doing it at write time regardless of whether anyone will ever read that message. What actually gets written at log time is compact, structured, binary data: a reference to the log format string (not the fully rendered string itself), the actual variable values being logged, and structured metadata like subsystem, category, and severity level. This alone accounts for a meaningful reduction in the overhead of routine logging, since the expensive string-formatting work only happens on-demand for messages someone is actually viewing.
Log levels and their actual retention behavior
Unified logging defines several severity levels — from debug (most verbose, essentially never persisted, only visible when actively streaming logs from a running process) up through default, info, error, and fault — with meaningfully different persistence behavior per level baked into the system itself, rather than left entirely to per-application configuration the way traditional syslog largely was. debug and info level messages, in particular, generally aren’t persisted to disk at all by default — they’re only visible if you’re actively streaming logs in real time — which is a deliberate trade-off: extremely verbose, high-volume diagnostic detail is available when you’re actively debugging something live, without that same volume of data being permanently retained and consuming disk space indefinitely for messages that are only useful in the moment.
Structured subsystems and categories
Every unified log message can carry a subsystem identifier (typically a reverse-DNS-style string identifying the responsible component or application) and a category within that subsystem, which is what makes the modern log show/log stream command-line tools’ filtering actually useful in practice — you can query specifically for a given subsystem’s error-and-above messages across the entire system’s unified log, correlated by time against every other subsystem’s messages, in a way that grepping through a collection of separate flat-text log files never made straightforward.
Activity tracing: following one logical operation across processes
Beyond individual log messages, unified logging supports activities — a mechanism for tagging a logical unit of work (say, handling one specific user-initiated action) with an identifier that follows it across thread and even process boundaries, so log messages related to that same logical operation can be correlated together even if the actual work involved several different processes handing off to each other along the way. This directly addresses a real, common pain point in diagnosing modern macOS behavior, where a single user-facing action frequently touches multiple separate system processes and services (exactly the kind of XPC-based, privilege-separated architecture common throughout modern macOS) — without activity tracing, correlating log messages across that many involved processes by timestamp alone is unreliable and tedious.
Why this trade-off was the right one for a modern OS
The core trade-off unified logging makes — genuinely more complex to inspect casually with basic text tools like cat or grep against a raw log file, in exchange for dramatically better performance at write time and dramatically better filtering, correlation, and cross-process tracing capability when actually diagnosing something using the purpose-built tools — reflects a reasonable bet that structured, purpose-built logging tooling would pay for itself as the operating system’s own internal complexity (and the number of separate, cooperating processes involved in any given user action) continued to grow, which is exactly the direction macOS’s XPC-heavy, privilege-separated internal architecture has continued moving in the years since unified logging was introduced.