Introduction
Errors in production are inevitable. Proper logging and monitoring ensure you know about errors before users report them, understand their impact, and have the context needed to fix them.
Key Concepts
- Structured Logging: JSON logs with consistent fields
- Error Context: Information needed to debug
- Error Tracking: Aggregating and alerting on errors
- Correlation ID: Linking related log entries
Real World Context
Proper error logging enables:
- Proactive issue detection
- Fast incident response
- Root cause analysis
- Trend identification
Deep Dive
Structured Error Logging
typescript@Injectable() export class ErrorLoggerService { private readonly logger = new Logger('Error'); logError( error: Error, context: { correlationId?: string; userId?: string; path?: string; method?: string; body?: any; [key: string]: any; }, ) { const errorLog = { name: error.name, message: error.message, stack: error.stack, code: (error as any).code, ...context, timestamp: new Date().toISOString(), }; this.logger.error(JSON.stringify(errorLog)); // Send to error tracking service this.sendToErrorTracking(errorLog); } private sendToErrorTracking(errorLog: any) { // Integration with Sentry, Datadog, etc. } }
Global Exception Filter with Logging
typescript@Catch() export class GlobalExceptionFilter implements ExceptionFilter { constructor( private errorLogger: ErrorLoggerService, private readonly httpAdapterHost: HttpAdapterHost, ) {} catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const request = ctx.getRequest(); const response = ctx.getResponse(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; const correlationId = request.headers['x-correlation-id'] || uuid(); // Log with full context this.errorLogger.logError(exception as Error, { correlationId, userId: request.user?.id, path: request.url, method: request.method, body: this.sanitizeBody(request.body), query: request.query, headers: this.sanitizeHeaders(request.headers), statusCode: status, }); // Return sanitized error to client response.status(status).json({ statusCode: status, message: this.getClientMessage(exception, status), correlationId, timestamp: new Date().toISOString(), }); } private sanitizeBody(body: any): any { const sensitiveFields = ['password', 'token', 'secret', 'creditCard']; return Object.fromEntries( Object.entries(body || {}).map(([key, value]) => sensitiveFields.includes(key) ? [key, '[REDACTED]'] : [key, value] ), ); } private sanitizeHeaders(headers: Record<string, string>): Record<string, string> { const sensitiveHeaders = ['authorization', 'cookie', 'x-api-key']; return Object.fromEntries( Object.entries(headers || {}).map(([key, value]) => sensitiveHeaders.includes(key.toLowerCase()) ? [key, '[REDACTED]'] : [key, value] ), ); } private getClientMessage(exception: unknown, status: number): string { if (exception instanceof HttpException) { return exception.message; } // Don't expose internal errors to clients return status >= 500 ? 'Internal server error' : 'An error occurred'; } }
Correlation ID Middleware
typescript@Injectable() export class CorrelationIdMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { const correlationId = req.headers['x-correlation-id'] || uuid(); req.headers['x-correlation-id'] = correlationId; res.setHeader('x-correlation-id', correlationId); next(); } }
Error Metrics
typescript@Injectable() export class ErrorMetricsService { private errorCounts = new Map<string, number>(); recordError(errorType: string, path: string) { const key = `${errorType}:${path}`; this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1); } getMetrics() { return Object.fromEntries(this.errorCounts); } // Reset metrics periodically @Cron('0 * * * *') // Every hour resetMetrics() { this.errorCounts.clear(); } }
Integration Example (Sentry)
typescriptimport * as Sentry from '@sentry/node'; @Injectable() export class SentryService { constructor() { Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, }); } captureException(error: Error, context?: Record<string, any>) { Sentry.withScope((scope) => { if (context) { Object.entries(context).forEach(([key, value]) => { scope.setExtra(key, value); }); } Sentry.captureException(error); }); } }
Common Pitfalls
- Logging sensitive data: Passwords, tokens, PII in logs.
- Too much logging: Noise obscures real issues.
- No correlation: Can't follow a request across services.
Best Practices
- Use structured JSON logging
- Include correlation IDs in all logs
- Sanitize sensitive data before logging
- Aggregate errors by type and frequency
- Set up alerts for error rate spikes
- Log context needed for debugging
Summary
Effective error logging uses structured JSON with consistent fields. Include correlation IDs, sanitize sensitive data, and integrate with error tracking services. Monitor error rates and set up alerts for anomalies.
Code Examples
typescript
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const correlationId = request.headers['x-correlation-id'] ?? uuid();
const status = exception instanceof HttpException
? exception.getStatus() : 500;
this.logger.error(JSON.stringify({
correlationId, status, path: request.url,
message: exception instanceof Error ? exception.message : 'Unknown',
}));
ctx.getResponse().status(status).json({
statusCode: status, correlationId,
message: status >= 500 ? 'Internal server error' : (exception as any).message,
});
}
}