8.1. Logging
Morloc programs can emit per-call log lines around any labeled term in source. Logging is opt-in — a term emits start, pass, and fail messages only after the user wires it up in the program’s YAML config.
The log lines go to stderr, so the program’s stdout (the computed result and any user-printed data) is unaffected. Templates are user-controlled and may include ANSI color codes; colors are automatically stripped when stderr is not a terminal so log files and pipes never contain control bytes.
8.1.1. Enabling logging
Two things turn logging on for a term:
-
The term must be labeled in source code. A labeled term is written
label@term. For example,a@mapis the termmapwith labela. Labels are per-call-site, so(a@foo x, b@foo y)labels two distinct invocations of the samefoo. -
The label must appear in the program’s
.yamlconfig underlabeled-groupswithlog: true. The config lives next to the.locsource: formain.loc, the config ismain.yaml.
A minimal config:
labeled-groups:
big: { log: true }
A label group can be applied to many terms (big@read, big@parse,
big@save); all of them log under the same group.
8.1.2. Template placeholders
The compiler emits up to three lines per labeled call — start (entry),
pass (success), and fail (exception). Each line’s text is a user
template with {placeholder} substitutions. The default template is:
log-template:
start: "[{date}] {module}:{line}:{name}:{lang} start"
pass: "[{date}] {module}:{line}:{name}:{lang} pass (time={runtime})"
fail: "[{date}] {module}:{line}:{name}:{lang} fail (time={runtime})"
Templates resolve in this order, per subfield: per-label override >
program-wide log-template (top of the config) > built-in default. A
null subfield silences that event. A common case is to silence the
verbose start and pass messages and keep only the failure trace:
log-template:
start: null
pass: null
fail: "{date} {module}:{line}:{name} FAILED (time={runtime})"
Setting all three subfields to null while keeping log: true is
rejected at compile time as contradictory (use log: false instead).
Available placeholders:
| Placeholder | Value |
|---|---|
|
The labeled term’s identifier in source (e.g. |
|
The label group name (e.g. |
|
The pool language: |
|
The source file path of the labeled reference. |
|
Line number of the labeled reference. |
|
Column number of the labeled reference. |
|
The manifold ID assigned by the compiler. Useful for cross-referencing with |
|
UTC ISO 8601 timestamp at the moment of emission, second resolution (e.g. |
|
Elapsed time in seconds since the call’s start, with microsecond precision. Pass |
|
Call id pairing a start with its pass/fail. Format |
Unknown placeholder names are a compile-time error citing the file and line of the offending YAML entry.
8.1.3. Color codes
A {c:NAME} placeholder expands to the corresponding ANSI SGR escape
sequence at compile time. Apply a color, render the text, then reset
with {c:reset}:
start: "{c:blue}{name}{c:reset} start"
Attributes and clears:
| Placeholder | Effect |
|---|---|
|
Reset every attribute to terminal default. |
|
Bold / increased intensity. |
|
Faint. |
|
Italic. |
|
Underline. |
|
Slow blink. |
|
Rapid blink. Spotty terminal support; prefer |
|
Swap foreground and background. |
|
Conceal text (still occupies space). |
|
Strikethrough. |
|
Cancel bold (ANSI conflates with dim; see |
|
Cancel dim (same SGR code as |
|
Cancel italic. |
|
Cancel underline. |
|
Cancel blink. |
|
Cancel reverse. |
|
Cancel conceal. |
|
Cancel strikethrough. |
Foreground colors:
| Standard | Bright |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
— |
Background colors:
| Standard | Bright |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
— |
8.1.4. Terminal detection and NO_COLOR
Color codes from {c:…} placeholders (or raw ANSI escapes a user
writes directly into a template) are emitted unchanged when stderr is
a terminal and the environment variable NO_COLOR is unset. In every
other case — stderr redirected to a pipe or file, or NO_COLOR set to
any value — the runtime strips all CSI sequences before writing, so
the output is plain text.
This means the same program produces colored output when run interactively:
./main '[[1,2],[3,4,5]]' # colors emitted to terminal
and plain text when piped or redirected:
./main '[[1,2],[3,4,5]]' 2> run.log # run.log is plain text
./main '[[1,2],[3,4,5]]' 2>&1 | grep pass # grep sees plain text
To suppress color even in an interactive terminal (e.g. for screen
captures, color-blind users, or terminals with non-standard palettes),
set NO_COLOR:
NO_COLOR=1 ./main '[[1,2],[3,4,5]]'
NO_COLOR follows the convention at no-color.org:
any non-empty value disables color; the variable being unset means color
is allowed.
8.1.5. Worked example
The source labels two terms — a@map and b@sum — in a small
two-stage pipeline that sums each inner list:
module main (foo)
import root-py
sum :: [Real] -> Real
sum = fold (+) 0.0
foo :: [[Real]] -> [Real]
foo = a@map b@sum
The config enables both labels and uses a maximalist template that exercises every color category and every placeholder:
log-template:
start: "{date} {c:gray}{module}:{line}:{column}:{lang}{c:reset} {name}:{id}: {c:blue}start{c:reset}"
pass: "{date} {c:gray}{module}:{line}:{column}:{lang}{c:reset} {name}:{id}: {c:green}pass{c:reset} {c:grey}(time: {runtime}){c:reset}"
fail: "{date} {c:gray}{module}:{line}:{column}:{lang}{c:reset} {name}:{id}: {c:red}fail{c:reset} {c:grey}(time: {runtime}){c:reset}"
labeled-groups:
a: { log: true }
b: { log: true }
Build and run on a two-row input:
morloc make -o main main.loc
./main '[[1,2],[3,4,5]]'
Output to stderr, rendered with the colors a terminal would show (one
start/pass pair per call; a@map brackets two inner b@sum calls; the
nested call ids 0, 1, 2 pair start lines with their corresponding
pass lines):
2026-06-08T16:59:59Z main.loc:9:7:py map:33804:0: start
2026-06-08T16:59:59Z main.loc:9:13:py sum:33804:1: start
2026-06-08T16:59:59Z main.loc:9:13:py sum:33804:1: pass (time: 0.000023)
2026-06-08T16:59:59Z main.loc:9:13:py sum:33804:2: start
2026-06-08T16:59:59Z main.loc:9:13:py sum:33804:2: pass (time: 0.000009)
2026-06-08T16:59:59Z main.loc:9:7:py map:33804:0: pass (time: 0.000520)
Output to stdout (the actual program result):
[3,12]
Run the same command with NO_COLOR=1 for plain output even when stderr
is a terminal:
NO_COLOR=1 ./main '[[1,2],[3,4,5]]'