Files
michaelschiemer/resources/js/core/logger-next.js

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();