Files
ISA-Frontend/.claude/skills/logging/SKILL.md
Lorenz Hilpert db4f30af86 🔧 chore: improve skills cross-references and CLAUDE.md guidance
- Add --amend option with safety rules to commit command
- Add logging skill cross-references to angular-template and html-template
- Fix logging skill name from logging-helper to logging
- Add extended thinking triggers, context monitoring, and code investigation
  rules to CLAUDE.md
2025-12-02 12:57:27 +01:00

7.0 KiB

name, description
name description
logging This skill should be used when working with Angular components, directives, services, pipes, guards, or TypeScript classes. Logging is MANDATORY in all Angular files. Implements @isa/core/logging with logger() factory pattern, appropriate log levels, lazy evaluation for performance, error handling, and avoids console.log and common mistakes.

Logging Helper Skill

Ensures consistent and efficient logging using @isa/core/logging library.

When to Use

  • Adding logging to new components/services
  • Refactoring existing logging code
  • Reviewing code for proper logging patterns
  • Debugging logging issues

Core Principles

1. Always Use Factory Pattern

import { logger } from '@isa/core/logging';

// ✅ DO
#logger = logger();

// ❌ DON'T
constructor(private loggingService: LoggingService) {}

2. Choose Appropriate Log Levels

  • Trace: Fine-grained debugging (method entry/exit)
  • Debug: Development debugging (variable states)
  • Info: Runtime information (user actions, events)
  • Warn: Potentially harmful situations
  • Error: Errors affecting functionality

3. Context Patterns

Static Context (component level):

#logger = logger({ component: 'UserProfileComponent' });

Dynamic Context (instance level):

#logger = logger(() => ({
  userId: this.authService.currentUserId,
  storeId: this.config.storeId
}));

Message Context (use functions for performance):

// ✅ Recommended - lazy evaluation
this.#logger.info('Order processed', () => ({
  orderId: order.id,
  total: order.total,
  timestamp: Date.now()
}));

// ✅ Acceptable - static values
this.#logger.info('Order processed', {
  orderId: order.id,
  status: 'completed'
});

Essential Patterns

Component Logging

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

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

  onAction(id: string): void {
    this.#logger.debug('Action triggered', { id });
  }
}

Service Logging

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

  fetchData(endpoint: string): Observable<Data> {
    this.#logger.debug('Fetching data', { endpoint });

    return this.http.get<Data>(endpoint).pipe(
      tap((data) => this.#logger.info('Data fetched', () => ({
        endpoint,
        size: data.length
      }))),
      catchError((error) => {
        this.#logger.error('Fetch failed', error, () => ({
          endpoint,
          status: error.status
        }));
        return throwError(() => error);
      })
    );
  }
}

Error Handling

try {
  await this.processOrder(orderId);
} catch (error) {
  this.#logger.error('Order processing failed', error as Error, () => ({
    orderId,
    step: this.currentStep,
    attemptNumber: this.retryCount
  }));
  throw error;
}

Hierarchical Context

@Component({
  providers: [
    provideLoggerContext({ feature: 'checkout', module: 'sales' })
  ]
})
export class CheckoutComponent {
  #logger = logger(() => ({ userId: this.userService.currentUserId }));

  // Logs include: feature, module, userId + message context
}

Common Mistakes to Avoid

// ❌ Don't use console.log
console.log('User logged in');
// ✅ Use logger
this.#logger.info('User logged in');

// ❌ Don't create expensive context eagerly
this.#logger.debug('Processing', {
  data: this.computeExpensive() // Always executes
});
// ✅ Use function for lazy evaluation
this.#logger.debug('Processing', () => ({
  data: this.computeExpensive() // Only if debug enabled
}));

// ❌ Don't log in tight loops
for (const item of items) {
  this.#logger.debug('Item', { item });
}
// ✅ Log aggregates
this.#logger.debug('Batch processed', () => ({
  count: items.length
}));

// ❌ Don't log sensitive data
this.#logger.info('User auth', { password: user.password });
// ✅ Log safe identifiers only
this.#logger.info('User auth', { userId: user.id });

// ❌ Don't miss error object
this.#logger.error('Failed');
// ✅ Include error object
this.#logger.error('Failed', error as Error);

Configuration

App Configuration

// app.config.ts
import { ApplicationConfig, isDevMode } from '@angular/core';
import {
  provideLogging, withLogLevel, withSink,
  LogLevel, ConsoleLogSink
} from '@isa/core/logging';

export const appConfig: ApplicationConfig = {
  providers: [
    provideLogging(
      withLogLevel(isDevMode() ? LogLevel.Debug : LogLevel.Warn),
      withSink(ConsoleLogSink),
      withContext({ app: 'ISA', version: '1.0.0' })
    )
  ]
};

Testing

import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { LoggingService } from '@isa/core/logging';

describe('MyComponent', () => {
  const createComponent = createComponentFactory({
    component: MyComponent,
    mocks: [LoggingService]
  });

  it('should log error', () => {
    const spectator = createComponent();
    const loggingService = spectator.inject(LoggingService);

    spectator.component.riskyOperation();

    expect(loggingService.error).toHaveBeenCalledWith(
      expect.any(String),
      expect.any(Error),
      expect.any(Function)
    );
  });
});

Code Review Checklist

  • Uses logger() factory, not LoggingService injection
  • Appropriate log level for each message
  • Context functions for expensive operations
  • No sensitive information (passwords, tokens, PII)
  • No console.log statements
  • Error logs include error object
  • No logging in tight loops
  • Component/service identified in context
  • E2E attributes on interactive elements

Quick Reference

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

// Create logger
#logger = logger();                              // Basic
#logger = logger({ component: 'Name' });         // Static context
#logger = logger(() => ({ id: this.id }));       // Dynamic context

// Log messages
this.#logger.trace('Detailed trace');
this.#logger.debug('Debug info');
this.#logger.info('General info', () => ({ key: value }));
this.#logger.warn('Warning');
this.#logger.error('Error', error, () => ({ context }));

// Component context
@Component({
  providers: [provideLoggerContext({ feature: 'users' })]
})

Additional Resources

  • Full documentation: libs/core/logging/README.md
  • Examples: .claude/skills/logging-helper/examples.md
  • Quick reference: .claude/skills/logging-helper/reference.md
  • Troubleshooting: .claude/skills/logging-helper/troubleshooting.md