The End of Flat Text Logs
For decades, troubleshooting a UNIX or Linux system was straightforward: you navigated to /var/log/ and used standard command-line utilities like cat, grep, and tail to read flat text files such as system.log or wifi.log.
With the release of macOS Sierra (10.12), Apple completely overhauled this architecture, introducing the Unified Logging System. In modern macOS, applications and system daemons no longer write directly to flat text files. Instead, they write highly structured, compressed, and strictly formatted binary data into a centralized, memory-resident database.
This architecture is drastically faster and vastly improves privacy (by automatically redacting sensitive strings), but it completely breaks traditional text parsing tools like grep. You can no longer simply tail -f /var/log/system.log and expect to see meaningful modern debug data.
To interact with the Unified Logging System from the terminal, macOS administrators and developers must use the highly powerful log command.
Step 1: Viewing Live System Activity
The most immediate replacement for tail -f is the log stream command. This command taps directly into the active memory buffer, displaying log messages as they are generated in real-time.
Open the Terminal and run:
log stream
You will be instantly overwhelmed by an avalanche of data. Because every single daemon, application, and kernel process writes to the unified system simultaneously, a modern Mac generates thousands of log entries per second. To make this useful, you must aggressively filter the stream.
Step 2: Filtering by Subsystem and Process
The true power of the Unified Logging System is its structured nature. Every log entry is tagged with metadata, including the Process ID, the Subsystem (the broader category, like networking or Bluetooth), and the Category.
To monitor only logs generated by the Wi-Fi subsystem, use the --predicate flag. Predicates use a specific, SQL-like syntax to filter the binary data before it is printed to the screen.
log stream --predicate 'subsystem == "com.apple.wifi"'
If you are a developer debugging a specific application (e.g., Google Chrome), you can filter by the exact process name:
log stream --predicate 'process == "Google Chrome"'
You can combine predicates using logical operators (AND, OR, NOT). To view Chrome logs but filter out meaningless standard info messages, showing only Errors and Faults:
log stream --predicate 'process == "Google Chrome" AND messageType == error'
Step 3: Querying Historical Data
While log stream shows real-time data, log show queries the compressed binary archives stored on the hard drive (located in /var/db/diagnostics) to investigate past events.
Because these archives contain gigabytes of data, running a bare log show command will take a massive amount of time and output millions of lines. You must restrict the query using both predicates and timeframes.
If a user reported that their Mac abruptly dropped off the network and reconnected yesterday afternoon between 2:00 PM and 2:15 PM, you can query exactly that timeframe:
log show --predicate 'subsystem == "com.apple.wifi"' --start "2023-10-25 14:00:00" --end "2023-10-25 14:15:00"
Alternatively, if a server crashed randomly in the last hour, you can query recent history using relative time:
log show --last 1h --predicate 'messageType == fault'
Step 4: Outputting to JSON for Advanced Parsing
While the log command’s terminal output is human-readable, it is difficult to parse programmatically in a bash or Python script.
Because the underlying database is highly structured, the log command can effortlessly export its findings into perfect JSON objects. By combining the log command with the jq utility (a command-line JSON processor), you can perform incredibly advanced forensic analysis.
log show --last 10m --style json | jq '.[] | select(.processImagePath | contains("Safari")) | .eventMessage'
This pipeline instructs macOS to dump the last 10 minutes of logs in JSON format, passes the JSON to jq, filters the array for any process paths containing “Safari”, and extracts only the raw event message string.
Step 5: Addressing the <private> Tag
When analyzing logs, you will frequently see variables masked as <private>. To comply with strict privacy regulations, Apple forces developers to explicitly declare if a variable (like an IP address or a username) should be publicly logged. If not declared, the OS censors it automatically.
If you are managing the Mac and absolutely need to see this private data for debugging, you can install a specific configuration profile (managed via MDM) that disables privacy redaction globally, though this is heavily discouraged for production environments due to the risk of logging plain-text passwords.
Conclusion
The macOS Unified Logging System replaces the fragile chaos of flat text files with a lightning-fast, highly structured relational database. By mastering the log command and its predicate syntax, administrators can filter through millions of background events instantly, pinpointing kernel faults and application crashes with surgical precision.