Files
ISA-Frontend/.claude/skills/logging/examples.md
Lorenz Hilpert fd8e0194ac 🚚 refactor(skills): reorganize skill structure
- Rename logging-helper to logging for consistency
- Remove git-commit-helper (superseded by /commit command)
- Add git-workflow skill for Git Flow operations

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 13:39:14 +01:00

9.1 KiB

Logging Examples

Concise real-world examples of logging patterns.

1. Component with Observable

import { Component, OnInit } from '@angular/core';
import { logger } from '@isa/core/logging';

@Component({
  selector: 'app-product-list',
  standalone: true,
})
export class ProductListComponent implements OnInit {
  #logger = logger({ component: 'ProductListComponent' });

  constructor(private productService: ProductService) {}

  ngOnInit(): void {
    this.#logger.info('Component initialized');
    this.loadProducts();
  }

  private loadProducts(): void {
    this.productService.getProducts().subscribe({
      next: (products) => {
        this.#logger.info('Products loaded', () => ({ count: products.length }));
      },
      error: (error) => {
        this.#logger.error('Failed to load products', error);
      }
    });
  }
}

2. Service with HTTP

import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { logger } from '@isa/core/logging';
import { catchError, tap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class OrderService {
  private http = inject(HttpClient);
  #logger = logger({ service: 'OrderService' });

  getOrder(id: string): Observable<Order> {
    this.#logger.debug('Fetching order', { id });

    return this.http.get<Order>(`/api/orders/${id}`).pipe(
      tap((order) => this.#logger.info('Order fetched', () => ({
        id,
        status: order.status
      }))),
      catchError((error) => {
        this.#logger.error('Fetch failed', error, () => ({ id, status: error.status }));
        throw error;
      })
    );
  }
}

3. Hierarchical Context

import { Component } from '@angular/core';
import { logger, provideLoggerContext } from '@isa/core/logging';

@Component({
  selector: 'oms-return-process',
  standalone: true,
  providers: [
    provideLoggerContext({ feature: 'returns', module: 'oms' })
  ],
})
export class ReturnProcessComponent {
  #logger = logger(() => ({
    processId: this.currentProcessId,
    step: this.currentStep
  }));

  private currentProcessId = crypto.randomUUID();
  private currentStep = 1;

  startProcess(orderId: string): void {
    // Logs include: feature, module, processId, step, orderId
    this.#logger.info('Process started', { orderId });
  }
}

4. NgRx Effect

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { logger } from '@isa/core/logging';
import { map, catchError, tap } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class OrdersEffects {
  #logger = logger({ effect: 'OrdersEffects' });

  loadOrders$ = createEffect(() =>
    this.actions$.pipe(
      ofType(OrdersActions.loadOrders),
      tap((action) => this.#logger.debug('Loading orders', () => ({
        page: action.page
      }))),
      mergeMap((action) =>
        this.orderService.getOrders(action.filters).pipe(
          map((orders) => {
            this.#logger.info('Orders loaded', () => ({ count: orders.length }));
            return OrdersActions.loadOrdersSuccess({ orders });
          }),
          catchError((error) => {
            this.#logger.error('Load failed', error);
            return of(OrdersActions.loadOrdersFailure({ error }));
          })
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private orderService: OrderService
  ) {}
}

5. Guard with Authorization

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { logger } from '@isa/core/logging';

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  const log = logger({ guard: 'AuthGuard' });

  if (authService.isAuthenticated()) {
    log.debug('Access granted', () => ({ route: state.url }));
    return true;
  }

  log.warn('Access denied', () => ({
    attemptedRoute: state.url,
    redirectTo: '/login'
  }));
  return router.createUrlTree(['/login']);
};

6. HTTP Interceptor

import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { tap, catchError } from 'rxjs/operators';
import { LoggingService } from '@isa/core/logging';

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  const loggingService = inject(LoggingService);
  const startTime = performance.now();

  loggingService.debug('HTTP Request', () => ({
    method: req.method,
    url: req.url
  }));

  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response) {
        loggingService.info('HTTP Response', () => ({
          method: req.method,
          url: req.url,
          status: event.status,
          duration: `${(performance.now() - startTime).toFixed(2)}ms`
        }));
      }
    }),
    catchError((error) => {
      loggingService.error('HTTP Error', error, () => ({
        method: req.method,
        url: req.url,
        status: error.status
      }));
      return throwError(() => error);
    })
  );
};

7. Form Validation

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { logger } from '@isa/core/logging';

@Component({
  selector: 'shared-user-form',
  standalone: true,
})
export class UserFormComponent implements OnInit {
  #logger = logger({ component: 'UserFormComponent' });
  form!: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.form = this.fb.group({
      name: ['', Validators.required],
      email: ['', [Validators.required, Validators.email]]
    });
  }

  onSubmit(): void {
    if (this.form.invalid) {
      this.#logger.warn('Invalid form submission', () => ({
        errors: this.getFormErrors()
      }));
      return;
    }

    this.#logger.info('Form submitted');
  }

  private getFormErrors(): Record<string, unknown> {
    const errors: Record<string, unknown> = {};
    Object.keys(this.form.controls).forEach((key) => {
      const control = this.form.get(key);
      if (control?.errors) errors[key] = control.errors;
    });
    return errors;
  }
}

8. Async Progress Tracking

import { Injectable } from '@angular/core';
import { logger } from '@isa/core/logging';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class ImportService {
  #logger = logger({ service: 'ImportService' });

  importData(file: File): Observable<number> {
    const importId = crypto.randomUUID();

    this.#logger.info('Import started', () => ({
      importId,
      fileName: file.name,
      fileSize: file.size
    }));

    return this.processImport(file).pipe(
      tap((progress) => {
        if (progress % 25 === 0) {
          this.#logger.debug('Import progress', () => ({
            importId,
            progress: `${progress}%`
          }));
        }
      }),
      tap({
        complete: () => this.#logger.info('Import completed', { importId }),
        error: (error) => this.#logger.error('Import failed', error, { importId })
      })
    );
  }

  private processImport(file: File): Observable<number> {
    // Implementation
  }
}

9. Global Error Handler

import { Injectable, ErrorHandler } from '@angular/core';
import { logger } from '@isa/core/logging';

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
  #logger = logger({ handler: 'GlobalErrorHandler' });

  handleError(error: Error): void {
    this.#logger.error('Uncaught error', error, () => ({
      url: window.location.href,
      userAgent: navigator.userAgent,
      timestamp: new Date().toISOString()
    }));
  }
}

10. WebSocket Component

import { Component, OnInit, OnDestroy } from '@angular/core';
import { logger } from '@isa/core/logging';
import { Subject, takeUntil } from 'rxjs';

@Component({
  selector: 'oms-live-orders',
  standalone: true,
})
export class LiveOrdersComponent implements OnInit, OnDestroy {
  #logger = logger({ component: 'LiveOrdersComponent' });
  private destroy$ = new Subject<void>();

  constructor(private wsService: WebSocketService) {}

  ngOnInit(): void {
    this.#logger.info('Connecting to WebSocket');

    this.wsService.connect('orders').pipe(
      takeUntil(this.destroy$)
    ).subscribe({
      next: (msg) => this.#logger.debug('Message received', () => ({
        type: msg.type,
        orderId: msg.orderId
      })),
      error: (error) => this.#logger.error('WebSocket error', error),
      complete: () => this.#logger.info('WebSocket closed')
    });
  }

  ngOnDestroy(): void {
    this.#logger.debug('Component destroyed');
    this.destroy$.next();
    this.destroy$.complete();
  }
}