18 Logging und Monitoring

Stellen Sie sich vor, Sie sind der Kapitän eines Schiffes auf einer langen Reise. Sie würden niemals ohne Kompass, Seekarten und Instrumente zur Überwachung des Wetters und der Schiffssysteme ablegen. Genauso braucht jede Anwendung “Instrumente” - Logging und Monitoring sind die Augen und Ohren Ihrer Anwendung. Sie erzählen Ihnen, was gerade passiert, warnen Sie vor Problemen und helfen Ihnen zu verstehen, wie Ihre Anwendung sich verhält.

Logging ist wie das Führen eines detaillierten Logbuchs - es dokumentiert alle wichtigen Ereignisse, Entscheidungen und Probleme, die auftreten. Monitoring ist wie ein Wachposten, der ständig überwacht und Alarm schlägt, wenn etwas nicht stimmt. Zusammen geben sie Ihnen die Kontrolle und das Verständnis, das Sie brauchen, um Ihre Anwendung erfolgreich zu betreiben.

18.1 Warum Logging und Monitoring unverzichtbar sind

Denken Sie an einen Arzt, der eine Diagnose stellt. Ohne Symptome, Vital-Parameter und Testergebnisse wäre das unmöglich. Genauso ist es mit Software - ohne Logs und Monitoring-Daten können wir keine fundierten Entscheidungen treffen. Wir wissen nicht, ob unsere Anwendung gesund ist, wo Probleme auftreten oder wie wir sie verbessern können.

Gutes Logging und Monitoring helfen uns bei verschiedenen kritischen Aufgaben:

Debugging und Troubleshooting: Wenn etwas schief geht, sind Logs unsere Zeitmaschine - sie zeigen uns genau, was passiert ist und in welcher Reihenfolge.

Performance-Optimierung: Monitoring-Daten zeigen uns Engpässe und Performance-Probleme, bevor sie zu echten Problemen werden.

Security: Logs können verdächtige Aktivitäten und Sicherheitsvorfälle aufdecken.

Business Intelligence: Logs enthalten wertvolle Informationen über Nutzerverhalten und Anwendungsnutzung.

Compliance: Viele Branchen erfordern detaillierte Audit-Logs für rechtliche Compliance.

// Ein praktisches Beispiel: Was würde ohne Logging passieren?
@Injectable()
export class PaymentService {
  async processPayment(amount: number, cardToken: string): Promise<PaymentResult> {
    // Ohne Logging: Ein "schwarzes Loch"
    // - War die Zahlung erfolgreich?
    // - Gab es Fehler bei der Kreditkarten-Validierung?
    // - Wie lange hat die Verarbeitung gedauert?
    // - Welche Zahlungen wurden versucht?
    // - Bei Problemen: Wo fangen wir mit der Suche an?
    
    return this.externalPaymentProvider.charge(amount, cardToken);
  }
}

// Mit richtigem Logging: Vollständige Transparenz
@Injectable()
export class PaymentServiceWithLogging {
  constructor(private logger: LoggerService) {}

  async processPayment(amount: number, cardToken: string, userId: string): Promise<PaymentResult> {
    const startTime = Date.now();
    const paymentId = this.generatePaymentId();
    
    this.logger.log({
      message: 'Payment processing started',
      paymentId,
      userId,
      amount,
      // Niemals vollständige Kreditkartendaten loggen!
      cardTokenPreview: cardToken.substring(0, 8) + '...',
      timestamp: new Date().toISOString(),
    });

    try {
      // Validierung loggen
      this.logger.debug({
        message: 'Validating payment parameters',
        paymentId,
        validations: {
          amountValid: amount > 0,
          cardTokenValid: cardToken.length > 0,
          userIdValid: !!userId,
        },
      });

      const result = await this.externalPaymentProvider.charge(amount, cardToken);
      
      const duration = Date.now() - startTime;
      
      if (result.success) {
        this.logger.log({
          message: 'Payment processed successfully',
          paymentId,
          userId,
          amount,
          transactionId: result.transactionId,
          duration,
          status: 'success',
        });
      } else {
        this.logger.warn({
          message: 'Payment processing failed',
          paymentId,
          userId,
          amount,
          errorCode: result.errorCode,
          errorMessage: result.errorMessage,
          duration,
          status: 'failed',
        });
      }

      return result;
    } catch (error) {
      const duration = Date.now() - startTime;
      
      this.logger.error({
        message: 'Payment processing error',
        paymentId,
        userId,
        amount,
        error: error.message,
        errorStack: error.stack,
        duration,
        status: 'error',
      });

      throw error;
    }
  }

  private generatePaymentId(): string {
    return `pay_${Date.now()}_${Math.random().toString(36).substring(2)}`;
  }
}

18.2 Built-in Logger - Der Standard-Kompass

NestJS kommt mit einem eingebauten Logger, der wie ein zuverlässiger Kompass alle Grundfunktionen bietet, die Sie für den Anfang brauchen. Er ist einfach zu verwenden, gut integriert und für viele Anwendungen völlig ausreichend.

18.2.1 Den Built-in Logger verstehen

Der NestJS Logger ist wie ein intelligenter Assistent, der automatisch wichtige Informationen über Ihre Anwendung sammelt. Er weiß, wann die Anwendung startet, welche Module geladen werden, wann HTTP-Requests ankommen und vieles mehr.

// Der einfachste Weg, den Built-in Logger zu verwenden
import { Logger } from '@nestjs/common';

@Injectable()
export class UserService {
  // Erstelle einen Logger für diese Klasse
  private readonly logger = new Logger(UserService.name);

  async createUser(userData: CreateUserDto): Promise<User> {
    // Informatives Log - für allgemeine Informationen
    this.logger.log(`Creating new user with email: ${userData.email}`);

    try {
      // Debug-Information - nur in Development sichtbar
      this.logger.debug(`User creation data: ${JSON.stringify(userData)}`);
      
      const user = await this.userRepository.save(userData);
      
      // Erfolg loggen
      this.logger.log(`User created successfully with ID: ${user.id}`);
      
      return user;
    } catch (error) {
      // Fehler loggen - sehr wichtig für Debugging
      this.logger.error(
        `Failed to create user with email ${userData.email}: ${error.message}`,
        error.stack, // Stack Trace für detailliertes Debugging
      );
      
      throw error;
    }
  }

  async findUserById(id: string): Promise<User> {
    this.logger.debug(`Looking up user with ID: ${id}`);
    
    const user = await this.userRepository.findOne({ where: { id } });
    
    if (!user) {
      // Warnung - etwas Unerwartetes, aber nicht kritisch
      this.logger.warn(`User not found with ID: ${id}`);
      throw new NotFoundException('User not found');
    }

    this.logger.debug(`User found: ${user.email}`);
    return user;
  }
}

18.2.2 Log Levels verstehen - Die Lautstärke-Regler

Log Levels sind wie die Lautstärke-Regler an einem Mischpult. Sie bestimmen, welche Nachrichten “laut genug” sind, um gehört zu werden. In der Entwicklung wollen wir alles hören (auch leise Debug-Nachrichten), aber in der Produktion interessieren uns nur die wichtigen Dinge.

// Die verschiedenen Log Levels im Detail
@Injectable()
export class OrderService {
  private readonly logger = new Logger(OrderService.name);

  async processOrder(orderData: CreateOrderDto): Promise<Order> {
    const orderId = this.generateOrderId();

    // VERBOSE (Level 0) - Sehr detaillierte Informationen
    // Normalerweise nur für intensive Debugging
    this.logger.verbose(`Starting order processing with data: ${JSON.stringify(orderData)}`);

    // DEBUG (Level 1) - Entwicklungs-Informationen
    // Hilfreich für Entwickler, aber nicht für Produktion
    this.logger.debug(`Generated order ID: ${orderId}`);
    this.logger.debug(`Order contains ${orderData.items.length} items`);

    try {
      // LOG (Level 2) - Allgemeine Informationen
      // Wichtige Geschäftsereignisse, die wir verfolgen wollen
      this.logger.log(`Processing order ${orderId} for user ${orderData.userId}`);

      // Geschäftslogik...
      const order = await this.createOrder(orderData);
      await this.validateInventory(order);
      await this.processPayment(order);

      // Erfolgreiche Verarbeitung
      this.logger.log(`Order ${orderId} processed successfully. Total: $${order.total}`);

      return order;
    } catch (error) {
      if (error instanceof PaymentFailedException) {
        // WARN (Level 3) - Probleme, die Aufmerksamkeit brauchen
        // Nicht kritisch für das System, aber wichtig für das Geschäft
        this.logger.warn(`Payment failed for order ${orderId}: ${error.message}`);
      } else if (error instanceof InventoryException) {
        this.logger.warn(`Inventory issue for order ${orderId}: ${error.message}`);
      } else {
        // ERROR (Level 4) - Schwerwiegende Probleme
        // Diese brauchen sofortige Aufmerksamkeit
        this.logger.error(
          `Critical error processing order ${orderId}: ${error.message}`,
          error.stack,
        );
      }

      throw error;
    }
  }

  private async processPayment(order: Order): Promise<void> {
    this.logger.debug(`Starting payment processing for order ${order.id}`);
    
    try {
      const paymentResult = await this.paymentService.charge(order.total, order.paymentMethod);
      
      if (paymentResult.success) {
        this.logger.log(`Payment successful for order ${order.id}, transaction: ${paymentResult.transactionId}`);
      } else {
        this.logger.warn(`Payment declined for order ${order.id}: ${paymentResult.declineReason}`);
        throw new PaymentFailedException(paymentResult.declineReason);
      }
    } catch (error) {
      this.logger.error(
        `Payment processing error for order ${order.id}: ${error.message}`,
        error.stack,
      );
      throw error;
    }
  }
}

18.2.3 Logger-Konfiguration für verschiedene Umgebungen

Wie ein Fernglas, das Sie für verschiedene Zwecke anders einstellen, sollte Ihr Logger je nach Umgebung unterschiedlich konfiguriert sein.

// logger.config.ts - Umgebungsspezifische Logger-Konfiguration
export const getLoggerConfig = () => {
  const environment = process.env.NODE_ENV || 'development';

  const baseConfig = {
    timestamp: true,
    context: true,
  };

  switch (environment) {
    case 'development':
      return {
        ...baseConfig,
        // In Development wollen wir alles sehen
        logLevels: ['error', 'warn', 'log', 'debug', 'verbose'],
        prettyPrint: true, // Schöne Formatierung für bessere Lesbarkeit
        colorize: true,    // Farbige Ausgabe
      };

    case 'test':
      return {
        ...baseConfig,
        // In Tests nur Fehler, um die Ausgabe sauber zu halten
        logLevels: ['error'],
        prettyPrint: false,
        colorize: false,
      };

    case 'staging':
      return {
        ...baseConfig,
        // Staging ähnlich wie Produktion, aber etwas mehr Details
        logLevels: ['error', 'warn', 'log'],
        prettyPrint: false,
        colorize: false,
        outputFormat: 'json', // Strukturierte Logs für Log-Management-Tools
      };

    case 'production':
      return {
        ...baseConfig,
        // Produktion: Nur wichtige Nachrichten
        logLevels: ['error', 'warn'],
        prettyPrint: false,
        colorize: false,
        outputFormat: 'json',
        // In Produktion könnten wir auch externes Logging verwenden
        useExternalLogger: true,
      };

    default:
      return baseConfig;
  }
};

// Anwendung der Konfiguration
// main.ts
async function bootstrap() {
  const loggerConfig = getLoggerConfig();
  
  const app = await NestFactory.create(AppModule, {
    logger: loggerConfig.logLevels,
  });

  // Weitere App-Konfiguration...
  
  await app.listen(3000);
}

18.3 Custom Logger Implementation - Der maßgeschneiderte Beobachter

Manchmal reicht der Standard-Logger nicht aus - wie ein maßgeschneiderter Anzug passt ein Custom Logger perfekt zu Ihren spezifischen Bedürfnissen. Sie können entscheiden, wie Logs formatiert werden, wo sie gespeichert werden und welche zusätzlichen Informationen sie enthalten.

18.3.1 Einen Custom Logger entwickeln

// custom-logger.service.ts - Ein vollständig anpassbarer Logger
import { LoggerService, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as winston from 'winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';

@Injectable()
export class CustomLoggerService implements LoggerService {
  private winston: winston.Logger;
  private context: string;

  constructor(private configService: ConfigService) {
    this.createWinstonLogger();
  }

  private createWinstonLogger(): void {
    // Erstelle verschiedene "Transports" - Ziele für die Logs
    const transports: winston.transport[] = [];

    // Console Transport - für Development
    if (this.configService.get('NODE_ENV') !== 'production') {
      transports.push(
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.colorize(),
            winston.format.printf(({ timestamp, level, message, context, ...meta }) => {
              return `${timestamp} [${context}] ${level}: ${message} ${
                Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''
              }`;
            }),
          ),
        }),
      );
    }

    // File Transport - für alle Umgebungen
    transports.push(
      new DailyRotateFile({
        filename: 'logs/application-%DATE%.log',
        datePattern: 'YYYY-MM-DD',
        zippedArchive: true,     // Komprimiere alte Logs
        maxSize: '20m',          // Maximale Dateigröße
        maxFiles: '14d',         // Behalte Logs für 14 Tage
        format: winston.format.combine(
          winston.format.timestamp(),
          winston.format.json(),  // JSON für bessere Parsbarkeit
        ),
      }),
    );

    // Error File Transport - separate Datei für Fehler
    transports.push(
      new DailyRotateFile({
        filename: 'logs/error-%DATE%.log',
        datePattern: 'YYYY-MM-DD',
        zippedArchive: true,
        maxSize: '20m',
        maxFiles: '30d',         // Fehler länger behalten
        level: 'error',          // Nur Fehler in diese Datei
        format: winston.format.combine(
          winston.format.timestamp(),
          winston.format.json(),
        ),
      }),
    );

    // HTTP Transport - für externe Log-Services (optional)
    if (this.configService.get('LOG_EXTERNAL_URL')) {
      transports.push(
        new winston.transports.Http({
          host: this.configService.get('LOG_EXTERNAL_HOST'),
          port: this.configService.get('LOG_EXTERNAL_PORT'),
          path: this.configService.get('LOG_EXTERNAL_PATH'),
        }),
      );
    }

    this.winston = winston.createLogger({
      level: this.configService.get('LOG_LEVEL', 'info'),
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json(),
      ),
      defaultMeta: {
        service: this.configService.get('APP_NAME', 'nestjs-app'),
        version: this.configService.get('APP_VERSION', '1.0.0'),
        environment: this.configService.get('NODE_ENV', 'development'),
      },
      transports,
    });
  }

  // Implementation der LoggerService Interface
  log(message: any, context?: string): void {
    this.winston.info(message, { context: context || this.context });
  }

  error(message: any, stack?: string, context?: string): void {
    this.winston.error(message, { 
      context: context || this.context,
      stack,
    });
  }

  warn(message: any, context?: string): void {
    this.winston.warn(message, { context: context || this.context });
  }

  debug(message: any, context?: string): void {
    this.winston.debug(message, { context: context || this.context });
  }

  verbose(message: any, context?: string): void {
    this.winston.verbose(message, { context: context || this.context });
  }

  // Erweiterte Methoden für strukturiertes Logging
  logWithMetadata(
    level: string,
    message: string,
    metadata: Record<string, any>,
    context?: string,
  ): void {
    this.winston.log(level, message, {
      context: context || this.context,
      ...metadata,
    });
  }

  // Spezielle Methoden für häufige Use Cases
  logUserAction(
    userId: string,
    action: string,
    resource?: string,
    metadata?: Record<string, any>,
  ): void {
    this.winston.info('User action performed', {
      context: 'USER_ACTION',
      userId,
      action,
      resource,
      timestamp: new Date().toISOString(),
      ...metadata,
    });
  }

  logPerformance(
    operation: string,
    duration: number,
    metadata?: Record<string, any>,
  ): void {
    this.winston.info('Performance metric', {
      context: 'PERFORMANCE',
      operation,
      duration,
      timestamp: new Date().toISOString(),
      ...metadata,
    });
  }

  logSecurityEvent(
    eventType: string,
    severity: 'low' | 'medium' | 'high' | 'critical',
    details: Record<string, any>,
  ): void {
    this.winston.warn('Security event detected', {
      context: 'SECURITY',
      eventType,
      severity,
      timestamp: new Date().toISOString(),
      ...details,
    });
  }

  // Context-Management
  setContext(context: string): void {
    this.context = context;
  }

  // Logger für spezifische Module erstellen
  createModuleLogger(moduleName: string): CustomLoggerService {
    const moduleLogger = new CustomLoggerService(this.configService);
    moduleLogger.setContext(moduleName);
    return moduleLogger;
  }
}

18.3.2 Structured Logging - Logs als Datenstruktur

Structured Logging ist wie der Unterschied zwischen einem unorganisierten Notizbuch und einer gut sortierten Datenbank. Statt nur Text zu loggen, strukturieren wir unsere Logs so, dass sie leicht durchsuchbar und analysierbar sind.

// structured-logging.service.ts - Strukturierte Logs für bessere Analyse
@Injectable()
export class StructuredLoggingService {
  constructor(private logger: CustomLoggerService) {}

  // E-Commerce-spezifische Log-Strukturen
  logOrderEvent(event: OrderLogEvent): void {
    this.logger.logWithMetadata(
      'info',
      `Order event: ${event.eventType}`,
      {
        orderId: event.orderId,
        userId: event.userId,
        eventType: event.eventType,
        orderValue: event.orderValue,
        currency: event.currency,
        paymentMethod: event.paymentMethod,
        shippingAddress: event.shippingAddress?.city, // Keine vollständige Adresse loggen
        itemCount: event.itemCount,
        timestamp: new Date().toISOString(),
        
        // Zusätzliche Metadaten für Analyse
        metadata: {
          sessionId: event.sessionId,
          userAgent: event.userAgent,
          ipAddress: this.hashIpAddress(event.ipAddress), // IP-Hash für Privacy
          referrer: event.referrer,
        },
      },
      'ORDER_EVENTS',
    );
  }

  // API-Request/Response Logging
  logApiRequest(request: ApiRequestLog): void {
    this.logger.logWithMetadata(
      'info',
      `API Request: ${request.method} ${request.path}`,
      {
        requestId: request.requestId,
        method: request.method,
        path: request.path,
        statusCode: request.statusCode,
        duration: request.duration,
        userAgent: request.userAgent,
        userId: request.userId,
        
        // Request-spezifische Daten
        queryParams: this.sanitizeParams(request.queryParams),
        bodySize: request.bodySize,
        responseSize: request.responseSize,
        
        // Performance-Metriken
        performanceMetrics: {
          dbQueryCount: request.dbQueryCount,
          dbQueryTime: request.dbQueryTime,
          cacheHits: request.cacheHits,
          cacheMisses: request.cacheMisses,
        },
        
        timestamp: new Date().toISOString(),
      },
      'API_REQUESTS',
    );
  }

  // Error Logging mit Kontext
  logError(error: Error, context: ErrorContext): void {
    this.logger.logWithMetadata(
      'error',
      error.message,
      {
        errorName: error.name,
        errorMessage: error.message,
        errorStack: error.stack,
        
        // Kontext-Informationen
        userId: context.userId,
        requestId: context.requestId,
        operation: context.operation,
        
        // System-Informationen
        nodeVersion: process.version,
        platform: process.platform,
        memoryUsage: process.memoryUsage(),
        
        // Business-Kontext
        businessContext: context.businessContext,
        
        timestamp: new Date().toISOString(),
      },
      'ERRORS',
    );
  }

  // Business Metrics Logging
  logBusinessMetric(metric: BusinessMetric): void {
    this.logger.logWithMetadata(
      'info',
      `Business metric: ${metric.name}`,
      {
        metricName: metric.name,
        metricValue: metric.value,
        metricUnit: metric.unit,
        metricType: metric.type, // 'counter', 'gauge', 'histogram'
        
        // Dimensionen für Gruppierung
        dimensions: metric.dimensions,
        
        // Zeitfenster-Informationen
        timeWindow: metric.timeWindow,
        aggregationType: metric.aggregationType,
        
        timestamp: new Date().toISOString(),
      },
      'BUSINESS_METRICS',
    );
  }

  // Security Event Logging
  logSecurityEvent(securityEvent: SecurityEvent): void {
    this.logger.logWithMetadata(
      'warn',
      `Security event: ${securityEvent.eventType}`,
      {
        eventType: securityEvent.eventType,
        severity: securityEvent.severity,
        userId: securityEvent.userId,
        ipAddress: this.hashIpAddress(securityEvent.ipAddress),
        userAgent: securityEvent.userAgent,
        
        // Event-spezifische Details
        attemptedAction: securityEvent.attemptedAction,
        resource: securityEvent.resource,
        outcome: securityEvent.outcome,
        
        // Zusätzliche Sicherheitsinformationen
        geolocation: securityEvent.geolocation,
        deviceFingerprint: securityEvent.deviceFingerprint,
        
        timestamp: new Date().toISOString(),
      },
      'SECURITY_EVENTS',
    );
  }

  // Hilfsmethoden für Datenschutz und Sicherheit
  private sanitizeParams(params: any): any {
    const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth'];
    const sanitized = { ...params };

    Object.keys(sanitized).forEach(key => {
      if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
        sanitized[key] = '[REDACTED]';
      }
    });

    return sanitized;
  }

  private hashIpAddress(ipAddress: string): string {
    // Hashe IP-Adressen für Privacy, aber behalte Konsistenz für Analyse
    const crypto = require('crypto');
    return crypto.createHash('sha256').update(ipAddress).digest('hex').substring(0, 16);
  }
}

// Typ-Definitionen für strukturierte Logs
interface OrderLogEvent {
  orderId: string;
  userId: string;
  eventType: 'created' | 'updated' | 'cancelled' | 'shipped' | 'delivered';
  orderValue: number;
  currency: string;
  paymentMethod: string;
  shippingAddress?: { city: string; country: string };
  itemCount: number;
  sessionId?: string;
  userAgent?: string;
  ipAddress?: string;
  referrer?: string;
}

interface ApiRequestLog {
  requestId: string;
  method: string;
  path: string;
  statusCode: number;
  duration: number;
  userAgent?: string;
  userId?: string;
  queryParams?: Record<string, any>;
  bodySize: number;
  responseSize: number;
  dbQueryCount: number;
  dbQueryTime: number;
  cacheHits: number;
  cacheMisses: number;
}

interface ErrorContext {
  userId?: string;
  requestId?: string;
  operation: string;
  businessContext?: Record<string, any>;
}

interface BusinessMetric {
  name: string;
  value: number;
  unit: string;
  type: 'counter' | 'gauge' | 'histogram';
  dimensions: Record<string, string>;
  timeWindow?: string;
  aggregationType?: string;
}

interface SecurityEvent {
  eventType: string;
  severity: 'low' | 'medium' | 'high' | 'critical';
  userId?: string;
  ipAddress: string;
  userAgent: string;
  attemptedAction: string;
  resource: string;
  outcome: 'success' | 'failure' | 'blocked';
  geolocation?: string;
  deviceFingerprint?: string;
}

18.4 Request/Response Logging - Die Verkehrsüberwachung

Request/Response Logging ist wie eine Verkehrsüberwachung für Ihre API. Es verfolgt jeden Request, der in Ihre Anwendung hineinkommt und jeden Response, der hinausgeht. Das hilft beim Debugging, Performance-Monitoring und der Analyse von Nutzungsmustern.

18.4.1 HTTP Request Interceptor für Logging

// request-logging.interceptor.ts - Überwacht jeden HTTP-Request
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { Request, Response } from 'express';
import { v4 as uuidv4 } from 'uuid';

@Injectable()
export class RequestLoggingInterceptor implements NestInterceptor {
  constructor(private structuredLogger: StructuredLoggingService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const httpContext = context.switchToHttp();
    const request = httpContext.getRequest<Request>();
    const response = httpContext.getResponse<Response>();
    
    // Generiere eine eindeutige Request-ID für Tracking
    const requestId = uuidv4();
    request['requestId'] = requestId;
    response.setHeader('X-Request-ID', requestId);

    const startTime = Date.now();
    const startCpuUsage = process.cpuUsage();
    const startMemory = process.memoryUsage();

    // Log Request Start
    this.logRequestStart(request, requestId);

    return next.handle().pipe(
      tap((responseData) => {
        // Log Success Response
        this.logRequestSuccess(
          request,
          response,
          responseData,
          requestId,
          startTime,
          startCpuUsage,
          startMemory,
        );
      }),
      catchError((error) => {
        // Log Error Response
        this.logRequestError(
          request,
          response,
          error,
          requestId,
          startTime,
          startCpuUsage,
          startMemory,
        );
        throw error; // Re-throw den Error
      }),
    );
  }

  private logRequestStart(request: Request, requestId: string): void {
    this.structuredLogger.logWithMetadata(
      'info',
      `Request started: ${request.method} ${request.path}`,
      {
        requestId,
        method: request.method,
        path: request.path,
        query: this.sanitizeQuery(request.query),
        headers: this.sanitizeHeaders(request.headers),
        userAgent: request.headers['user-agent'],
        ip: this.getClientIp(request),
        timestamp: new Date().toISOString(),
      },
      'REQUEST_START',
    );
  }

  private logRequestSuccess(
    request: Request,
    response: Response,
    responseData: any,
    requestId: string,
    startTime: number,
    startCpuUsage: NodeJS.CpuUsage,
    startMemory: NodeJS.MemoryUsage,
  ): void {
    const duration = Date.now() - startTime;
    const cpuUsage = process.cpuUsage(startCpuUsage);
    const endMemory = process.memoryUsage();

    this.structuredLogger.logApiRequest({
      requestId,
      method: request.method,
      path: request.path,
      statusCode: response.statusCode,
      duration,
      userAgent: request.headers['user-agent'],
      userId: request['user']?.id,
      queryParams: request.query,
      bodySize: this.getRequestBodySize(request),
      responseSize: this.calculateResponseSize(responseData),
      
      // Performance-Metriken (diese würden normalerweise von anderen Services gesammelt)
      dbQueryCount: request['dbQueryCount'] || 0,
      dbQueryTime: request['dbQueryTime'] || 0,
      cacheHits: request['cacheHits'] || 0,
      cacheMisses: request['cacheMisses'] || 0,
    });

    // Zusätzliches Performance-Log für langsame Requests
    if (duration > 1000) { // Langsamer als 1 Sekunde
      this.structuredLogger.logWithMetadata(
        'warn',
        `Slow request detected: ${request.method} ${request.path}`,
        {
          requestId,
          duration,
          cpuUsage: {
            user: cpuUsage.user / 1000, // Konvertiere zu Millisekunden
            system: cpuUsage.system / 1000,
          },
          memoryDelta: {
            heapUsed: endMemory.heapUsed - startMemory.heapUsed,
            heapTotal: endMemory.heapTotal - startMemory.heapTotal,
          },
          timestamp: new Date().toISOString(),
        },
        'SLOW_REQUEST',
      );
    }
  }

  private logRequestError(
    request: Request,
    response: Response,
    error: any,
    requestId: string,
    startTime: number,
    startCpuUsage: NodeJS.CpuUsage,
    startMemory: NodeJS.MemoryUsage,
  ): void {
    const duration = Date.now() - startTime;

    this.structuredLogger.logError(error, {
      userId: request['user']?.id,
      requestId,
      operation: `${request.method} ${request.path}`,
      businessContext: {
        httpMethod: request.method,
        path: request.path,
        query: request.query,
        statusCode: response.statusCode,
        duration,
        userAgent: request.headers['user-agent'],
        ip: this.getClientIp(request),
      },
    });
  }

  // Hilfsmethoden für Datenbereinigung und -berechnung
  private sanitizeQuery(query: any): any {
    const sensitiveKeys = ['password', 'token', 'key', 'secret'];
    const sanitized = { ...query };

    Object.keys(sanitized).forEach(key => {
      if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
        sanitized[key] = '[REDACTED]';
      }
    });

    return sanitized;
  }

  private sanitizeHeaders(headers: any): any {
    const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key'];
    const sanitized = { ...headers };

    Object.keys(sanitized).forEach(key => {
      if (sensitiveHeaders.includes(key.toLowerCase())) {
        sanitized[key] = '[REDACTED]';
      }
    });

    return sanitized;
  }

  private getClientIp(request: Request): string {
    return (
      request.headers['x-forwarded-for'] as string ||
      request.headers['x-real-ip'] as string ||
      request.connection.remoteAddress ||
      request.ip ||
      'unknown'
    );
  }

  private getRequestBodySize(request: Request): number {
    const contentLength = request.headers['content-length'];
    return contentLength ? parseInt(contentLength, 10) : 0;
  }

  private calculateResponseSize(responseData: any): number {
    if (!responseData) return 0;
    
    try {
      return JSON.stringify(responseData).length;
    } catch {
      return 0;
    }
  }
}

18.4.2 Database Query Logging - Die Datenbankdetektive

Database Query Logging hilft dabei zu verstehen, wie Ihre Anwendung mit der Datenbank interagiert. Es kann Performance-Probleme aufdecken, ineffiziente Queries identifizieren und bei der Optimierung helfen.

// database-logging.interceptor.ts - Überwacht Datenbankzugriffe
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class DatabaseLoggingInterceptor implements NestInterceptor {
  constructor(private structuredLogger: StructuredLoggingService) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    
    // Initialisiere DB-Metriken für diesen Request
    request['dbQueryCount'] = 0;
    request['dbQueryTime'] = 0;
    request['dbQueries'] = [];

    return next.handle().pipe(
      tap(() => {
        // Log DB-Statistiken am Ende des Requests
        this.logDatabaseStatistics(request);
      }),
    );
  }

  private logDatabaseStatistics(request: any): void {
    const { dbQueryCount, dbQueryTime, dbQueries } = request;

    if (dbQueryCount > 0) {
      this.structuredLogger.logWithMetadata(
        'info',
        `Database statistics for request`,
        {
          requestId: request.requestId,
          queryCount: dbQueryCount,
          totalQueryTime: dbQueryTime,
          averageQueryTime: dbQueryTime / dbQueryCount,
          slowQueries: dbQueries.filter(q => q.duration > 100), // Queries > 100ms
          timestamp: new Date().toISOString(),
        },
        'DATABASE_STATS',
      );
    }

    // Warne vor zu vielen Queries (mögliches N+1 Problem)
    if (dbQueryCount > 10) {
      this.structuredLogger.logWithMetadata(
        'warn',
        `Excessive database queries detected`,
        {
          requestId: request.requestId,
          queryCount: dbQueryCount,
          path: `${request.method} ${request.path}`,
          possibleN1Problem: true,
          timestamp: new Date().toISOString(),
        },
        'DATABASE_WARNING',
      );
    }
  }
}

// database-logger.service.ts - Service für detailliertes DB-Logging
@Injectable()
export class DatabaseLogger {
  constructor(private structuredLogger: StructuredLoggingService) {}

  logQuery(queryInfo: DatabaseQueryInfo): void {
    const {
      query,
      parameters,
      duration,
      rowCount,
      queryType,
      tableName,
      requestId,
    } = queryInfo;

    // Standard Query-Log
    this.structuredLogger.logWithMetadata(
      'debug',
      `Database query executed`,
      {
        requestId,
        queryType,
        tableName,
        duration,
        rowCount,
        query: this.sanitizeQuery(query),
        parameters: this.sanitizeParameters(parameters),
        timestamp: new Date().toISOString(),
      },
      'DATABASE_QUERY',
    );

    // Warne vor langsamen Queries
    if (duration > 1000) { // > 1 Sekunde
      this.structuredLogger.logWithMetadata(
        'warn',
        `Slow database query detected`,
        {
          requestId,
          queryType,
          tableName,
          duration,
          query: this.sanitizeQuery(query),
          timestamp: new Date().toISOString(),
        },
        'SLOW_QUERY',
      );
    }

    // Aktualisiere Request-Metriken (falls verfügbar)
    if (requestId && global['currentRequests']?.[requestId]) {
      const request = global['currentRequests'][requestId];
      request.dbQueryCount = (request.dbQueryCount || 0) + 1;
      request.dbQueryTime = (request.dbQueryTime || 0) + duration;
      request.dbQueries = request.dbQueries || [];
      request.dbQueries.push({
        query: this.sanitizeQuery(query),
        duration,
        tableName,
        queryType,
      });
    }
  }

  private sanitizeQuery(query: string): string {
    // Entferne oder verkürze sehr lange Queries
    if (query.length > 500) {
      return query.substring(0, 500) + '... [TRUNCATED]';
    }
    
    // Entferne potentiell sensible Daten aus WHERE-Klauseln
    return query.replace(
      /(\bWHERE\s+\w+\s*=\s*)(['"][^'"]*['"])/gi,
      '$1[REDACTED]'
    );
  }

  private sanitizeParameters(parameters: any[]): any[] {
    if (!parameters) return [];
    
    return parameters.map(param => {
      if (typeof param === 'string' && param.length > 100) {
        return '[LONG_STRING]';
      }
      return param;
    });
  }
}

interface DatabaseQueryInfo {
  query: string;
  parameters?: any[];
  duration: number;
  rowCount?: number;
  queryType: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE';
  tableName?: string;
  requestId?: string;
}

18.5 Performance Monitoring - Der Gesundheitscheck

Performance Monitoring ist wie regelmäßige Gesundheitschecks für Ihre Anwendung. Es überwacht wichtige Vital-Parameter und warnt Sie, bevor kleine Probleme zu großen werden.

18.5.1 Application Performance Monitoring (APM)

// performance-monitor.service.ts - Überwacht Application Performance
@Injectable()
export class PerformanceMonitorService {
  private metrics = new Map<string, PerformanceMetric[]>();
  private readonly maxMetricsPerType = 1000; // Verhindere Memory Leaks

  constructor(private structuredLogger: StructuredLoggingService) {
    // Starte regelmäßige System-Metriken-Sammlung
    this.startSystemMetricsCollection();
  }

  // Messe die Ausführungszeit einer Operation
  async measureOperation<T>(
    operationName: string,
    operation: () => Promise<T>,
    context?: Record<string, any>,
  ): Promise<T> {
    const startTime = Date.now();
    const startCpuUsage = process.cpuUsage();
    const startMemory = process.memoryUsage();

    try {
      const result = await operation();
      
      // Messe Erfolgs-Metriken
      this.recordOperationMetrics(
        operationName,
        Date.now() - startTime,
        true,
        startCpuUsage,
        startMemory,
        context,
      );

      return result;
    } catch (error) {
      // Messe Fehler-Metriken
      this.recordOperationMetrics(
        operationName,
        Date.now() - startTime,
        false,
        startCpuUsage,
        startMemory,
        { ...context, error: error.message },
      );

      throw error;
    }
  }

  private recordOperationMetrics(
    operationName: string,
    duration: number,
    success: boolean,
    startCpuUsage: NodeJS.CpuUsage,
    startMemory: NodeJS.MemoryUsage,
    context?: Record<string, any>,
  ): void {
    const endCpuUsage = process.cpuUsage(startCpuUsage);
    const endMemory = process.memoryUsage();

    const metric: PerformanceMetric = {
      operationName,
      duration,
      success,
      timestamp: Date.now(),
      cpuUsage: {
        user: endCpuUsage.user / 1000, // Konvertiere zu Millisekunden
        system: endCpuUsage.system / 1000,
      },
      memoryDelta: {
        heapUsed: endMemory.heapUsed - startMemory.heapUsed,
        heapTotal: endMemory.heapTotal - startMemory.heapTotal,
        external: endMemory.external - startMemory.external,
      },
      context,
    };

    // Speichere Metrik
    this.addMetric(operationName, metric);

    // Log die Metrik
    this.structuredLogger.logPerformance(
      operationName,
      duration,
      {
        success,
        cpuUsage: metric.cpuUsage,
        memoryDelta: metric.memoryDelta,
        context,
      },
    );

    // Warne vor Performance-Problemen
    this.checkPerformanceThresholds(metric);
  }

  private addMetric(operationName: string, metric: PerformanceMetric): void {
    if (!this.metrics.has(operationName)) {
      this.metrics.set(operationName, []);
    }

    const operationMetrics = this.metrics.get(operationName);
    operationMetrics.push(metric);

    // Begrenze die Anzahl gespeicherter Metriken
    if (operationMetrics.length > this.maxMetricsPerType) {
      operationMetrics.shift(); // Entferne die älteste Metrik
    }
  }

  private checkPerformanceThresholds(metric: PerformanceMetric): void {
    const { operationName, duration, memoryDelta } = metric;

    // Warne vor langsamen Operationen
    if (duration > 5000) { // > 5 Sekunden
      this.structuredLogger.logWithMetadata(
        'warn',
        `Very slow operation detected: ${operationName}`,
        {
          operationName,
          duration,
          threshold: 5000,
          timestamp: new Date().toISOString(),
        },
        'PERFORMANCE_WARNING',
      );
    }

    // Warne vor hohem Speicherverbrauch
    if (memoryDelta.heapUsed > 50 * 1024 * 1024) { // > 50MB
      this.structuredLogger.logWithMetadata(
        'warn',
        `High memory usage detected: ${operationName}`,
        {
          operationName,
          memoryUsage: memoryDelta.heapUsed,
          threshold: 50 * 1024 * 1024,
          timestamp: new Date().toISOString(),
        },
        'MEMORY_WARNING',
      );
    }
  }

  // Berechne Statistiken für eine Operation
  getOperationStatistics(operationName: string): OperationStatistics {
    const metrics = this.metrics.get(operationName) || [];
    
    if (metrics.length === 0) {
      return null;
    }

    const durations = metrics.map(m => m.duration);
    const successCount = metrics.filter(m => m.success).length;

    return {
      operationName,
      totalCalls: metrics.length,
      successRate: (successCount / metrics.length) * 100,
      averageDuration: durations.reduce((a, b) => a + b, 0) / durations.length,
      minDuration: Math.min(...durations),
      maxDuration: Math.max(...durations),
      p95Duration: this.calculatePercentile(durations, 95),
      p99Duration: this.calculatePercentile(durations, 99),
      lastCall: new Date(Math.max(...metrics.map(m => m.timestamp))),
    };
  }

  // Sammle System-weite Metriken
  private startSystemMetricsCollection(): void {
    setInterval(() => {
      this.collectSystemMetrics();
    }, 30000); // Alle 30 Sekunden
  }

  private collectSystemMetrics(): void {
    const memoryUsage = process.memoryUsage();
    const cpuUsage = process.cpuUsage();

    this.structuredLogger.logWithMetadata(
      'info',
      'System metrics collected',
      {
        memory: {
          heapUsed: memoryUsage.heapUsed,
          heapTotal: memoryUsage.heapTotal,
          external: memoryUsage.external,
          rss: memoryUsage.rss,
        },
        cpu: {
          user: cpuUsage.user / 1000,
          system: cpuUsage.system / 1000,
        },
        uptime: process.uptime(),
        activeHandles: (process as any)._getActiveHandles?.()?.length || 0,
        activeRequests: (process as any)._getActiveRequests?.()?.length || 0,
        timestamp: new Date().toISOString(),
      },
      'SYSTEM_METRICS',
    );

    // Warne vor kritischen System-Zuständen
    this.checkSystemHealth(memoryUsage, cpuUsage);
  }

  private checkSystemHealth(
    memoryUsage: NodeJS.MemoryUsage,
    cpuUsage: NodeJS.CpuUsage,
  ): void {
    // Memory-Warnung (> 80% des verfügbaren Heaps)
    const heapUsagePercent = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100;
    if (heapUsagePercent > 80) {
      this.structuredLogger.logWithMetadata(
        'warn',
        'High memory usage detected',
        {
          heapUsagePercent,
          heapUsed: memoryUsage.heapUsed,
          heapTotal: memoryUsage.heapTotal,
          timestamp: new Date().toISOString(),
        },
        'SYSTEM_WARNING',
      );
    }

    // CPU-Warnung ist schwieriger zu messen, da cpuUsage kumulativ ist
    // In einer echten Anwendung würden Sie hier ein CPU-Monitoring-Tool verwenden
  }

  private calculatePercentile(values: number[], percentile: number): number {
    const sorted = values.slice().sort((a, b) => a - b);
    const index = Math.ceil((percentile / 100) * sorted.length) - 1;
    return sorted[index];
  }

  // API für externe Performance-Tests
  getAllStatistics(): Record<string, OperationStatistics> {
    const allStats: Record<string, OperationStatistics> = {};
    
    for (const operationName of this.metrics.keys()) {
      allStats[operationName] = this.getOperationStatistics(operationName);
    }

    return allStats;
  }

  // Cleanup für Memory-Management
  clearOldMetrics(olderThanMs: number = 24 * 60 * 60 * 1000): void {
    const cutoffTime = Date.now() - olderThanMs;
    
    for (const [operationName, metrics] of this.metrics.entries()) {
      const filteredMetrics = metrics.filter(m => m.timestamp > cutoffTime);
      this.metrics.set(operationName, filteredMetrics);
    }
  }
}

// Typ-Definitionen für Performance-Monitoring
interface PerformanceMetric {
  operationName: string;
  duration: number;
  success: boolean;
  timestamp: number;
  cpuUsage: {
    user: number;
    system: number;
  };
  memoryDelta: {
    heapUsed: number;
    heapTotal: number;
    external: number;
  };
  context?: Record<string, any>;
}

interface OperationStatistics {
  operationName: string;
  totalCalls: number;
  successRate: number;
  averageDuration: number;
  minDuration: number;
  maxDuration: number;
  p95Duration: number;
  p99Duration: number;
  lastCall: Date;
}

18.6 Health Checks - Der Puls der Anwendung

Health Checks sind wie der Puls Ihrer Anwendung - sie geben schnell Auskunft darüber, ob alles in Ordnung ist oder ob Aufmerksamkeit benötigt wird. Sie sind besonders wichtig in Container-Umgebungen und Load Balancern, die wissen müssen, ob sie Traffic an eine Instanz weiterleiten können.

18.6.1 Comprehensive Health Check System

// health-check.service.ts - Umfassende Gesundheitsüberwachung
import { Injectable } from '@nestjs/common';
import { 
  HealthCheckService,
  HealthCheck,
  HealthCheckResult,
  HealthIndicator,
  HealthIndicatorResult,
  HealthCheckError,
} from '@nestjs/terminus';

@Injectable()
export class ApplicationHealthService {
  constructor(
    private health: HealthCheckService,
    private databaseHealthIndicator: DatabaseHealthIndicator,
    private redisHealthIndicator: RedisHealthIndicator,
    private externalApiHealthIndicator: ExternalApiHealthIndicator,
    private diskHealthIndicator: DiskHealthIndicator,
    private memoryHealthIndicator: MemoryHealthIndicator,
    private structuredLogger: StructuredLoggingService,
  ) {}

  @HealthCheck()
  async checkLiveness(): Promise<HealthCheckResult> {
    // Liveness Check - ist die Anwendung am Leben?
    // Sollte nur die allergrundlegendsten Checks enthalten
    return this.health.check([
      () => this.basicApplicationCheck(),
    ]);
  }

  @HealthCheck()
  async checkReadiness(): Promise<HealthCheckResult> {
    // Readiness Check - ist die Anwendung bereit, Traffic zu empfangen?
    // Umfasst alle kritischen Dependencies
    return this.health.check([
      () => this.basicApplicationCheck(),
      () => this.databaseHealthIndicator.isHealthy('database'),
      () => this.redisHealthIndicator.isHealthy('redis'),
      () => this.memoryHealthIndicator.checkHeap('memory'),
      () => this.diskHealthIndicator.checkStorage('disk'),
    ]);
  }

  @HealthCheck()
  async checkComplete(): Promise<HealthCheckResult> {
    // Vollständiger Health Check - umfasst auch nicht-kritische Services
    const result = await this.health.check([
      () => this.basicApplicationCheck(),
      () => this.databaseHealthIndicator.isHealthy('database'),
      () => this.redisHealthIndicator.isHealthy('redis'),
      () => this.memoryHealthIndicator.checkHeap('memory'),
      () => this.diskHealthIndicator.checkStorage('disk'),
      () => this.externalApiHealthIndicator.checkPaymentService('payment-api'),
      () => this.externalApiHealthIndicator.checkEmailService('email-api'),
    ]);

    // Log den Health Check
    this.logHealthCheckResult(result);

    return result;
  }

  private async basicApplicationCheck(): Promise<HealthIndicatorResult> {
    const isHealthy = true; // Basis-Check - App läuft
    const result = {
      'basic-check': {
        status: isHealthy ? 'up' : 'down',
        uptime: process.uptime(),
        timestamp: new Date().toISOString(),
        version: process.env.APP_VERSION || '1.0.0',
        nodeVersion: process.version,
        platform: process.platform,
      },
    };

    if (isHealthy) {
      return result;
    } else {
      throw new HealthCheckError('Basic application check failed', result);
    }
  }

  private logHealthCheckResult(result: HealthCheckResult): void {
    const status = result.status;
    const checks = result.info || result.error;

    this.structuredLogger.logWithMetadata(
      status === 'ok' ? 'info' : 'error',
      `Health check completed with status: ${status}`,
      {
        status,
        checks,
        timestamp: new Date().toISOString(),
      },
      'HEALTH_CHECK',
    );

    // Warne vor fehlgeschlagenen Checks
    if (status !== 'ok' && result.error) {
      Object.entries(result.error).forEach(([checkName, checkResult]) => {
        this.structuredLogger.logWithMetadata(
          'warn',
          `Health check failed: ${checkName}`,
          {
            checkName,
            checkResult,
            timestamp: new Date().toISOString(),
          },
          'HEALTH_CHECK_FAILURE',
        );
      });
    }
  }
}

// Custom Health Indicators für verschiedene Services
@Injectable()
export class DatabaseHealthIndicator extends HealthIndicator {
  constructor(
    @InjectDataSource() private dataSource: DataSource,
    private structuredLogger: StructuredLoggingService,
  ) {
    super();
  }

  async isHealthy(key: string): Promise<HealthIndicatorResult> {
    const startTime = Date.now();
    
    try {
      // Führe eine einfache Query aus
      await this.dataSource.query('SELECT 1');
      
      const responseTime = Date.now() - startTime;
      
      const result = this.getStatus(key, true, {
        responseTime,
        connection: 'active',
        timestamp: new Date().toISOString(),
      });

      // Warne vor langsamen DB-Responses
      if (responseTime > 1000) {
        this.structuredLogger.logWithMetadata(
          'warn',
          'Database health check slow',
          { responseTime, threshold: 1000 },
          'DATABASE_HEALTH',
        );
      }

      return result;
    } catch (error) {
      const responseTime = Date.now() - startTime;
      
      this.structuredLogger.logWithMetadata(
        'error',
        'Database health check failed',
        {
          error: error.message,
          responseTime,
          timestamp: new Date().toISOString(),
        },
        'DATABASE_HEALTH',
      );

      throw new HealthCheckError(
        'Database check failed',
        this.getStatus(key, false, {
          error: error.message,
          responseTime,
          timestamp: new Date().toISOString(),
        }),
      );
    }
  }
}

@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
  constructor(
    @Inject('REDIS_CLIENT') private redisClient: any,
    private structuredLogger: StructuredLoggingService,
  ) {
    super();
  }

  async isHealthy(key: string): Promise<HealthIndicatorResult> {
    const startTime = Date.now();
    
    try {
      // Ping Redis
      await this.redisClient.ping();
      
      const responseTime = Date.now() - startTime;
      
      return this.getStatus(key, true, {
        responseTime,
        connection: 'active',
        timestamp: new Date().toISOString(),
      });
    } catch (error) {
      const responseTime = Date.now() - startTime;
      
      this.structuredLogger.logWithMetadata(
        'error',
        'Redis health check failed',
        {
          error: error.message,
          responseTime,
          timestamp: new Date().toISOString(),
        },
        'REDIS_HEALTH',
      );

      throw new HealthCheckError(
        'Redis check failed',
        this.getStatus(key, false, {
          error: error.message,
          responseTime,
          timestamp: new Date().toISOString(),
        }),
      );
    }
  }
}

@Injectable()
export class MemoryHealthIndicator extends HealthIndicator {
  constructor(private structuredLogger: StructuredLoggingService) {
    super();
  }

  checkHeap(key: string): HealthIndicatorResult {
    const memoryUsage = process.memoryUsage();
    const heapUsed = memoryUsage.heapUsed;
    const heapTotal = memoryUsage.heapTotal;
    const heapUsagePercent = (heapUsed / heapTotal) * 100;
    
    // Kritischer Speicherverbrauch bei > 90%
    const isHealthy = heapUsagePercent < 90;
    
    const result = this.getStatus(key, isHealthy, {
      heapUsed,
      heapTotal,
      heapUsagePercent: Math.round(heapUsagePercent * 100) / 100,
      rss: memoryUsage.rss,
      external: memoryUsage.external,
      timestamp: new Date().toISOString(),
    });

    if (!isHealthy) {
      this.structuredLogger.logWithMetadata(
        'error',
        'Memory health check failed - high heap usage',
        {
          heapUsagePercent,
          heapUsed,
          heapTotal,
          threshold: 90,
          timestamp: new Date().toISOString(),
        },
        'MEMORY_HEALTH',
      );

      throw new HealthCheckError('Memory usage too high', result);
    }

    // Warne bei > 80% Speicherverbrauch
    if (heapUsagePercent > 80) {
      this.structuredLogger.logWithMetadata(
        'warn',
        'Memory health check warning - high heap usage',
        {
          heapUsagePercent,
          heapUsed,
          heapTotal,
          threshold: 80,
          timestamp: new Date().toISOString(),
        },
        'MEMORY_HEALTH',
      );
    }

    return result;
  }
}

@Injectable()
export class ExternalApiHealthIndicator extends HealthIndicator {
  constructor(
    private httpService: HttpService,
    private structuredLogger: StructuredLoggingService,
  ) {
    super();
  }

  async checkPaymentService(key: string): Promise<HealthIndicatorResult> {
    return this.checkExternalService(
      key,
      'https://api.payment-provider.com/health',
      'Payment Service',
      5000, // 5 Sekunden Timeout
    );
  }

  async checkEmailService(key: string): Promise<HealthIndicatorResult> {
    return this.checkExternalService(
      key,
      'https://api.sendgrid.com/v3/user/profile',
      'Email Service',
      3000, // 3 Sekunden Timeout
    );
  }

  private async checkExternalService(
    key: string,
    url: string,
    serviceName: string,
    timeoutMs: number,
  ): Promise<HealthIndicatorResult> {
    const startTime = Date.now();
    
    try {
      const response = await this.httpService.get(url, {
        timeout: timeoutMs,
        headers: {
          'User-Agent': 'HealthCheck/1.0',
        },
      }).toPromise();

      const responseTime = Date.now() - startTime;
      
      const isHealthy = response.status >= 200 && response.status < 300;
      
      if (isHealthy) {
        return this.getStatus(key, true, {
          service: serviceName,
          statusCode: response.status,
          responseTime,
          url,
          timestamp: new Date().toISOString(),
        });
      } else {
        throw new Error(`HTTP ${response.status}`);
      }
    } catch (error) {
      const responseTime = Date.now() - startTime;
      
      this.structuredLogger.logWithMetadata(
        'warn',
        `External service health check failed: ${serviceName}`,
        {
          service: serviceName,
          url,
          error: error.message,
          responseTime,
          timestamp: new Date().toISOString(),
        },
        'EXTERNAL_SERVICE_HEALTH',
      );

      // Externe Services sind oft nicht kritisch für die Anwendung
      // Daher loggen wir als Warning statt Error
      return this.getStatus(key, false, {
        service: serviceName,
        error: error.message,
        responseTime,
        url,
        timestamp: new Date().toISOString(),
      });
    }
  }
}

Logging und Monitoring sind wie das Nervensystem Ihrer Anwendung - sie geben Ihnen die Augen und Ohren, die Sie brauchen, um zu verstehen, was in Ihrer Anwendung passiert. Eine gut durchdachte Logging- und Monitoring-Strategie macht den Unterschied zwischen einer Anwendung, die Sie blind betreiben, und einer, die Sie vollständig unter Kontrolle haben.

Denken Sie daran: Gutes Logging und Monitoring sind eine Investition in die Zukunft. Die Zeit, die Sie heute in die Einrichtung verwenden, spart Ihnen später unzählige Stunden beim Debugging und hilft Ihnen, Probleme zu lösen, bevor Ihre Benutzer sie bemerken.