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>
107 lines
3.4 KiB
TypeScript
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 });
|
|
}
|