TL;DR: Logging ist in Angular weit mehr als console.log(). Mit einem eigenen TypeScript-Decorator loggst du automatisch Methodenaufrufe, ein LoggerService bringt Struktur rein, und mit Sentry oder LogRocket schickst du Logs direkt in die Cloud. Hier erfährst du alles, was du für professionelles Logging brauchst.

In der Softwareentwicklung spielt das Logging eine entscheidende Rolle, um die Ausführung von Programmen zu überwachen und Fehler schnell zu identifizieren. Insbesondere in großen Anwendungen ist ein effektives Logging unverzichtbar. Dieser Artikel stellt ein Logging-Decorator für Angular vor, der es ermöglicht, Methodenaufrufe und deren Rückgabewerte komfortabel zu protokollieren.

🔧 Der Code im Detail

Der bereitgestellte Code nutzt TypeScript und die Angular-Entwicklungsumgebung, um ein flexibles Logging-System zu erstellen. Er besteht aus mehreren Teilen, die jeweils spezifische Aufgaben erfüllen:

Warum du nur noch TypeScript nutzen solltest
Angular: Framework für dynamische Single-Page-Applications

Import der Entwicklungsmodus-Funktion

import { isDevMode } from '@angular/core';

Diese Zeile importiert die isDevMode-Funktion aus dem Angular-Kernmodul, um festzustellen, ob die Anwendung im Entwicklungsmodus läuft.

Konstanten für das Log-Format

import { isDevMode } from '@angular/core';

export const logFormat = 'font-size:11px;display:block;border-left:5px solid {{color}};padding-left:5px;';
const startColor = 'green';
const endColor = 'red';

export function Log(whitelist?: Array<string>, blacklist?: Array<string>, logType: logType = { type: 'both' }): ClassDecorator {
    return function (cls: any) {
        const properties = whitelist || Object.getOwnPropertyNames(cls.prototype);
        properties.forEach(propName => {
            if (shouldLog(propName, whitelist, blacklist)) {
                log(cls, propName, logType);
            }
        });
    };
}

function shouldLog(propName: string, whitelist?: Array<string>, blacklist?: Array<string>): boolean {
    const isBlacklisted = blacklist?.some(f => f.toLowerCase() === propName.toLowerCase());
    return !isBlacklisted && (!whitelist || whitelist.includes(propName));
}

function log(cls: any, fnName: string, logType: logType) {
    if (!isDevMode()) return;

    const original = cls.prototype[fnName];
    if (typeof original === 'function') {
        cls.prototype[fnName] = function (...args) {
            if (logType.type === 'in' || logType.type === 'both') {
                console.log(`%c${cls.name}.${fnName}():`, logFormat.replace('{{color}}', startColor), ...args);
            }

            const ret = original.apply(this, args);

            if (logType.type === 'out' || logType.type === 'both') {
                console.log(`%c${cls.name}.${fnName}()`, logFormat.replace('{{color}}', endColor), ret);
            }

            return ret;
        };
    }
}

export interface logLine {
    logging: boolean;
}

export interface logType {
    type: 'in' | 'out' | 'both';
}

🏗️ Den Decorator in einer Komponente verwenden

Jetzt wird es spannend: Du kannst den @Log()-Decorator direkt auf eine Angular-Komponente anwenden. Damit werden automatisch alle Methodenaufrufe geloggt – ganz ohne manuelles console.log() in jeder Methode.

import { Component, OnInit } from '@angular/core';
import { Log } from './log.decorator';

@Log()
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
})
export class DashboardComponent implements OnInit {

  ngOnInit(): void {
    this.loadData();
  }

  loadData(): void {
    // Deine Logik hier...
    console.log('Daten werden geladen');
  }

  calculateSum(a: number, b: number): number {
    return a + b;
  }
}

Sobald du die Komponente im Entwicklungsmodus aufrufst, siehst du in der Konsole automatisch:

  • Grüner Balken: Methodenaufruf mit übergebenen Argumenten
  • Roter Balken: Rückgabewert der Methode

Du kannst den Decorator auch mit einer Whitelist oder Blacklist nutzen, um nur bestimmte Methoden zu loggen:

// Nur loadData() loggen
@Log(['loadData'])

// Alles außer ngOnInit() loggen
@Log(undefined, ['ngOnInit'])

// Nur Eingabeparameter loggen, keine Rückgabewerte
@Log(undefined, undefined, { type: 'in' })

📋 Überblick über die wichtigsten Console-Methoden

Bevor wir tiefer einsteigen, hier ein Überblick über die wichtigsten Console-Methoden, die dir im Alltag helfen:

console.log(), console.warn(), console.error()

Die Klassiker – für verschiedene Log-Level:

console.log('📝 Info: Benutzer eingeloggt');
console.warn('⚠️ Warnung: API-Antwort langsam');
console.error('❌ Fehler: Netzwerk nicht erreichbar');

console.table()

Perfekt für Arrays und Objekte – stellt Daten als Tabelle dar:

const users = [
  { name: 'Anna', role: 'Admin' },
  { name: 'Max', role: 'User' },
  { name: 'Lisa', role: 'Editor' },
];
console.table(users);

console.group() und console.groupEnd()

Gruppiere zusammengehörige Logs für bessere Übersicht:

console.group('🔄 HTTP Request');
console.log('URL: /api/users');
console.log('Method: GET');
console.log('Status: 200');
console.groupEnd();

console.time() und console.timeEnd()

Miss die Ausführungszeit von Code-Blöcken:

console.time('Datenladen');
await this.httpClient.get('/api/data').toPromise();
console.timeEnd('Datenladen');
// Ausgabe: Datenladen: 142.5ms

🏛️ Strukturiertes Logging mit einem LoggerService

Für größere Projekte reicht console.log() allein nicht aus. Ein zentraler LoggerService gibt dir Kontrolle über Log-Level, Formatierung und Ausgabekanäle:

import { Injectable, isDevMode } from '@angular/core';

export enum LogLevel {
  DEBUG = 0,
  INFO = 1,
  WARN = 2,
  ERROR = 3,
  OFF = 4,
}

@Injectable({ providedIn: 'root' })
export class LoggerService {
  private level: LogLevel = isDevMode() ? LogLevel.DEBUG : LogLevel.WARN;

  setLevel(level: LogLevel): void {
    this.level = level;
  }

  debug(message: string, ...data: any[]): void {
    if (this.level <= LogLevel.DEBUG) {
      console.log(`[DEBUG] ${this.timestamp()} ${message}`, ...data);
    }
  }

  info(message: string, ...data: any[]): void {
    if (this.level <= LogLevel.INFO) {
      console.log(`[INFO] ${this.timestamp()} ${message}`, ...data);
    }
  }

  warn(message: string, ...data: any[]): void {
    if (this.level <= LogLevel.WARN) {
      console.warn(`[WARN] ${this.timestamp()} ${message}`, ...data);
    }
  }

  error(message: string, ...data: any[]): void {
    if (this.level <= LogLevel.ERROR) {
      console.error(`[ERROR] ${this.timestamp()} ${message}`, ...data);
    }
  }

  private timestamp(): string {
    return new Date().toISOString();
  }
}

Den Service nutzt du dann in deinen Komponenten per Dependency Injection:

import { Component, OnInit } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({
  selector: 'app-orders',
  templateUrl: './orders.component.html',
})
export class OrdersComponent implements OnInit {

  constructor(private logger: LoggerService) {}

  ngOnInit(): void {
    this.logger.info('OrdersComponent initialisiert');
    this.loadOrders();
  }

  loadOrders(): void {
    this.logger.debug('Lade Bestellungen...');
    try {
      // HTTP-Call...
      this.logger.info('Bestellungen erfolgreich geladen', { count: 42 });
    } catch (error) {
      this.logger.error('Fehler beim Laden der Bestellungen', error);
    }
  }
}

Der große Vorteil: Im Production-Build werden automatisch nur Warnungen und Fehler geloggt, während im Dev-Modus alle Details sichtbar sind.

🌐 Integration mit externen Logging-Services

Für produktionsreife Anwendungen willst du Logs nicht nur in der Browser-Konsole sehen. Externe Services wie Sentry und LogRocket helfen dir, Fehler in Echtzeit zu tracken.

Sentry-Integration

Sentry fängt Fehler automatisch ab und gruppiert sie intelligent. Die Integration in Angular ist einfach:

import * as Sentry from '@sentry/angular';

// In deinem app.config.ts oder app.module.ts
Sentry.init({
  dsn: 'https://[email protected]/projekt-id',
  integrations: [
    Sentry.browserTracingIntegration(),
  ],
  tracesSampleRate: 1.0,
  environment: isDevMode() ? 'development' : 'production',
});

Du kannst den LoggerService erweitern, um Fehler automatisch an Sentry zu senden:

error(message: string, ...data: any[]): void {
  if (this.level <= LogLevel.ERROR) {
    console.error(`[ERROR] ${this.timestamp()} ${message}`, ...data);
    Sentry.captureException(data[0] instanceof Error ? data[0] : new Error(message));
  }
}

LogRocket-Integration

LogRocket zeichnet Benutzersitzungen auf und verknüpft Logs mit dem, was der User tatsächlich gesehen hat:

import LogRocket from 'logrocket';

LogRocket.init('dein-app-id/projekt');

// Im LoggerService
error(message: string, ...data: any[]): void {
  if (this.level <= LogLevel.ERROR) {
    console.error(`[ERROR] ${this.timestamp()} ${message}`, ...data);
    LogRocket.captureException(data[0] instanceof Error ? data[0] : new Error(message));
  }
}

✅ Best Practices für Logging in Angular

Hier sind die wichtigsten Regeln, die du beim Logging beachten solltest:

1. Nutze Log-Level konsequent

  • DEBUG: Detaillierte Informationen für die Entwicklung
  • INFO: Wichtige Geschäftsereignisse (Login, Bestellung, etc.)
  • WARN: Unerwartetes Verhalten, das aber kein Fehler ist
  • ERROR: Echte Fehler, die Aufmerksamkeit erfordern

2. Unterscheide zwischen Production und Development

Im Development-Modus willst du so viele Informationen wie möglich. In Production solltest du nur das Nötigste loggen:

// Im LoggerService bereits eingebaut:
private level: LogLevel = isDevMode() ? LogLevel.DEBUG : LogLevel.WARN;

Damit stellst du sicher, dass keine sensiblen Daten in der Produktionskonsole landen und die Performance nicht leidet.

3. Achte auf Performance

  • Vermeide exzessives Logging in Schleifen oder häufig aufgerufenen Methoden
  • Nutze console.time() um Performance-Bottlenecks zu identifizieren
  • Deaktiviere Debug-Logs in Production über den LoggerService
  • Verwende Tree-Shaking-freundliche Imports für externe Logging-Bibliotheken

4. Logge strukturierte Daten

Statt einzelner Strings solltest du strukturierte Objekte loggen – das macht die Analyse einfacher:

// ❌ Schlecht
this.logger.info('User logged in: ' + user.email);

// ✅ Gut
this.logger.info('User logged in', { email: user.email, role: user.role, timestamp: Date.now() });

5. Logge keine sensiblen Daten

Achte darauf, dass du niemals Passwörter, Tokens oder persönliche Daten in Logs schreibst. Nutze Filtering oder Masking:

// ❌ Niemals
this.logger.debug('Auth Token:', token);

// ✅ Sicher
this.logger.debug('Auth Token:', token.substring(0, 8) + '***');

🎯 Fazit

Logging in Angular ist mehr als nur console.log(). Mit dem richtigen Setup hast du ein mächtiges Werkzeug zur Fehlersuche und Überwachung:

  • Ein TypeScript-Decorator automatisiert das Logging auf Klassenebene
  • Die Console-API bietet weit mehr als nur .log() – nutze .table(), .group() und .time()
  • Ein LoggerService bringt Struktur und Kontrolle über Log-Level
  • Externe Services wie Sentry und LogRocket machen dein Logging produktionsreif
  • Best Practices sorgen dafür, dass dein Logging sicher, performant und nützlich bleibt

Fang am besten mit dem Decorator und dem LoggerService an – du wirst schnell merken, wie viel einfacher das Debugging wird! 🚀

Artikel teilen:Share article: