Logging in TypeScript-Anwendungen: Best Practices in der Praxis
Logging ist ein grundlegender Bestandteil jeder Production-Anwendung. Es ermöglicht nicht nur die Nachverfolgung der Codeausführung und das Auffinden von Fehlern, sondern auch die Durchführung von Sicherheitsaudits, die Analyse der Leistung und das Debuggen komplexer Szenarien. In TypeScript wird der Ansatz zum Logging mit seinem leistungsstarken Typsystem noch flexibler und zuverlässiger. In diesem Artikel werden wir die Best Practices des Loggings anhand konkreter Beispiele erläutern.
1. Auswahl der Bibliothek und Konfiguration des Loggers
Das Erste, womit man beginnen sollte, ist die Auswahl einer geeigneten Bibliothek. Zu den beliebten Lösungen gehören winston und pino. Beide unterstützen TypeScript von Haus aus, haben aber unterschiedliche Prioritäten: winston – Flexibilität und umfangreiche Funktionalität, pino – maximale Leistung.
Betrachten wir ein Beispiel für die Konfiguration von winston mit Typisierung:
import winston from 'winston';
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'user-service' }, transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }), new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }), ],});
export default logger;Für pino ist die Konfiguration prägnanter, aber nicht weniger effektiv:
import pino from 'pino';
const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', }, },});
export default logger;Tipp: Lagern Sie die Konfiguration des Log-Levels immer in Umgebungsvariablen aus (LOG_LEVEL). In der Produktion wird normalerweise info oder warn verwendet, in der Entwicklung debug.
2. Strukturiertes Logging und Kontext
Einer der Hauptfehler ist das Loggen einfacher Zeichenketten. Verwenden Sie stattdessen strukturierte Objekte. Diese ermöglichen ein einfaches Filtern und Aggregieren von Logs in Systemen wie ELK, Grafana oder Datadog.
Beispiel für schlechtes Logging:
logger.info('User logged in successfully');Beispiel für gutes Logging mit Kontext:
logger.info({ message: 'User logged in', userId: user.id, email: user.email, loginMethod: 'oauth', ip: request.ip, duration: Date.now() - startTime,});Der Einfachheit halber können Sie einen typisierten Wrapper erstellen:
interface LogContext { requestId?: string; userId?: string; action?: string; [key: string]: unknown;}
function logWithContext(message: string, context: LogContext = {}): void { logger.info({ message, ...context });}
// VerwendunglogWithContext('Order processed', { orderId: '12345', userId: 'user_678', total: 99.99 });Dieser Ansatz stellt sicher, dass jeder Logeintrag die für die Analyse wichtige Informationen enthält.
3. Logging von Fehlern und Ausnahmen
Die Fehlerbehandlung ist der wichtigste Teil des Loggings. Loggen Sie immer den vollständigen Stack-Trace des Fehlers und den Kontext, in dem er aufgetreten ist. Verwenden Sie nicht console.error – es bietet keine Flexibilität für Rotation und Formatierung.
Das richtige Pattern mit TypeScript:
import logger from './logger';
async function fetchUserData(userId: string): Promise<UserData> { try { const response = await api.get(`/users/${userId}`); ret