/** * lib/logger.ts * Einfacher File-Logger für alle Agenten. * * Schreibt strukturierte Log-Einträge nach ~/.pi/agent/logs/[_].log * Im verbose-Modus werden alle Einträge zusätzlich auf stderr ausgegeben. * Warnung/Fehler gehen immer auf stderr (unabhängig von verbose). * * Verwendung: * import { createLogger } from "../lib/logger.js"; * const log = createLogger({ verbose: cliFlags.verbose }); * log.info("Claims extrahieren...", { model, numChunks: 3 }); * log.warn("0 Claims in Chunk", { chunk: 2 }); * log.error("Ollama nicht erreichbar", { url: OLLAMA_HOST }); */ import { appendFileSync, mkdirSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; // --------------------------------------------------------------------------- // Konstanten // --------------------------------------------------------------------------- export const LOG_DIR = join(homedir(), ".pi", "agent", "logs"); export type LogLevel = "info" | "warn" | "error" | "debug"; // --------------------------------------------------------------------------- // Logger-Klasse // --------------------------------------------------------------------------- export class Logger { private logFile: string | null; private verbose: boolean; constructor(opts?: { logFile?: string; verbose?: boolean }) { this.logFile = opts?.logFile ?? null; this.verbose = opts?.verbose ?? false; } log(level: LogLevel, message: string, data?: Record): void { const ts = new Date().toISOString(); const dataStr = data ? " " + JSON.stringify(data) : ""; const line = `[${ts}] [${level.toUpperCase().padEnd(5)}] ${message}${dataStr}\n`; // In Datei schreiben (append, non-blocking, Fehler ignorieren) if (this.logFile) { try { appendFileSync(this.logFile, line); } catch { // Log-Fehler dürfen den Programmablauf nicht stören } } // Auf stderr ausgeben wenn verbose ODER level >= warn if (this.verbose || level === "error" || level === "warn") { process.stderr.write(line); } } info(message: string, data?: Record): void { this.log("info", message, data); } warn(message: string, data?: Record): void { this.log("warn", message, data); } error(message: string, data?: Record): void { this.log("error", message, data); } debug(message: string, data?: Record): void { this.log("debug", message, data); } } // Null-Logger für Kontexte wo kein Logging gewünscht ist export const nullLogger = new Logger(); // --------------------------------------------------------------------------- // Factory // --------------------------------------------------------------------------- /** * Erstellt einen Logger der in eine neue Log-Datei schreibt. * @param opts.jobId Optionaler Suffix für den Dateinamen (z.B. "umerziehung") * @param opts.verbose Wenn true: alle Log-Einträge auf stderr */ export function createLogger(opts?: { jobId?: string; verbose?: boolean }): Logger { try { mkdirSync(LOG_DIR, { recursive: true }); } catch { // Verzeichnis existiert bereits oder kein Schreibzugriff } const ts = new Date() .toISOString() .replace(/T/, "_") .replace(/:/g, "-") .slice(0, 19); // "2026-04-16_14-30-00" const suffix = opts?.jobId ? `_${opts.jobId}` : ""; const logFile = join(LOG_DIR, `${ts}${suffix}.log`); return new Logger({ logFile, verbose: opts?.verbose ?? false }); }