Logging and profiling
The LOG_LEVEL grammar and its four modules, timestamps, the stderr-only rule, NO_COLOR, and the three artifacts DEBUG_PERF captures.
Logging is configured by the environment, so it applies to every command
and works before any flag is parsed. serve, pull, and commit also
take --log-level and --log-timestamp, which override the environment
once the flags resolve; the few records emitted before that point — the
command router, a configuration refusal — follow the environment.
| Variable | Effect |
|---|---|
LOG_LEVEL |
Per-module levels. A bare level is the default. |
SLIVINGDOC_LOG_TIMESTAMP |
false removes the time= field, for hosts that stamp log lines themselves. |
NO_COLOR |
Any non-empty value disables ANSI colour and the terminal marks and spinners. |
DEBUG_PERF |
Captures CPU, heap, and execution-trace profiles across one whole command. |
Records
Records are structured key=value text on stderr. Each one carries a
timestamp (unless timestamps are off), a level, and the module that
emitted it.
Warning: Stdout carries only MCP protocol messages and command output. Nothing ever logs to stdout — that is what keeps the stdio transport usable.
LOG_LEVEL
LOG_LEVEL takes a comma-separated list. module=level sets one module;
a bare level sets the default for the rest:
LOG_LEVEL="cli=warn,mcp=debug,info"
The modules are:
| Module | Emits |
|---|---|
cli |
Command routing. |
app |
Startup and shutdown. |
mcp |
A start and a completion record per tool call, both carrying mcpReqID. |
notebook |
Best-effort checkpoint and cleanup records, and warnings when a pack cannot be written to the shared cache. |
The levels are debug, info, warn (or warning), and error. The
default is info.
A malformed LOG_LEVEL is reported and falls back to info; it never
refuses startup. An invalid --log-level flag value, in contrast,
refuses startup like any other flag. The flag takes the same grammar as
the environment variable.
Startup records
At startup the app module writes, at info, which store the process
chose. Neither record carries a key or a token.
storage selected backend=hosted endpoint=https://api.slivingdoc.dev space=notes token=login
hosted space resolved space=notes from=default space
storage selectednames thebackend(s3orhosted), theendpoint, thespace(the bucket in S3 mode), and where the credential came from:tokenisnonefor S3,envforSLIVINGDOC_TOKEN, orloginfor a stored login. An S3 process with no endpoint of its own logsaws-default (SDK: env or profile), or the AWS variable that supplies one. A hosted process with a token and no named space logsthe token's ownhere, before the lookup.hosted space resolvedfollows in hosted mode with the finalspaceand where it came from:--space,--bucket,SLIVINGDOC_SPACE,SLIVINGDOC_BUCKET,default space(the login’s stored default), ortoken.
These two records are the quickest way to see why a process picked S3 or
hosted storage; see
Choosing the storage.
At debug, the app module also says why the shared pack cache is not in
use, when it is not.
Correlating a tool call
The mcp module writes one pair of records per tool call — a start
record and a completion record — and both carry the same mcpReqID. That
value is the diagnosticId an agent sees in a structured error, so a
failing call in an agent transcript points straight at the two log
records that describe it. The completion record carries duration and an
outcome of ok, error, or invalid_request; an error outcome also
carries cause. Both failing outcomes are logged at warn, the start and
the successful completion at info. It is also how an operator reads the full cause
of an ENGINE_FAILED error, which the envelope itself withholds. See
Errors.
Timestamps
--log-timestamp (environment SLIVINGDOC_LOG_TIMESTAMP, default
true) set to false removes the time= field. Use it when the host
already stamps every line it captures, so the record is not stamped
twice.
Colour
Any non-empty NO_COLOR disables the ANSI colour of the log levels, and
makes every terminal presentation plain at the same time: the CLI report’s
colour and marks, the home screen, help headers, the progress spinner, and
the styled login output. Interactive pickers still appear on a terminal.
The convention is the value being non-empty, not the literal string
true. See CLI for what a terminal report adds.
Profiling with DEBUG_PERF
DEBUG_PERF captures performance profiles across one whole command —
startup, the operation, and shutdown — for finding where a slow pull or
commit spends its time.
| Value | Effect |
|---|---|
1 or true |
Write under slivingdoc-perf/ in the system temporary directory. |
0, false, empty |
No capture. |
| Any other value | Use that value as the base directory. |
DEBUG_PERF=1 slivingdoc pull ./notes
Each invocation creates its own timestamped run directory under the base,
so repeated benchmark runs never overwrite each other and can be compared
with go tool pprof -diff_base. A run directory holds three artifacts:
| File | Shows |
|---|---|
cpu.pprof |
Where CPU time went: go tool pprof cpu.pprof. |
heap.pprof |
What the command retained at exit: go tool pprof heap.pprof. |
trace.out |
The execution timeline, including time blocked on the network and on locks: go tool trace trace.out. |
For an operation dominated by object-store round trips, the CPU profile
stays near-empty and trace.out shows the waiting. That split is the
reason both are captured.
The exact paths are reported on stderr when the capture starts and finishes; stdout stays protocol-only. A capture that cannot start or finish is a warning, never a refusal, and it never changes the command’s result or exit code.
Next
- Errors — the diagnostic ID and what each error means.
- CLI — exit behaviour and the report grammar.
- Configuration —
--log-leveland--log-timestampbeside every other flag.