evOo-local/packages/logging/src/console-sink.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

43 lines
1.4 KiB
TypeScript

import type { LogEvent, LogFormat, LogSink } from "./types.js";
const SKIP_PRETTY = new Set(["timestamp", "level", "message"]);
export function createConsoleSink(options: { format?: LogFormat } = {}): LogSink {
const format = options.format ?? "json";
return {
write(event) {
emit(event, format);
},
};
}
function emit(event: LogEvent, format: LogFormat): void {
const line = format === "pretty" ? formatPretty(event) : JSON.stringify(event);
if (event.level === "error") console.error(line);
else if (event.level === "warn") console.warn(line);
else console.info(line);
}
function formatPretty(event: LogEvent): string {
const time = event.timestamp.slice(11, 23) || event.timestamp;
const fields: string[] = [];
for (const [key, value] of Object.entries(event)) {
if (SKIP_PRETTY.has(key) || value === undefined) continue;
fields.push(`${key}=${formatField(value)}`);
}
const suffix = fields.length > 0 ? ` ${fields.join(" ")}` : "";
return `${time} ${event.level.toUpperCase().padEnd(5)} ${event.message}${suffix}`;
}
function formatField(value: unknown): string {
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (value instanceof Error) return value.message;
try {
const json = JSON.stringify(value);
return json && json.length > 200 ? `${json.slice(0, 197)}...` : (json ?? "");
} catch {
return "[unserializable]";
}
}