Code Quality Help

Error Handling and Logging

Proper error handling and consistent logging are critical for diagnosing issues in production and maintaining system reliability. This section defines the rules for both backend (C#) and frontend (Angular/TypeScript).

10.1 General Principles

  • Fail fast: Validate inputs early and throw appropriate exceptions rather than silently proceeding in an invalid state.

  • Never swallow exceptions: Catch only when you can handle the error meaningfully (retry, fallback, user-friendly message). Always log the exception and/or rethrow.

  • Don't expose internals: Error messages returned to API consumers (including the frontend) must not contain stack traces, connection strings, or sensitive data. Use generic messages for unhandled errors; detailed information goes to logs.

  • Structured logging: Use structured (semantic) logging instead of string interpolation. Log events with parameters, so logs can be queried by key values.

  • Log levels must be used consistently:

    • Trace/Debug – detailed diagnostic information, only in development or when explicitly enabled.

    • Information – high‑level operational messages (e.g., "Order processed", "User logged in").

    • Warning – unexpected but non‑critical situations (e.g., retry occurred, fallback used).

    • Error – failures that break an operation (exceptions, failed HTTP calls).

    • Critical – catastrophic failures requiring immediate attention (out of memory, service down).

10.2 C# Error Handling

  • Throwing exceptions: Always throw the most specific exception type available.

    // Good throw new InvalidOperationException("Order must be in Pending status to cancel."); // Bad throw new Exception("Something went wrong.");
  • Custom exceptions: Create custom exception classes (inheriting from Exception or ApplicationException) when you need to convey domain-specific errors that callers can handle.

  • Argument validation: Use ArgumentException.ThrowIfNullOrWhiteSpace, ArgumentNullException.ThrowIfNull, ArgumentOutOfRangeException.ThrowIf* for public methods.

  • Global error handler: In ASP.NET Core, use exception middleware (UseExceptionHandler) that catches unhandled exceptions, logs them, and returns a problem details response (ProblemDetails). Do not catch Exception in every controller action.

  • try-catch placement: Place try-catch only around code that might fail and you have a recovery action or meaningful log. Avoid large try-catch blocks; be as specific as possible.

    try { await _paymentGateway.ChargeAsync(request, ct); } catch (PaymentTimeoutException ex) { _logger.LogWarning(ex, "Payment timed out for order {OrderId}, retrying", request.OrderId); await _paymentGateway.RetryAsync(request.OrderId, ct); }
  • No empty catch: Empty catch is a code smell. If you absolutely must ignore an exception (e.g., optional cleanup), log it at Debug level and include a comment.

  • Async void: Avoid async void. Exceptions thrown in async void methods crash the process. Use async Task and let the framework handle the exception.

10.3 C# Logging

  • Use ILogger<T>: Inject ILogger<T> from Microsoft.Extensions.Logging into every service.

  • Structured logging: Use log templates with named placeholders.

    // Good _logger.LogInformation("User {UserId} placed order {OrderId} with total {Total}", userId, orderId, total); // Bad _logger.LogInformation($"User {userId} placed order {orderId} with total {total}");
  • Log scopes: Use BeginScope to attach contextual information to a series of log events (e.g., correlation ID, request path).

    using (_logger.BeginScope("OrderScope:{OrderId}", orderId)) { _logger.LogInformation("Processing order"); // ... }
  • Exception logging: When logging an exception, always pass the exception object as the first argument to include the full stack trace.

    _logger.LogError(ex, "Failed to process order {OrderId}", orderId);
  • Sensitive data: Never log PII (personal identifiable information), passwords, tokens, or full credit card numbers. Use a masking helper or log only the last 4 digits. The code review must check for this.

  • Log level configuration: In production, the default log level is Information. Use Warning for transient failures. Do not leave Debug or Trace enabled in production unless troubleshooting an active issue.

10.4 TypeScript / Angular Error Handling

  • API Errors in Services: HTTP services must handle HTTP errors centrally using HttpInterceptor or catchError operator, not inside each component.

    // In a service getOrders(): Observable<Order[]> { return this.http.get<Order[]>(`${this.apiUrl}/orders`).pipe( catchError(this.handleError('getOrders', [])) ); } private handleError<T>(operation = 'operation', result?: T) { return (error: HttpErrorResponse): Observable<T> => { this.logger.error(`${operation} failed: ${error.message}`, error); // re-throw a user-friendly error or return fallback throw new Error('A server error occurred. Please try again later.'); }; }
  • Global error handler: Implement ErrorHandler class and provide it in AppModule to catch unhandled Angular errors.

    @Injectable() export class GlobalErrorHandler implements ErrorHandler { handleError(error: Error) { console.error('Unhandled error:', error); // Send to server-side logging service if available } }
  • Observable subscriptions: Always include error handling in subscriptions if you don’t propagate the error via async pipe. The pipe can output an error to the template (| async inside *ngIf="obs$ | async as data; else errorTemplate").

  • Template error boundaries: Use ng-container with error template to display fallback UI when observable errors.

    <ng-container *ngIf="data$ | async as data; else error"> ... </ng-container> <ng-template #error> <p>Failed to load data. Please try again.</p> </ng-template>
  • **Avoid swallowing errors in .subscribe() without a fallback or notification to the user. At minimum, log the error.

10.5 Angular Logging

  • Use a logging service: Do not call console.log() directly. Create an injectable LoggingService that wraps console methods (or a third-party service) and respects log levels.

    @Injectable({ providedIn: 'root' }) export class LoggingService { log(message: string, data?: any) { console.log(`[INFO] ${message}`, data); } warn(message: string, data?: any) { console.warn(`[WARN] ${message}`, data); } error(message: string, error?: any) { console.error(`[ERROR] ${message}`, error); } }
  • Disable console in production: Use Angular’s environment.production flag to conditionally disable log and debug. Always keep error and warn active for diagnostics.

  • Structured frontend logs: When sending logs to a backend service, include a correlation ID (from the HTTP response header) to link frontend errors with backend traces.

  • No PII in browser console: Do not log user passwords, tokens, or full credit card details. When debugging, always remove such logs before committing code.

04 мая 2026