System
Unified Logs
Overview
The macOS Unified Logging System (ULS) is the system-wide structured logging infrastructure introduced in macOS 10.12 Sierra. It replaced the legacy Apple System Log (ASL) and syslog frameworks and is now the authoritative event record for the kernel, all Apple system frameworks, and most third-party applications. Every process on a modern macOS system writes to ULS; no other artifact offers the same breadth of cross-subsystem correlation.
Architecture
ULS has four physical components that together form a self-contained, decodable archive:
| Component | Location | Purpose |
|---|---|---|
tracev3 chunks | /var/db/diagnostics/ (subdirs) | Binary log records; opaque without the format-string databases |
uuidtext format strings | /var/db/uuidtext/ | Per-binary format strings keyed by UUID; required to render messages |
| Dynamic Shared Cache (dsc) | /var/db/uuidtext/dsc/ | Shared-library format strings; ship once, referenced by many binaries |
timesync anchors | /var/db/diagnostics/timesync/ | Mach-time-to-wall-clock calibration records, one per boot |
The tracev3 files contain compressed, binary-encoded log entries that reference format strings by UUID rather than embedding them. Rendering a human-readable message requires the uuidtext database for that UUID. The timesync anchors convert the Mach absolute time stored in each record to a calendar timestamp — without the correct anchor, timestamps cannot be recovered.
.logarchive bundles are self-contained: log collect copies all four components into a single directory bundle, making the bundle independently renderable on any macOS host.
Ring-Buffer Retention
The store is partitioned into priority tiers. The Persist/ tier survives reboots; the HighVolume/ tier is overwritten most aggressively.
| Tier | Path | Typical Retention |
|---|---|---|
| Persist | /var/db/diagnostics/Persist/ | Days to a few weeks |
| Special | /var/db/diagnostics/Special/ | Extended retention for high-priority subsystems |
| Signpost | /var/db/diagnostics/Signpost/ | Performance intervals; short retention |
| HighVolume | /var/db/diagnostics/HighVolume/ | Hours; high-frequency debug output |
| Live data | /var/db/diagnostics/logdata.LiveData.tracev3 | Current write buffer |
| Timesync | /var/db/diagnostics/timesync/ | One file per boot; retained longer |
On a typical active laptop the Persist tier retains approximately one to two weeks of events. Under sustained high activity the window may be as short as a few days. ULS is not a long-term audit log.
Retention is hours to weeks, not months
Do not assume historical events survive in ULS. The ring-buffer overwrites itself continuously. Events older than two to three weeks are likely lost unless a .logarchive was captured contemporaneously. Plan collection to run as early as possible in any investigation timeline.
Forensic Significance
ULS is the only macOS artifact that can answer all of the following questions from a single source:
| Question | Subsystem |
|---|---|
| Was this binary allowed to execute, and what was the AMFI verdict? | com.apple.AMFI, com.apple.kernel |
| What did the user authenticate to, and when? | com.apple.loginwindow, com.apple.securityd, com.apple.authorization |
What hostnames did mDNSResponder resolve during the incident window? | com.apple.network, com.apple.mDNSResponder |
| Which USB devices were attached, and at what exact time? | com.apple.iokit.IOUSBFamily, com.apple.usbmuxd |
| Did Gatekeeper or XProtect block or allow this download? | com.apple.syspolicy, com.apple.xprotect, com.apple.LaunchServices.quarantine |
| Which app requested a TCC permission, and was it granted? | com.apple.TCC |
File Locations
| Artifact | Path | SIP Protected | Root Required | Notes |
|---|---|---|---|---|
| Persist chunk store | /var/db/diagnostics/Persist/ | Yes | Yes | Primary forensic target; survives reboots |
| Special chunk store | /var/db/diagnostics/Special/ | Yes | Yes | Priority-retained subsystems |
| Signpost store | /var/db/diagnostics/Signpost/ | Yes | Yes | Performance trace intervals |
| HighVolume store | /var/db/diagnostics/HighVolume/ | Yes | Yes | Short-lived; collect immediately |
| Live write buffer | /var/db/diagnostics/logdata.LiveData.tracev3 | Yes | Yes | Active file; may be locked |
| Timesync anchors | /var/db/diagnostics/timesync/ | Yes | Yes | One file per boot; critical for timestamps |
| UUID format strings | /var/db/uuidtext/ | Yes | Yes | Keyed by binary UUID |
| DSC shared strings | /var/db/uuidtext/dsc/ | Yes | Yes | Shared library format strings |
All paths are under /var/db/ and protected by System Integrity Protection (SIP). Read access requires root on a live system. On a mounted disk image, standard read permissions apply once the image is attached with appropriate tools.
File Naming
tracev3 chunk files within each subdirectory carry a UUID-derived filename. The timesync directory contains one .timesync file per boot, named with a timestamp. There is no persistent sequential numbering analogous to FSEvents record files.
Subsystem Forensic Value
The table below maps the six predicate presets collected by macfor to the subsystems they target and their primary forensic use cases.
| Preset | Key Subsystems / Processes | What It Captures | Primary Use Cases |
|---|---|---|---|
exec | com.apple.AMFI, com.apple.kernel, com.apple.launchd | Process execution decisions, code-signing verdicts, AMFI allow/deny, launchd service activations | Malware execution tracing; unsigned binary detection; privilege escalation via launchd |
auth | com.apple.loginwindow, com.apple.securityd, com.apple.authorization, com.apple.opendirectoryd, sudo, sshd | Console login/logout, screen lock/unlock, sudo invocations, SSH sessions, Open Directory lookups, keychain authorization | Account activity reconstruction; lateral movement; privilege abuse |
network | com.apple.network, com.apple.network.connection, mDNSResponder, com.apple.networkextension | TCP/UDP connection establishment, DNS resolution, NE policy decisions, VPN tunnel events | C2 identification; DNS-based IOC matching; network egress baseline |
usb | com.apple.iokit.IOUSBFamily, com.apple.usbmuxd, messages containing USBMSC or IOUSBHostDevice | Device attach/detach, vendor/product IDs, mass-storage enumeration, iOS device pairing | Data exfiltration via USB storage; device-timeline correlation; insider threat |
xprotect | com.apple.xprotect, com.apple.syspolicy, com.apple.LaunchServices.quarantine | XProtect signature matches, Gatekeeper notarisation checks, quarantine assessments, MRT actions | Malware detection tracing; Gatekeeper bypass evidence; download provenance |
tcc | com.apple.TCC | Permission prompts, grant/deny decisions, service access events | Spyware/RAT permission tracing; MDM-managed grant audit; FDA escalation |
Critical Forensic Caveats
Private data redaction cannot be reversed
<private> tokens in eventMessage fields are applied at write time by the logging subsystem. The unredacted value is never written to disk — not even in the raw tracev3 store. Retroactive recovery is impossible. The only way to obtain unredacted messages is to configure a com.apple.system.logging MDM profile with Enable-Private-Data = true before the events are generated. On a system without this profile, many forensically relevant message fields — including process arguments, filenames, and network addresses — will appear as <private>.
Always use --timezone UTC when rendering logs
log show renders timestamps in the host system's local timezone by default. Forensic output produced without --timezone UTC will have timestamps offset by the host TZ, which may be different from the source system's TZ. The rendered timestamps will be inconsistent across sessions on different investigators' machines. Always pass --timezone UTC; macfor does this automatically for all preset renders.
Boot UUID boundaries require per-boot timesync anchors
Each system boot generates a new Boot UUID. The tracev3 files partition by Boot UUID; Mach absolute time resets to zero at each boot. Correlating events across two boots requires the timesync anchor for each boot to convert Mach time to wall clock time. The log show CLI handles this automatically when operating on a .logarchive that includes the timesync directory. Manual cross-boot analysis against raw tracev3 files without the timesync data produces incorrect timestamps.
Predicate Presets
macfor renders six predicate-filtered NDJSON extracts. Each preset targets a specific forensic domain using an NSPredicate expression passed to log show --predicate.
| Preset | NSPredicate | Forensic Purpose |
|---|---|---|
auth | (subsystem == "com.apple.loginwindow") OR (subsystem == "com.apple.opendirectoryd") OR (subsystem == "com.apple.authorization") OR (subsystem == "com.apple.securityd") OR (process == "sudo") OR (process == "sshd") | Authentication, session, and privilege escalation events |
usb | (subsystem == "com.apple.iokit.IOUSBFamily") OR (subsystem == "com.apple.usbmuxd") OR (eventMessage CONTAINS "USBMSC") OR (eventMessage CONTAINS "IOUSBHostDevice") | USB device attachment, mass-storage enumeration, iOS pairing |
network | (subsystem == "com.apple.network") OR (subsystem == "com.apple.network.connection") OR (process == "mDNSResponder") OR (subsystem == "com.apple.networkextension") | Network connections, DNS resolution, VPN/NE policy |
exec | (subsystem == "com.apple.kernel") OR (subsystem == "com.apple.AMFI") OR (subsystem == "com.apple.launchd") OR (eventMessage CONTAINS "exec") | Process execution, AMFI code-signing verdicts, launchd activations |
xprotect | (subsystem BEGINSWITH "com.apple.xprotect") OR (subsystem == "com.apple.syspolicy") OR (subsystem == "com.apple.LaunchServices.quarantine") | XProtect / Gatekeeper / MRT decisions on downloaded content |
tcc | (subsystem == "com.apple.TCC") | TCC permission prompts and grant/deny decisions |
Predicates are applied during log show rendering. They filter events before writing to NDJSON; no events outside the predicate are stored in the extract.
NDJSON Event Record Schema
Each line of a rendered NDJSON extract is a JSON object with the following fields:
| Field | Type | Source | Notes |
|---|---|---|---|
preset | string | macfor-added | One of auth, usb, network, exec, xprotect, tcc |
timestamp | string | log show | RFC 3339 with nanosecond precision, always UTC |
subsystem | string | log show | Reverse-DNS bundle-style identifier |
category | string | log show | Sub-categorisation within the subsystem |
process | string | log show | Short process name |
process_image_path | string | log show | Full path to process binary |
sender_image_path | string | log show | Library or framework that emitted the entry |
message | string | log show | Rendered log message; may contain <private> tokens |
message_type | string | log show | One of default, info, debug, error, fault |
thread_id | uint64 | log show | May be absent on older macOS versions |
trace_id | uint64 | log show | Correlates entries within an activity chain |
activity_id | uint64 | log show | Activity tree root identifier |
extra | object | macfor-added | Passthrough map for any unrecognised log show fields |
message_type values error and fault represent unexpected conditions; fault indicates a programmer error at the source. Filtering to these levels first is a useful triage shortcut when reviewing a large extract.
macfor Collection Details
What Is Collected
The system.unifiedlogs plugin produces three artifacts:
| Artifact | Evidence Container Path | Description |
|---|---|---|
| Raw diagnostic store | unifiedlogs/raw/ | Verbatim copy of /var/db/diagnostics and /var/db/uuidtext with per-file SHA-256 hashes |
| Logarchive bundle | unifiedlogs/logs.logarchive/ | Self-contained .logarchive generated by log collect; independently renderable |
| Event extracts | unifiedlogs/events/<preset>.ndjson | Six predicate-filtered NDJSON files (one per preset) |
Manifest Contents
The collection manifest (manifest.json) for this plugin records:
logCLI version (fromlog version)- Boot UUID (from
logs.logarchive/Info.plist) - Collection window start and end timestamps
- Per-file SHA-256 hashes for all raw store files
- File count and total bytes per directory tier
- Per-preset event counts and any preset-level errors
Collection Options
| Flag | Default | Description |
|---|---|---|
--time-window | 720h (30 days) | --last window passed to log collect |
--logarchive-path <path> | — | Offline mode: render presets against a pre-built .logarchive |
--skip-raw | false | Skip raw /var/db/diagnostics + /var/db/uuidtext preservation |
--skip-logarchive | false | Skip .logarchive generation |
--skip-events | false | Skip all NDJSON preset rendering |
--skip-auth / --skip-usb / etc. | false | Disable individual presets |
Requirements
Live collection requires root. The plugin detects privilege at runtime and emits a clear error if root is absent. Detection (macfor detect) reports the artifact as present even without root, so the privilege gap is visible in pre-collection reports.
macOS 10.15 Catalina through 15 Sequoia are supported. The plugin relies on Apple's log CLI, which Apple maintains for forward compatibility.
Investigation Recipes
Was a Specific Binary Allowed to Execute?
Use the exec preset, which captures AMFI verdicts and kernel-level execution events.
# Render the exec extract (macfor already does this during collection)
log show logs.logarchive \
--style ndjson \
--predicate '(subsystem == "com.apple.AMFI") OR (subsystem == "com.apple.kernel")' \
--timezone UTC \
| grep -i "mymalware"
In the NDJSON extract, look for message fields containing the binary path. AMFI messages include verdict strings such as allowing (permitted), denied (blocked), and Library Validation failed. A sequence of allowing verdict followed by execution with no subsequent crash indicates successful execution. A denied entry indicates SIP or AMFI blocked it.
Cross-reference with the xprotect preset if the binary was downloaded: a Gatekeeper assessment log entry will reference the same path within seconds of the AMFI verdict.
Which USB Devices Were Attached During a Suspicious Window?
Use the usb preset. Mass-storage devices produce IOUSBFamily attachment entries followed by USBMSC enumeration messages that carry vendor string, product string, and serial number.
# Filter usb.ndjson to a specific time window
cat unifiedlogs/events/usb.ndjson \
| python3 -c "
import sys, json
for line in sys.stdin:
e = json.loads(line)
ts = e.get('timestamp','')
if '2026-04-10T03' <= ts <= '2026-04-10T05':
print(e['timestamp'], e['message'])
"
Key message patterns to look for in the usb extract:
| Pattern | Meaning |
|---|---|
IOUSBHostDevice::start | Device enumeration began |
USBMSC: Vendor = ..., Product = ... | Mass-storage class device; note vendor/product strings |
setConfiguration: 1 | Device configuration set; attachment complete |
IOUSBHostDevice::stop | Device detached |
Vendor/product strings from USBMSC messages can be correlated against the USB device history in the devices.bluetooth artifact and macOS's IORegistry for a complete attachment timeline.
Did Gatekeeper Quarantine or Block This Download?
Use the xprotect preset. Gatekeeper (syspolicy) and the quarantine LaunchServices subsystem emit assessment entries whenever a downloaded item is opened.
# Search xprotect extract for a specific filename
grep -i "SuspiciousInstaller.pkg" unifiedlogs/events/xprotect.ndjson \
| python3 -c "import sys, json; [print(json.dumps(json.loads(l), indent=2)) for l in sys.stdin]"
Key syspolicy message patterns:
| Message Pattern | Meaning |
|---|---|
assessmentd: verdict granted | Gatekeeper allowed execution |
assessmentd: verdict denied | Gatekeeper blocked execution |
XProtect: matched rule ... | XProtect signature triggered |
MRT: removing ... | Malware Removal Tool acted on the item |
com.apple.LaunchServices.quarantine: ... releasing quarantine | Quarantine bit cleared (user clicked Open) |
Combine the xprotect extract with the Quarantine Events database (~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2) for the download URL and originating process. The two artifacts provide complementary views: the SQLite database records the download origin; ULS records what Gatekeeper decided.
Version Differences
| macOS Version | Notes |
|---|---|
| 10.12 Sierra | ULS introduced; replaces ASL |
| 10.15 Catalina | Baseline for macfor support; Signpost chunks stable |
| 11 Big Sur | com.apple.networkextension subsystem added; Catalog v3 chunk extensions |
| 12 Monterey | Increased <private> redaction scope by default across more subsystems |
| 13 Ventura | com.apple.xprotect.daemon subsystem added; additional XPC activity logging |
| 14 Sonoma | Minor tracev3 chunk tweaks; handled transparently by log CLI |
| 15 Sequoia | No breaking changes identified |
Forward compatibility is inherited from Apple's log CLI, which Apple maintains across OS versions. New NDJSON fields from future macOS releases are absorbed into the extra passthrough map.
Known Limitations (v1)
| Limitation | Detail |
|---|---|
No native Go tracev3 parsing | The plugin wraps Apple's /usr/bin/log. Raw tracev3 files in the evidence container cannot be decoded by macfor without the log CLI — they require macOS to render. |
| Live collection requires root | Non-root invocations produce a collection error. Offline mode (supplying a pre-built .logarchive) has no root requirement. |
| No custom predicate expressions | User-supplied predicate strings are not supported in v1. Only the six built-in presets are available. Custom predicates are planned for v1.1. |
| No live streaming | log stream is not implemented. Collection captures a historical window; there is no provision for tailing the live log stream. |
| Offline disk-image reconstruction not supported | If a .logarchive was not generated on the source host, macfor cannot construct one from a mounted disk image. The user must provide a pre-built .logarchive via --logarchive-path. |
| NDJSON volume | log show output is 10–30x the size of the source tracev3 data. On large stores, each preset extract may be several gigabytes. Use --skip-* flags to omit low-priority presets when storage is constrained. |
Tool Support
| Tool | Support |
|---|---|
| macfor | Raw store preservation + .logarchive + six NDJSON presets via system.unifiedlogs |
Apple log CLI (/usr/bin/log) | Native rendering; required by macfor for all decode operations |
| mandiant/macos-UnifiedLogs | Open-source Rust parser; can decode tracev3 without the log CLI |
| UnifiedLogReader (Yogesh Khatri) | Python-based parser; broad format support |
| mac_apt | Full macOS forensics platform; includes ULS module |
| AXIOM (Magnet) | Commercial ULS parsing and timeline integration |
References
- Apple
logman page - mandiant/macos-UnifiedLogs — Rust parser
- Reviewing macOS Unified Logs — Mandiant
- UnifiedLogReader — Yogesh Khatri
- SANS: macOS Unified Log Investigations
- Jonathan Levin — MacOS and iOS Internals, Vol. III
- Apple Platform Security — Logging
- MITRE ATT&CK: Indicator Removal — Clear macOS Logs (T1070.002)
- Related artifacts: FSEvents, TCC Database, Quarantine Events, XProtect