logging

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package logging is the CLI's leveled diagnostic logger, built on log/slog.

It is deliberately distinct from program output. Command results and the structured error envelope are the product, and they go through internal/output (the stdout/stderr contract): results on stdout, one {"error": ...} object plus actionable guidance on stderr, never gated. This package carries the other thing — operational logs about the CLI's own behavior (retry/backoff decisions, request target and timing, clock-sync attempts, journal-write health, the order-place reconcile trail) — to stderr, gated by level. The default threshold is Error: a command's outcome and failures already reach the user through program output, so warn-and-below operational logs are an opt-in diagnostic (they would otherwise only echo, on the same stderr sink, what the error envelope already says). Opt in with --log-level (or --debug, which lowers the threshold to Debug).

Levels, coarse to fine: error, warn, info, debug, and trace (LevelTrace, one step below Debug).

  • Error is the only tier shown by default, so it is kept LEAN: it is reserved for a genuinely operational failure with NO program-output equivalent — something the operator must see that the error envelope won't tell them. The sole case is a monitor --where/--on/--jq per-event eval error that SILENTLY SKIPS events while the run still exits 0. A condition that is already program output must NEVER be logged at Error (it would duplicate the error on the shared stderr sink).
  • Debug carries the DECISIONS and OUTCOMES a "run with --log-level debug and send me the log" round-trip needs (each request, the place protocol's resend/duplicate/lookup verdict, a paged walk's pages/rows/complete summary).
  • Trace adds the per-ITERATION firehose (each send attempt, each lookup retry, each history/candles page) for dissecting a single operation.

A money-unsafe verdict (a placement UNKNOWN or placed-but-unreadable) is NOT given a default-visible level: it already reaches the user as program output (the returned error + a guidance note), so the operational log records it at Debug as part of the trail rather than re-emitting it. --debug resolves to Debug, never Trace; Trace is explicit-opt-in (--log-level trace) only. slog.Logger has no Trace method, so Trace is this package's shim over it.

What is NOT a log (stays program output, never level-gated): the result, the error envelope, the "signing as key" safety disclosure, doctor's report, and the key/keystore/ip command guidance. A log is telemetry about how the tool is operating; product output is the result or a safety-relevant fact about the action the user requested.

Output is one record per line, in one of three shapes selected by Style (the cli resolves it from --log-format and whether --log-file is in use):

korbit-cli: <level>: <message>[ key=value …]   // text, stderr (the default)
<rfc3339-local> <level> <message>[ key=value …] // text, --log-file (Style.Timestamp)
{"time":"…","level":"…","msg":"…",…}            // --log-format json (FormatJSON)

where <level> is one of trace/debug/info/warn/error (the JSON level uses the same vocabulary). The "korbit-cli: " tag scopes a line to this tool on a shared terminal; a file or JSON trail has no such session to scope, so it carries a wall-clock timestamp instead.

Each record is emitted to the sink in exactly ONE write (the line is fully assembled first, in every format). Within a single logger and its With/ WithGroup derivatives that write is also serialized by a shared lock, so one logger is safe for concurrent use. But several INDEPENDENTLY constructed loggers (the monitor's operational + stream + notice loggers) hold separate locks, so keeping THEIR lines from interleaving on one shared sink is the caller's job: the cli wraps the sink in a locking writer (*syncWriter), and the one-write-per-record guarantee above is what makes that serialization line-atomic.

Never log secrets. A value that carries key material must redact itself: the keystore-backed types already do under fmt verbs, and any value passed as a log attribute should implement slog.LogValuer if it could expose a secret. The keystore/keys layer in particular logs only non-secret facts (backend, key name, public api-key id, OSStatus-bearing error strings, paths, counts) — never PEM/ciphertext/AES-key/HMAC-secret bytes.

Wiring. The lower layers (wire/clock/keystore/keys/config/journal/callrec/ sandbox/selfupdate/stream and the rawapi/ops call path) take an OPTIONAL operational logger and log their own fragile-path diagnostics directly: a struct exposes a `Log *slog.Logger` field (nil = silent) and resolves it once with Or so call sites never need a nil check, or a free function takes a trailing optional `*slog.Logger`. The cli (frontend) is the only layer that BUILDS a logger — it wires rt.logger() (or, for the alt-screen tui, the file-only logger from runtime.tuiLogging) into those fields/params. rawapi.Client logs only its one diagnostic (a typed-decode mismatch); ops.API logs the operation-LEVEL decisions the wire layer can't see (the place reconcile trail, paging summaries) and leaves a single-call passthrough to the wire layer's own log. Three seams stand outside this convention: the wire retry-decision seam (korbit.Client.Observe func(string)), the callrec journal-write warn callback, and ops.API.Stderr (program output, not a log).

Index

Constants

View Source
const LevelOff = slog.Level(1 << 30)

LevelOff is a threshold above every real record level, so a logger created with it emits nothing. It backs the "off" log level (--log-level off).

View Source
const LevelTrace = slog.Level(-8)

LevelTrace is one step below slog.LevelDebug — the firehose tier for per-iteration call sites (each send attempt, each lookup retry, each history/ candles page) that would bloat a normal Debug log. It backs "--log-level trace": Debug shows the decisions and outcomes a support log needs; Trace adds the step-by-step detail when a single operation must be dissected. --debug resolves to Debug (LevelFor), never Trace — Trace is explicit-opt-in only.

Variables

This section is empty.

Functions

func LevelFor

func LevelFor(debug bool) slog.Level

LevelFor maps the CLI's debug switch onto a handler threshold: Debug when debug mode is on (show everything), else Error. A command's failures and safety-relevant facts surface through program output (the error envelope + guidance notes, never gated), so the warn-and-below operational log is a pure opt-in diagnostic that would otherwise only echo what the error already says; opt in with --log-level or --debug. The Error tier alone shows by default and is reserved for a genuinely operational failure with NO program-output equivalent — keep it lean and never duplicate the error envelope (a default run with no such failure is quiet). This is the default when no explicit --log-level is given.

func New

func New(w io.Writer, level slog.Level) *slog.Logger

New returns a logger that writes human diagnostic lines to w at or above level in the default stderr style (text, "korbit-cli: " tagged). Each record is assembled into a single line and written under an internal lock, so the returned logger and every logger derived from it are safe to use concurrently.

func NewStyled

func NewStyled(w io.Writer, level slog.Level, st Style) *slog.Logger

NewStyled is New with an explicit Style (text vs JSON, timestamped vs tagged). The cli resolves one Style per invocation from --log-format and whether --log-file is in use, and builds every logger — the operational logger and the stream-layer loggers — through it so a run's lines share a shape. Like New, every derived logger is safe for concurrent use.

func Nop

func Nop() *slog.Logger

Nop returns a logger that discards every record. It is the right zero value for an optional Log field on a lower-layer type, so call sites can log unconditionally (the handler drops everything) without a nil check.

func Or

func Or(l *slog.Logger) *slog.Logger

Or returns l, or a no-op logger when l is nil. A lower layer that takes an optional *slog.Logger calls Or(x.Log) once and then logs unconditionally, so an unwired field is silent rather than a nil panic.

func ParseLevel

func ParseLevel(s string) (level slog.Level, ok bool)

ParseLevel maps a level word (case- and space-insensitive) onto a handler threshold: trace, debug, info, warn, error, or off (off suppresses every operational log). ok is false for an unrecognized word, so each caller can phrase its own usage error. This is the one place the level vocabulary is defined.

func Trace

func Trace(l *slog.Logger, msg string, args ...any)

Trace logs at LevelTrace (one step below Debug). slog.Logger exposes no Trace method, so this is the package's Trace shim: it is the firehose tier for per-iteration call sites. l must be non-nil (resolve via Or first); args are slog key/value pairs. The handler gates by level, so a disabled Trace costs only the Enabled check.

Types

type Format

type Format int

Format selects the wire shape of each emitted record.

const (
	// FormatText is the human-readable one-line form. On stderr it is tagged
	// "korbit-cli: <level>: <message>[ k=v …]"; with [Style.Timestamp] set (the
	// form used when logs are diverted to a file) it is stamped with a local
	// RFC3339 timestamp instead of the tag — "<ts> <level> <message>[ k=v …]" —
	// since a file trail wants a wall-clock anchor and has no terminal session to
	// scope the "korbit-cli:" tag to.
	FormatText Format = iota
	// FormatJSON emits one JSON object per record via slog's JSON handler (the
	// standard time/level/msg keys, then the attributes), with the level rendered
	// in this package's vocabulary (trace/debug/info/warn/error).
	FormatJSON
)

type Style

type Style struct {
	Format Format
	// Timestamp applies to FormatText only: prefix each line with a local
	// RFC3339 timestamp and drop the "korbit-cli: " tag. FormatJSON always
	// carries a timestamp (slog's time key), so this field is ignored there.
	Timestamp bool
}

Style is the resolved presentation for a logger: which Format, and — for FormatText only — whether to stamp lines with a timestamp instead of the "korbit-cli: " tag. The zero Style is the stderr default (text, tagged, no timestamp).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL