/** * Erweiterter Logger mit Processor-Architektur, Request-ID-Unterstützung und Server-Kommunikation. */ export class Logger { /** * Konfiguration des Loggers */ static config = { enabled: true, apiEndpoint: '/api/log', consoleEnabled: true, serverEnabled: true, minLevel: 'debug', }; /** * Liste der registrierten Processors */ static processors = []; /** * Registrierte Handler */ static handlers = []; /** * Aktive RequestID */ static requestId = null; /** * Logger initialisieren */ static initialize(config = {}) { // Konfiguration überschreiben this.config = { ...this.config, ...config }; // Standard-Processors registrieren this.registerProcessor(this.requestIdProcessor); this.registerProcessor(this.timestampProcessor); // Standard-Handler registrieren if (this.config.consoleEnabled) { this.registerHandler(this.consoleHandler); } if (this.config.serverEnabled) { this.registerHandler(this.serverHandler); } // Request-ID aus dem Document laden, wenn vorhanden if (typeof document !== 'undefined') { this.initFromDocument(); } // Unhandled Errors abfangen this.setupErrorHandling(); } /** * Debug-Nachricht loggen */ static debug(...args) { this.log('debug', ...args); } /** * Info-Nachricht loggen */ static info(...args) { this.log('info', ...args); } /** * Warnungs-Nachricht loggen */ static warn(...args) { this.log('warn', ...args); } /** * Fehler-Nachricht loggen */ static error(...args) { this.log('error', ...args); } /** * Log-Nachricht mit beliebigem Level erstellen */ static log(level, ...args) { if (!this.config.enabled) return; // Level-Validierung const validLevels = ['debug', 'info', 'warn', 'error']; if (!validLevels.includes(level)) { level = 'info'; } // Nachricht und Kontext extrahieren const message = this.formatMessage(args); const context = args.find(arg => typeof arg === 'object' && arg !== null && !(arg instanceof Error)) || {}; // Exception extrahieren, falls vorhanden const error = args.find(arg => arg instanceof Error); if (error) { context.exception = error; } // Log-Record erstellen let record = { level, message, context, timestamp: new Date(), extra: {}, }; // Alle Processors durchlaufen this.processors.forEach(processor => { record = processor(record); }); // Log-Level-Prüfung (nach Processors, da sie das Level ändern könnten) const levelPriority = { debug: 100, info: 200, warn: 300, error: 400, }; if (levelPriority[record.level] < levelPriority[this.config.minLevel]) { return; } // Alle Handler durchlaufen this.handlers.forEach(handler => { handler(record); }); } /** * Nachricht aus verschiedenen Argumenten formatieren */ static formatMessage(args) { return args .filter(arg => !(arg instanceof Error) && (typeof arg !== 'object' || arg === null)) .map(arg => String(arg)) .join(' '); } /** * Processor registrieren */ static registerProcessor(processor) { if (typeof processor !== 'function') return; this.processors.push(processor); } /** * Handler registrieren */ static registerHandler(handler) { if (typeof handler !== 'function') return; this.handlers.push(handler); } /** * Error-Handling-Setup */ static setupErrorHandling() { if (typeof window !== 'undefined') { // Unbehandelte Fehler abfangen window.addEventListener('error', (event) => { this.error('Unbehandelter Fehler:', event.error || event.message); }); // Unbehandelte Promise-Rejects abfangen window.addEventListener('unhandledrejection', (event) => { this.error('Unbehandelte Promise-Ablehnung:', event.reason); }); } } /** * Request-ID aus dem Document laden */ static initFromDocument() { const meta = document.querySelector('meta[name="request-id"]'); if (meta) { const fullRequestId = meta.getAttribute('content'); // Nur den ID-Teil ohne Signatur verwenden this.requestId = fullRequestId.split('.')[0] || null; } } /*** STANDARD-PROCESSORS ***/ /** * Processor für Request-ID */ static requestIdProcessor(record) { if (Logger.requestId) { record.extra.request_id = Logger.requestId; } return record; } /** * Processor für Timestamp-Formatierung */ static timestampProcessor(record) { record.formattedTimestamp = record.timestamp.toLocaleTimeString('de-DE'); return record; } /*** STANDARD-HANDLERS ***/ /** * Handler für Console-Ausgabe */ static consoleHandler(record) { const levelColors = { debug: 'color: gray', info: 'color: green', warn: 'color: orange', error: 'color: red', }; const color = levelColors[record.level] || 'color: black'; const requestIdStr = record.extra.request_id ? `[${record.extra.request_id}] ` : ''; const formattedMessage = `[${record.formattedTimestamp}] [${record.level.toUpperCase()}] ${requestIdStr}${record.message}`; // Farbige Ausgabe in der Konsole console[record.level]( `%c${formattedMessage}`, color, ...(record.context ? [record.context] : []) ); } /** * Handler für Server-Kommunikation */ static serverHandler(record) { fetch(Logger.config.apiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Request-ID': Logger.requestId || '' }, body: JSON.stringify({ level: record.level, message: record.message, context: record.context || {} }) }) .then(response => { // Request-ID aus dem Header extrahieren const requestId = response.headers.get('X-Request-ID'); if (requestId) { // Nur den ID-Teil ohne Signatur speichern const idPart = requestId.split('.')[0]; if (idPart) { Logger.requestId = idPart; } } return response.json(); }) .catch(() => { // Fehler beim Senden des Logs ignorieren (keine rekursive Fehlerbehandlung) }); } } // Standard-Initialisierung Logger.initialize();