/** * The logger: one stream of events, written the same way by go-utils, * ts-utils and py-utils. The contract is the utils README * (github.com/adriangalilea/utils); this file is the TypeScript side of it. * * Writing * Every verb takes a message, then optional fields: * log.warn("retrying", { attempt: 2 }) * log.error("charge failed", { err }) * The message says what happened, the fields say with what. Fields are a * plain object; anything else panics. * * Verbs print at fixed levels * error ⨯ · fail ⨯ (error) · warn ⚠ · warnOnce ⚠ (warn) · info · success ✓ * · wait ○ · ready ▶ · step • (info) · debug ◦ (debug) · trace » (trace). * warnOnce fires once per message and scope (memory bounded). * * Scopes * log.scope("api").scope("auth") carries ["api", "auth"]. Its threshold * is read live from API_AUTH_LOG_LEVEL, then API_LOG_LEVEL, then * LOG_LEVEL, then setLogLevel(), then info. Levels: * silent < error < warn < info < debug < trace. * * Spans * A span is a piece of work written as two events (start, end) and a * span is a logger: what is written through it carries its span_id and * trace_id, nothing else does. There is no ambient context. * await log.span("sync", { repo }, async (s) => { * s.info("cloned", { files }) * await s.span("build", async (c) => { ... }) * s.set({ pushed: true }) * }) * span returns what fn returns; a throw ends the span failed (the error * in fields.err) and rethrows. The open form, for work that does not fit * a block: const s = log.begin("upload", { size }) ... s.end() or * s.end(err). Ending twice, or set() after the end, panics. * Whether a span is shown is decided once, at begin, by its start's level * against its scope's threshold; the end follows, so a hidden span writes * no end. A failure is never lost to that: a hidden span that fails * writes one fail line (its name; its fields with err and dur_ms) as a * line of the nearest shown span. What a hidden span holds attaches to the nearest * shown span, a joined remote one included: its lines carry that id, its * children and its traceparent() name it. With no shown ancestor its * lines carry no ids. A viewer never meets a missing parent. * * Traces cross processes explicitly * s.traceparent() is the W3C value to hand on (HTTP header, or the * TRACEPARENT env of a child). log.join(value) returns a logger whose * spans are children of that remote span; its lines outside a span carry * no ids. null/undefined returns the logger itself; a malformed value is * ignored with one warn per distinct value. TRACEPARENT in the * environment at process start is joined by every root span. * * Renderings * json: one event per line, keys in the contract's order, fields sorted * at every depth. human: symbols, colour (TTY, NO_COLOR, FORCE_COLOR), * fields dim as key=value, an error's stack under its line once (a span * failing with the error a span inside it failed with shows the message; * json writes it on both ends), spans as * "○ name" ... "✓ name 254ms" with what they hold indented. * Default: Node/Bun/Deno human when stderr is a TTY, else json; browser * human; Worker json. * * Configuration * Each variable has a setter; the setter is the program's default and the * variable, read live, overrides it: LOG_FORMAT=human|json (setLogFormat), * LOG_TIME=1|0 (setLogTime, a dim local HH:MM:SS before human lines, off * by default), LOG_LEVEL and {SCOPE}_LOG_LEVEL (setLogLevel), LOG_SERVICE * (setLogService), LOG_CONSOLE=1|0 (setLogConsole, on by default; off, * events only ship). Values match without regard to case, an empty variable * is unset, an unknown value panics. The service defaults to the entry * script's name without extension; in a Worker or browser it must be * set, or the first json event panics. * * Sinks * Node/Bun/Deno write every event through console.error: stderr is the * log, stdout stays data, and cli/live's console patch keeps lines above * a live region. Browsers and Workers use the level's console method. * * Shipping * LOG_SHIP= (setLogShip) also POSTs every event that passes its * threshold there: the json line, whatever the console renders, in * batches of lines joined by "\n" (text/plain;charset=utf-8, at most * 64 KB and 50 events). A batch leaves 2s after its first event, at 50, * when a span opened outside any local span ends (a request's trace * leaves together), before a Node/Bun process exits (beforeExit), and in * a browser on hidden or pagehide; whatever a hidden page sends goes * through sendBeacon, which outlives the page. LOG_SHIP_TOKEN * (setLogShipToken) is a bearer for servers; a browser sends none. * flushLog() sends what waits and resolves when nothing is in flight: * a Worker hands it to waitUntil, a process that calls process.exit() * awaits it first. Shipping never throws and never blocks a write: a * refused or lost batch is one warnOnce, scope ["ship"]. * * The wire shape is exported as LogEvent, and parseEvent(line) reads one back. */ export type LogLevel = "silent" | "error" | "warn" | "info" | "debug" | "trace"; type EventLevel = Exclude; export type LogVerb = "error" | "fail" | "warn" | "info" | "success" | "wait" | "ready" | "step" | "debug" | "trace"; export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue; }; /** What a verb, span or set() takes: a plain object, any values (serialized by the contract's table). */ export type Fields = Readonly>; interface EventBase { readonly time: string; readonly level: EventLevel; readonly verb: LogVerb; readonly msg: string; readonly service: string; readonly scope?: readonly string[]; readonly fields?: { readonly [key: string]: JsonValue; }; } /** A log line. Inside a shown span it carries the span's trace_id and span_id. */ export interface LineEvent extends EventBase { readonly phase?: undefined; readonly trace_id?: string; readonly span_id?: string; readonly parent_id?: undefined; readonly status?: undefined; readonly dur_ms?: undefined; } export interface SpanStartEvent extends EventBase { readonly level: "info"; readonly verb: "wait"; readonly phase: "start"; readonly trace_id: string; readonly span_id: string; readonly parent_id?: string; readonly status?: undefined; readonly dur_ms?: undefined; } interface SpanEndBase extends EventBase { readonly phase: "end"; readonly trace_id: string; readonly span_id: string; readonly parent_id?: string; /** Monotonic, milliseconds, 3 decimals. */ readonly dur_ms: number; } export interface SpanOkEvent extends SpanEndBase { readonly level: "info"; readonly verb: "success"; readonly status: "ok"; } /** A failed span's end: the error is in fields.err. */ export interface SpanFailEvent extends SpanEndBase { readonly level: "error"; readonly verb: "fail"; readonly status: "error"; } export type SpanEndEvent = SpanOkEvent | SpanFailEvent; /** One event of the stream, as written on the wire. Keys appear in this order. */ export type LogEvent = LineEvent | SpanStartEvent | SpanEndEvent; export type LogFormat = "human" | "json"; /** The program's default level. LOG_LEVEL and {SCOPE}_LOG_LEVEL override it. */ export declare function setLogLevel(level: LogLevel): void; /** The program's default rendering, human or json. LOG_FORMAT overrides it. */ export declare function setLogFormat(format: LogFormat): void; /** A dim local HH:MM:SS before human lines (json always carries time). LOG_TIME overrides it. */ export declare function setLogTime(on: boolean): void; /** * Whether events are written to the console (stderr, a browser's console) as well * as shipped: off, they only ship. A page that ships its stream needs no console * a visitor can read. LOG_CONSOLE overrides it. */ export declare function setLogConsole(on: boolean): void; /** The program's name in every event. LOG_SERVICE overrides it. */ export declare function setLogService(name: string): void; /** Where events are also POSTed, in batches. LOG_SHIP overrides it. */ export declare function setLogShip(url: string): void; /** The bearer sent with each batch (servers only). LOG_SHIP_TOKEN overrides it. */ export declare function setLogShipToken(token: string): void; /** * Send what waits to LOG_SHIP and resolve when no batch is in flight. A * Worker hands it to waitUntil; a process that calls process.exit() awaits it * first. Never rejects: a lost batch is a warnOnce. */ export declare function flushLog(): Promise; export interface Logger { error(message: string, fields?: Fields): void; fail(message: string, fields?: Fields): void; warn(message: string, fields?: Fields): void; /** warn, once per message and scope for the process. */ warnOnce(message: string, fields?: Fields): void; info(message: string, fields?: Fields): void; success(message: string, fields?: Fields): void; wait(message: string, fields?: Fields): void; ready(message: string, fields?: Fields): void; /** An indented sub-step. */ step(message: string, fields?: Fields): void; debug(message: string, fields?: Fields): void; trace(message: string, fields?: Fields): void; /** A child logger: events carry the scope, its level reads {SCOPE}_LOG_LEVEL first. */ scope(name: string): Logger; /** * Run fn inside a span and return what it returns (a Promise when fn is * async). A throw or rejection ends the span failed and is rethrown. */ span(name: string, fn: (s: Span) => T): T; span(name: string, fields: Fields, fn: (s: Span) => T): T; /** Open a span by hand, for work that does not fit a block. End it once with end(). */ begin(name: string, fields?: Fields): Span; /** * A logger whose spans are children of a remote span. null/undefined * returns this logger; a malformed value is ignored with one warn. */ join(traceparent: string | null | undefined): Logger; } export interface Span extends Logger { /** Fields learned while the span runs, carried on its end event. */ set(fields: Fields): void; /** The W3C traceparent value to hand to a child process or request. */ traceparent(): string; /** The id of the trace it belongs to, as every event of it carries in `trace_id`: * what a reader of the stream files and finds it under. */ readonly traceId: string; /** End the span: ok with no error, failed with one (in fields.err). Twice panics. */ end(err?: unknown): void; } /** The root logger. */ export declare const log: Logger; /** * A reader of a stream of events that renders each one the way the `human` format shows * it live: a span's start and end at its depth, a line under the span that wrote it, * durations, fields dim, an error as its type and message with its stack under it. The * depth comes from the stream (each span's `parent_id`), so a stored file, or a stream * another process shipped, reads like the console of the process that wrote it. Call it * with every event in order; `clock` puts each event's own local HH:MM:SS first. * * ```typescript * const show = humanReader({ clock: true }) * for (const line of text.split('\n')) { * const e = parseEvent(line) * if (e) console.log(show(e)) * } * ``` */ export declare function humanReader({ clock, }?: { clock?: boolean; }): (e: LogEvent) => string; /** Where a trace's time went, by what its spans were doing. */ export interface Breakdown { /** The time the outermost spans covered, together. */ readonly wall_ms: number; /** Each kind's own time: the time its spans ran with no child span running. Spans * that ran side by side each count, so these can add up to more than the wall. */ readonly by: Readonly>; } /** * Where a trace's time went: each span's own time (its duration, less the time its * children cover) counts for the span's `kind` field, or the nearest enclosing span's * that has one, else `other`. Name what a span does once, where it begins * (`span('look', { kind: 'perception' })`), and every span inside it counts for it * until one says otherwise. Spans still open are not counted. * * ```typescript * breakdown(events) // { wall_ms: 61200, by: { inference: 38100, input: 14800, wait: 7900, perception: 400 } } * ``` */ export declare function breakdown(events: Iterable): Breakdown; export declare function parseEvent(text: string): LogEvent | null; export {}; //# sourceMappingURL=log.d.ts.map