magus v0.3.0 is out. See what's new
¶ View markdown source · ✎ Suggest an edit
6 min read

Logging and verbosity

magus has four output modes over three log levels. The flags are cheap to remember; what they actually buy you is not obvious from their names, and two of them change behavior beyond filtering.

Flag Level What you get
-q / --quiet error Which targets failed, and their full output. Nothing else.
(none) info The run's scoreboard, plus warnings and workspace changes.
-v debug Cache-key derivation and every subprocess, scoreboard kept.
-vv debug The same, plus each target's output live and interleaved.
-vvv trace The same, plus DAG scheduling and a startup timing table.

-s / --silent is a variant of --quiet, not a fifth level. See quiet versus silent.

Set the level without flags via log.level in magus.yaml or MAGUS_LOG_LEVEL. See Configuration for the full key list.

Default: the scoreboard

Without flags you get the result of each target and little else:

[pass] docs (ran, 1m36s)
  magus run generate:rw docs
out3a777178
[summary] 2 cached, 5 ran, 0 failed (1m44s)

Each result carries an output reference - the short out... id addressing the exact bytes that run produced. magus query output out3a777178 replays them verbatim.

A failure prints more, because more is actionable:

[fail] docs generate:rw (ran, 50s)
  cause: magus generate docs: 1 spell(s) failed
  output: out462efb79
  inspect: magus query output out462efb79
  reproduce: magus run generate:rw docs

The inspect: hint is omitted on CI. Refs are portable (the same inputs mint the same ref anywhere), but the ephemeral runner that printed one is usually gone by the time anyone reads the log, and its full output is already inline above the hint - a command that cannot work where the log is read is worse than no hint. The reference itself stays, since it still correlates the failure with the run, and magus query output <ref> still resolves it later from any machine whose cache holds a run of those exact inputs.

Warnings surface here too, because they are things magus wants you to reconsider before they become failures: a charm no target declares (usually a typo), a filesystem with coarse mtime resolution that can return stale cache hashes, a concurrency setting above twice your CPU count.

What you do NOT get by default is the target's own output. A passing target's subprocess stdout and stderr are withheld entirely, and shown only if it fails. That is what keeps a wide fan-out readable, and it is what -vv turns off (see below).

-v: why did this run, and what did it run

Reach for -v when the question is "why did magus rebuild this?" or "what command did it actually execute?".

Two records answer nearly every such question. cache.key fires once per step with the derived hash and the inputs that produced it - source count, dependency count, tool versions, charms. run.exec fires once per subprocess with the command, its full arguments, and its directory, rendered as a shell line you can copy and run yourself:

go test ./... -race

Beyond that: cross-project dispatch, spell fan-out decisions, the derived affected set with the full project list, and breadcrumbs for best-effort operations that failed quietly (a knowledge-shard push, a diagnostics write).

Every record also gains a dir attribute naming the project that emitted it. magus never changes the process working directory - targets carry their own directory so they can run concurrently - so per-line attribution is how a log line is traced back to its project. There is no "entering directory" marker to look for, and with parallel targets there could not usefully be one.

-vv: the firehose

-v keeps the scoreboard: each target's own output is still withheld unless it fails, so the run stays readable. -vv turns that off, streaming every target's subprocess output live and interleaved.

The two are separate because they answer separate questions. Raising the log level asks magus for more records ABOUT the run; it is not a request to stop withholding the output OF each target. Reach for -vv when you want to watch a build happen, and expect concurrent projects to interleave.

You can set this without the flag, independent of level:

# magus.yaml
log:
  stream: true

or MAGUS_LOG_STREAM=1. -vvv implies -vv.

-vvv: scheduling and startup

Trace is not "even more debug". It is two specific questions.

Why is my build serialized? Trace adds exactly two events: schedule.wait (a step's upstream dependencies) and schedule.run (its admission to the pool, with its slot count and whether it holds the pool exclusively). Together they answer what a step is waiting on, without a profiler.

Why does magus take so long to start? On exit, trace prints a phase timing table:

magus startup trace:
  startup.find_root_early              1.204ms
  startup.config_load                  3.881ms
  startup.flag_parse                   0.442ms
  total                                7.918ms

Trace also stamps source locations on records - but the default pretty output does not render them. Pair it with a machine format to actually see them:

magus -vvv --log-format=text run build

Without that, -vvv pays for collecting source locations and shows you nothing extra.

Quiet versus silent

Both suppress everything below error. They differ in what happens when a target fails, and that difference is the reason to pick one.

--quiet dumps the failing target's log in full, verbatim. Use it when you intend to read the whole failure, and in a pipeline where you want the raw log preserved.

--silent bounds the dump to the last 50 lines and prints the path to the complete log:

-- api (failed) --
... 812 earlier line(s) omitted; full log: .magus/logs/<hash>.log

--silent also bubbles up notices. Any line a target prints beginning with magus:notice: is re-emitted to stderr, on success as well as failure:

echo "magus:notice: deployed api v1.2.3"
notice: api: deployed api v1.2.3

That is the only output an otherwise-silent passing run produces, which makes --silent the right choice for a scheduled or unattended run: near-zero noise, a bounded failure excerpt, and an explicit channel for the few lines that matter.

What ends up in your logs

Two things are logged that you may not want to share.

Full command arguments at -v. run.exec logs a subprocess's complete argument list, unredacted, and renders it as a copy-pasteable shell line. A target invoking a tool with --token=..., -p <password>, or a signing key on the command line puts that value in your terminal. Nothing in that path redacts anything.

Read -v output before pasting it into an issue, a chat, or a CI artifact.

Full request URLs at default verbosity. Outbound HTTP from a magusfile is logged with the complete URL including its query string, at info - no flag required. Presigned URLs and ?token= parameters land in ordinary output.

Prefer environment variables over command-line arguments for secrets in magusfile targets. That is good practice regardless, since a process's arguments are readable by other processes on the machine, but it also keeps them out of these two paths.

Formats

--log-format selects the renderer: pretty (default, for a terminal), text, or json.

One thing to know before piping: with json, cache events go to stdout while diagnostics go to stderr. Redirecting only stdout gives you a clean event stream and leaves warnings on the terminal, which is usually what you want - but it does mean neither stream is the whole picture.

Some output is also TTY-dependent by design. The concurrency pool status line is suppressed when output is not a terminal, so a piped or CI log will not contain it. That is intentional: it repaints in place and would otherwise flood the log.

loggingverbosityquietsilentdebugtracelog levellog format
Last updated (a103255f)
Earlier changes on this page (2)

Full history ↗ · Blame source ↗

Glossary

Workspace

The magus root directory that owns a set of projects and shared config; the unit magus operates over. See workspace.

Project

A directory magus recognizes as a unit of work (it has a magusfile); the unit of caching, scheduling, and dependency tracking. See workspace.

Magusfile

The magusfile.buzz that declares a project's targets (as export funs) and binds its spells. See targets.

Target

A named operation (build, test, ...) you invoke with magus run <target>; it may compose a spell's tool-native operations and depend on other targets. See targets.

Operation

A single tool-native command a target composes; the middle of the work hierarchy (Spell to Operation to Target). See operations.

Spell

A language/runtime adapter (e.g. go, md) that maps generic targets onto a toolchain's real commands. See spells.

Charm

An execution modifier attached with : (lint:rw) that changes how a target runs, not which one; the built-in rw flips a check-only target to mutate in place, and ci always strips it. See charms.

Cache

The content-addressed store magus consults before running a target, so unchanged work is skipped. See cache.

Affected

The set of projects touched by a change; magus affected <target> runs a target only over them. See affected.

CI

An ordinary magusfile-defined target you compose yourself with magus\needs - magus does not hardcode its stages. Magus.RunCI treats it specially only in that it strips the rw charm, it is the anchor magus affected ci keys off, and a selected scope with no project declaring it is a load error rather than a silent no-op. See targets.

Output reference

A short, shareable id (out1a2b3c, "ref" for short) for one target execution's captured output; it appears on each target's line, and magus query output out1a2b3c prints those exact bytes. In OpenTelemetry terms it corresponds to a span (one target execution) within its trace (the whole magus invocation). See output-refs.

Trace

OpenTelemetry's name for one whole magus invocation; every target it runs is a span beneath it. See telemetry.

Pool

The concurrency pool: the shared set of slots that caps how many targets run in parallel on one machine. Its capacity defaults to MAGUS_CONCURRENCY, then 4 on GitHub-hosted runners, then min(NumCPU, 8); magus status and the dashboard report it live. See daemon.

Slot

One unit of the pool's capacity. A target acquires the slots it needs to run (most take one) and releases them when it finishes; the pool tracks capacity (total slots), running (acquired), and queued (blocked). See daemon.

Concurrency

How many targets run at once. It is bounded by the pool's capacity and set with --concurrency, MAGUS_CONCURRENCY, or the concurrency config key. See daemon.

Conventions

Placeholders

Angle brackets mark a value you replace with your own - never type the brackets:

magus run <target>
magus completion <shell>    # e.g. bash, zsh, fish

<target>, <path>, <shell>, <name> and the like are stand-ins, not literal text.