278 lines
7.2 KiB
JavaScript
278 lines
7.2 KiB
JavaScript
/**
|
|
* 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();
|