Skip to content

Logging and Reporting

Rod Christiansen edited this page Sep 3, 2026 · 2 revisions

Logging and Reporting

Every BootstrapMate run writes one log file, updates two status artefacts, and optionally POSTs a run summary to an endpoint you nominate. This page is the reference for all of it, and ends with the sequence of lines a healthy run produces.

Where the logs are

/Library/Managed Bootstrap/logs/

One file per process run, named from the run's start time in local time:

2026-09-01-131514.log

The pattern is yyyy-MM-dd-HHmmss followed by .log. There is no size-based rotation and no appending — a new run means a new file. The GUI's Logs tab reads this exact directory.

The directory is created by the package's postinstall, and again by the CLI at startup if it is missing. If it cannot be created the CLI writes bootstrapmate: failed to create log directory <dir>: <err> to stderr and continues with no log file. There is no fallback log location.

Retention

At logger initialisation, every file in the log directory ending in .log whose modification date is older than 30 days is deleted. The sweep is non-recursive and error-tolerant, and logs its own result as Removed N log file(s) older than 30 days.

Line format

[2026-09-01 13:15:14] INFO  Session started
[2026-09-01 13:15:14] ERROR Failed to load manifest
[2026-09-01 13:15:14] WARN  Retrying
[2026-09-01 13:15:14] DEBUG Detail

The fields are: a bracketed timestamp yyyy-MM-dd HH:mm:ss in local time, then the level label padded to five characters, then the message. Every physical line gets its own timestamp, blank lines are dropped, and \r\n and \r are normalised.

Levels

Internally there are five: debug, info, warning, error, success. On disk only four labels appear — DEBUG, INFO, WARN, ERROR. Success is written as INFO.

The console gets icon prefixes instead: [~] debug, [i] info, [!] warning, [x] error, [+] success. --silent suppresses console output entirely; [~] lines only appear with --verbose. Console verbosity comes from the CLI flags — setting verboseMode or silentMode by profile does not change it, because the logger is initialised before those preferences are read.

Structured markers

Inside the message text you will also see [SECTION], [PROGRESS], [SUB-PROGRESS], [SUCCESS], [SKIPPED], [COMPLETION], === <title> === headings, and — for output captured from a child script — [OUTPUT] <scriptname>: . Child stdout is logged at INFO, child stderr at WARN.

Session header and footer

The header is written at init and records Version:, Session Start Time:, Process ID:, User:, Machine:, OS:, Architecture: (ARM64 or X64), Working Directory:, Command Line:, Verbose Console: and Silent Mode:. The footer records the duration.

The Version: value in the header is not the build version — see Troubleshooting and Gotchas.

Following a run live

Tail the newest log file:

tail -f "$(ls -t /Library/Managed\ Bootstrap/logs/*.log | head -1)"

Everything also goes to the unified log under subsystem com.github.bootstrapmate, category general, so you can stream it without knowing the filename:

log stream --predicate 'subsystem == "com.github.bootstrapmate"'

Or read back a window that has already passed:

log show --predicate 'subsystem == "com.github.bootstrapmate"' --last 1h

Levels map to debug, info (info and success), default (warning) and error.

Status files

BootstrapMate writes per-phase status, keyed by the phase names Preflight, SetupAssistant and Userland, to two places.

/Library/Managed Bootstrap/status.json — pretty-printed JSON with sorted keys, one entry per phase, each carrying stage, startTime, completionTime, exitCode, version, phase, architecture, bootstrapUrl, lastError and runId.

/Library/Preferences/com.github.bootstrapmate.plist — the same information as an XML plist, with capitalised key names (Stage, StartTime, CompletionTime, ExitCode, Version, Phase, Architecture, BootstrapUrl, LastError, RunId).

Stage takes one of Starting, Running, Completed, Failed, Skipped. Timestamps are yyyy-MM-dd HH:mm:ss.

On a successful run the plist is then overwritten with only LastRunVersion, LastUpdated and Architecture, so the per-phase detail is not there afterwards. This is listed under known issues in Troubleshooting and Gotchas.

The reporting endpoint

If reportingUrl is configured — by profile, or with --reporting-url — BootstrapMate POSTs one JSON run summary after the dialog closes and before cleanup. It fires once, and a failure to report never fails the run.

The request uses Content-Type: application/json and User-Agent: BootstrapMate/<version>, with a 15-second request timeout and a 20-second bound on the wait. If reportingHeader is set, its value is sent as the Authorization header.

The payload has sorted keys and these fields:

Field Value
tool "BootstrapMate"
platform "macOS"
schemaVersion 1
version the tool's version string
runId UUID generated for this run
success boolean
startTime / endTime ISO-8601
durationSeconds integer, rounded
architecture ARM64 or X64
hostname the host name
serialNumber the platform serial, or "" if unavailable
manifestUrl the configured manifest URL
phases per-phase stage, exitCode, startTime, completionTime, lastError

The payload is vendor-neutral; any service that accepts a JSON POST can consume it.

The three log lines to grep for are Posting run summary to reporting endpoint: <scheme>://<host><path> (query string and userinfo are redacted), then either Run summary reported (HTTP 2xx) or Reporting endpoint returned HTTP <code> / Reporting POST failed: <err>.

On a successful run the phases object arrives empty. That is a known issue, not a misconfigured endpoint — see Troubleshooting and Gotchas.

What a healthy run looks like

In order, in the log file:

  1. === BootstrapMate Session Started === and the header block.
  2. BootstrapMate v<version> started.
  3. Network is available — or the timeout line, which is not by itself fatal.
  4. If the data volume was not ready, Data volume is now writable after Ns.
  5. If configuration came from a profile, the wait ends quietly; if it did not, you would see Still waiting for management config... (Ns elapsed).
  6. The manifest loads. No Failed to load manifest from <url> line.
  7. Preflight: either nothing (no preflight item, phase Skipped) or Preflight script exited N - continuing with bootstrap. Preflight script exited 0 means the device was deemed already configured and the whole bootstrap was intentionally skipped.
  8. Setup Assistant items, each downloading and installing. Re-runs show Already have valid file: <path>. Skipping re-download. for payloads already on disk. No Refusing to install, no All retries failed.
  9. Waiting for user session in the log, followed by a matching user-session line once somebody logs in. A Waiting for user session with nothing after it is the stuck case.
  10. Userland items run.
  11. If reporting is configured: Posting run summary to reporting endpoint: … then Run summary reported (HTTP 2xx).
  12. Session complete — removing one-shot LaunchDaemon com.github.bootstrapmate and Removed /Library/LaunchDaemons/com.github.bootstrapmate.plist.
  13. === BootstrapMate Session Ended === (Duration: N.Ns) and the total duration line.

A quick pass for anything wrong:

grep -E '\] ERROR |\] WARN ' "$(ls -t /Library/Managed\ Bootstrap/logs/*.log | head -1)"

See also

Clone this wiki locally