Logging & output#

Emitting messages at the right level and framing a long-running command’s output.

Log at the right level#

Narrate a command with knit_trace/debug/info/warning/error/critical and control verbosity with KNIT_LOG_LEVEL or knit_log_set_level.

APIs: knit_trace, knit_debug, knit_info, knit_warning, knit_error, knit_critical, knit_fatal, knit_log_set_level, KNIT_LOG_LEVEL

Knit gives you one logging function per severity — knit_trace, knit_debug, knit_info, knit_warning, knit_error, and knit_critical — so you can narrate a command and let the reader choose how much to see. Each takes printf-style arguments, prefixes the line with [knit:<level>], and writes to stderr, leaving stdout for the command’s real output:

@command "work" "Do some work, narrating at several log levels."
_do_work() {
    knit_trace   "entering _do_work"            # shown only at --log-level trace
    knit_debug   "computing the result"         # trace or debug
    knit_info    "starting the computation"     # info and below (the default)
    knit_warning "input looks unusually large"  # warning and below
    knit_error   "a recoverable step failed"    # error and below
    # knit_fatal prints at ANY level and then exits non-zero:
    #   knit_fatal "unrecoverable: %s" "${reason}"
    echo "done"
}
@done
knit_register "work" _do_work "Do some work, narrating at several log levels."
_do_work() {
    knit_trace   "entering _do_work"            # shown only at --log-level trace
    knit_debug   "computing the result"         # trace or debug
    knit_info    "starting the computation"     # info and below (the default)
    knit_warning "input looks unusually large"  # warning and below
    knit_error   "a recoverable step failed"    # error and below
    # knit_fatal prints at ANY level and then exits non-zero:
    #   knit_fatal "unrecoverable: %s" "${reason}"
    echo "done"
}
knit_done

A message prints only when its level is at or above the current threshold. The levels, from most to least verbose, are trace < debug < info < warning < error < critical. The default threshold is info, so knit_trace and knit_debug stay silent until you ask for them.

Set the threshold two ways. Before launch, export the KNIT_LOG_LEVEL environment variable; from inside a script, call knit_log_set_level:

# Raise the threshold from inside a body so only warnings and above surface.
# The same effect is available before launch with KNIT_LOG_LEVEL=warning.
@command "quiet" "Run the work quietly (warnings and errors only)."
_go_quiet() {
    knit_log_set_level warning
    _do_work
}
@done
# Raise the threshold from inside a body so only warnings and above surface.
# The same effect is available before launch with KNIT_LOG_LEVEL=warning.
knit_register "quiet" _go_quiet "Run the work quietly (warnings and errors only)."
_go_quiet() {
    knit_log_set_level warning
    _do_work
}
knit_done

KNIT_LOG_LEVEL=trace ./exp.sh work turns everything on for one run without touching the script. An invalid level is rejected: knit_log_set_level returns non-zero and leaves the level unchanged, and an invalid KNIT_LOG_LEVEL is reset to info with a warning when knit loads.

knit_fatal is the odd one out: it prints at any level and then exits the process with status 1, so reach for it only when the command cannot continue. If knit has captured a subprocess’s output in its trace file, the fatal message also points you at that file.

Frame a long-running command#

Pipe a noisy command’s output into knit_framed to keep it inside a fixed scrolling box, with safe non-TTY passthrough.

APIs: knit_framed

A build or install can spew hundreds of lines. Pipe its output into knit_framed to confine that noise to a fixed, scrolling box with a title, instead of letting it flood the terminal:

# Pipe a long-running command's output into knit_framed to keep it inside a
# fixed, scrolling box with a title. On a non-TTY (a log file, CI) the input is
# forwarded unchanged, so framing never corrupts captured output.
@command "build" "Build something, framing the output."
_do_build() {
    _build_steps | knit_framed 10 60 --title "Building" --cleanup
}
_build_steps() {
    echo "step 1/3"
    echo "step 2/3"
    echo "step 3/3"
}
@done
# Pipe a long-running command's output into knit_framed to keep it inside a
# fixed, scrolling box with a title. On a non-TTY (a log file, CI) the input is
# forwarded unchanged, so framing never corrupts captured output.
knit_register "build" _do_build "Build something, framing the output."
_do_build() {
    _build_steps | knit_framed 10 60 --title "Building" --cleanup
}
_build_steps() {
    echo "step 1/3"
    echo "step 2/3"
    echo "step 3/3"
}
knit_done

The two leading positional arguments are the frame’s height and width (both default to the terminal size; pass -1 for either to keep the default). Useful named options follow:

  • --title <text> centers a label on the top border.

  • --cleanup erases the frame once the command finishes, leaving no trace on screen.

  • --log-level <level> only draws the frame when KNIT_LOG_LEVEL is at or below that level; otherwise the input is drained silently. Use it to hide verbose output unless someone is debugging.

  • --frame-color / --text-color (and their --*-bg-color variants) tint the border and text.

knit_framed only draws when stdout is a terminal. When it is not — a redirect to a log file, a pipe, a CI run — stdin is forwarded to stdout unchanged, so framing decorates an interactive session but never corrupts captured or piped output.