Text_Agent/lib/logger.ts
dschlueter 5146b7fa30 feat: Pi Text-Agent — initialer Commit (sauberes Repo)
Vollständiges Multi-Agenten-System für Fact-Checking, Artikelschreiben
und Argumentationsanalyse. Zwei Backends: llama.cpp (★ bevorzugt) und Ollama.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 04:21:48 +02:00

107 lines
3.4 KiB
TypeScript

/**
* lib/logger.ts
* Einfacher File-Logger für alle Agenten.
*
* Schreibt strukturierte Log-Einträge nach ~/.pi/agent/logs/<timestamp>[_<jobId>].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<string, unknown>): 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<string, unknown>): void {
this.log("info", message, data);
}
warn(message: string, data?: Record<string, unknown>): void {
this.log("warn", message, data);
}
error(message: string, data?: Record<string, unknown>): void {
this.log("error", message, data);
}
debug(message: string, data?: Record<string, unknown>): 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 });
}