← BACK TO HOME

The Shell Crossover, Part 8: Logs and Troubleshooting from the macOS Terminal

Translate Event Viewer habits to macOS terminal troubleshooting with unified logging, log show, log stream, predicates, install.log, and tail.

Windows administrators often begin troubleshooting in Event Viewer or with Get-WinEvent. On macOS, the closest starting point is the unified logging system and the log command.

Traditional log files still exist, but many modern macOS diagnostics are not stored as plain text files. You need both patterns: log show and log stream for unified logs, and tail for traditional files such as /var/log/install.log.

The troubleshooting map

Windows habitmacOS terminal equivalentUse case
Event Viewerlog showHistorical unified log review.
Live event viewlog streamWatch new unified log messages.
Get-WinEvent -FilterHashtablelog --predicateFilter by process, subsystem, category, or message.
Text log tailingtail -fWatch traditional text logs.
Export event datalog show --style jsonCapture logs for review or escalation.
Application install logs/var/log/install.logPackage install and software update history.

Historical unified logs with log show

Use log show when you need historical data.

log show --last 1h --style compact

That is usually too noisy. Add a predicate.

log show --last 1h --predicate 'process == "mdmclient"' --style compact

Predicates are the macOS logging equivalent of a focused event query. You can filter by fields such as process, subsystem, category, and event message. NSPredicate syntax accepts both AND and &&; AND is used throughout for readability.

log show --last 30m \
  --predicate 'process == "mdmclient" AND eventMessage CONTAINS[c] "profile"' \
  --style compact

Use --info when the messages you need are at the info level.

log show --last 30m --info --predicate 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient"' --style compact

Use --debug only when you need that level and understand the volume.

Live unified logs with log stream

Use log stream when you need to watch new events as they occur.

log stream --predicate 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient"' --style compact

For install-related troubleshooting:

log stream --predicate 'process == "installer" OR process == "installd"' --style compact

A common mistake is using log stream when you need history. log stream starts watching from now. Use log show when the event already happened.

Traditional logs still matter

Some high-value logs are still plain text. The most common admin example is /var/log/install.log.

tail -f /var/log/install.log

Search recent install activity:

grep -i "example" /var/log/install.log

Capture the last 200 lines for review:

tail -n 200 /var/log/install.log > /tmp/install-last-200.log

PowerShell can do the same work.

Get-Content -Path "/var/log/install.log" -Tail 200

For live tailing in PowerShell:

Get-Content -Path "/var/log/install.log" -Wait

MDM troubleshooting example

When investigating MDM behavior, start with mdmclient, but do not stop at process-only filtering. For MDM protocol activity, filtering on the com.apple.ManagedClient subsystem reduces noise from unrelated mdmclient activity.

log show --last 2h --info \
  --predicate 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient"' \
  --style compact

Add a message filter when the output is too broad.

log show --last 2h --info \
  --predicate 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient" AND eventMessage CONTAINS[c] "InstallProfile"' \
  --style compact

PowerShell wrapper:

$Predicate = 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient" AND eventMessage CONTAINS[c] "InstallProfile"'
& /usr/bin/log show --last 2h --info --predicate $Predicate --style compact

Export JSON for deeper review.

$Predicate = 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient"'
$OutputPath = Join-Path $HOME "Desktop/mdmclient-log.json"
& /usr/bin/log show --last 2h --info --predicate $Predicate --style json |
    Set-Content -Path $OutputPath

Software installation example

For package installs, check both the traditional install log and unified logs.

tail -n 100 /var/log/install.log
log show --last 1h \
  --predicate 'process == "installer" OR process == "installd"' \
  --style compact

If you know the package identifier, combine log review with receipt inspection.

pkgutil --pkg-info com.example.package
pkgutil --files com.example.package | head

LaunchDaemon troubleshooting example

Start with the job state.

sudo launchctl print system/dev.admincrossover.example.heartbeat

Then inspect launchd messages that mention the label.

log show --last 1h \
  --predicate 'process == "launchd" AND eventMessage CONTAINS[c] "dev.admincrossover.example.heartbeat"' \
  --style compact

Check the stdout and stderr paths from the LaunchDaemon plist.

tail -n 50 /var/log/admincrossover-heartbeat.log
tail -n 50 /var/log/admincrossover-heartbeat.err

The fastest LaunchDaemon fixes usually come from three checks:

sudo plutil -lint /Library/LaunchDaemons/dev.admincrossover.example.heartbeat.plist
ls -l /Library/LaunchDaemons/dev.admincrossover.example.heartbeat.plist
sudo launchctl print system/dev.admincrossover.example.heartbeat

Reviewing a collected log archive

When you receive a .logarchive from another Mac, query it with log show --archive instead of querying the live local log store.

log show --archive "$HOME/Desktop/incident.logarchive" \
  --last 2h \
  --predicate 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient"' \
  --style compact

This pattern is useful when logs were collected with log collect, included in a sysdiagnose, or gathered from a remote endpoint for offline review.

Capturing evidence for escalation

Create a folder, capture focused logs, and include command output.

case_dir="$HOME/Desktop/admincrossover-log-review-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$case_dir"

log show --last 2h --info \
  --predicate 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient"' \
  --style json > "$case_dir/mdmclient.json"
tail -n 300 /var/log/install.log > "$case_dir/install-last-300.log"
sw_vers > "$case_dir/sw_vers.txt"
profiles status -type enrollment > "$case_dir/profiles-status.txt" 2>&1

PowerShell version:

$CaseDir = Join-Path $HOME ("Desktop/admincrossover-log-review-{0}" -f (Get-Date -Format "yyyyMMdd-HHmmss"))
New-Item -ItemType Directory -Path $CaseDir -Force | Out-Null

$Predicate = 'process == "mdmclient" AND subsystem == "com.apple.ManagedClient"'
& /usr/bin/log show --last 2h --info --predicate $Predicate --style json |
    Set-Content -Path (Join-Path $CaseDir "mdmclient.json")

Get-Content -Path "/var/log/install.log" -Tail 300 |
    Set-Content -Path (Join-Path $CaseDir "install-last-300.log")

& /usr/bin/sw_vers | Set-Content -Path (Join-Path $CaseDir "sw_vers.txt")
& /usr/bin/profiles status -type enrollment *> (Join-Path $CaseDir "profiles-status.txt")

A note on sysdiagnose

sysdiagnose is a collection mechanism, not the first troubleshooting step. It gathers a broad diagnostic package that can be useful for escalation, but it is not a substitute for a focused log query.

Use focused logs first. Use sysdiagnose when Apple, a vendor, or your escalation path asks for a full diagnostic collection.

The operating rule

Use log show for history, log stream for live observation, and tail for traditional text logs. Build predicates early. Capture evidence to files when you need peer review, vendor support, or a repeatable troubleshooting record.