feat(shared): add error handling & structured logging strategy
- Global exception filter with consistent error response format - Domain exceptions (NotFoundException, ValidationException, etc.) - Error codes enum for domain-specific error identification - Correlation ID middleware for request tracing - Request/response logging middleware with structured JSON - PII masking in logs (emails, phone numbers, sensitive fields) - Enhanced LoggerService with pino formatters and ISO timestamps - Tests for exception filter, domain exceptions, and PII masker Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import { Injectable, type NestMiddleware } from '@nestjs/common';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
|
||||
const CORRELATION_ID_HEADER = 'x-correlation-id';
|
||||
|
||||
@Injectable()
|
||||
export class CorrelationIdMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: NextFunction): void {
|
||||
const correlationId = (req.headers[CORRELATION_ID_HEADER] as string) || randomUUID();
|
||||
req.headers[CORRELATION_ID_HEADER] = correlationId;
|
||||
res.setHeader(CORRELATION_ID_HEADER, correlationId);
|
||||
next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Injectable, type NestMiddleware } from '@nestjs/common';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
import { LoggerService } from '../logger.service';
|
||||
|
||||
@Injectable()
|
||||
export class RequestLoggingMiddleware implements NestMiddleware {
|
||||
private readonly pinoChild;
|
||||
|
||||
constructor(private readonly logger: LoggerService) {
|
||||
this.pinoChild = this.logger.child({ component: 'http' });
|
||||
}
|
||||
|
||||
use(req: Request, res: Response, next: NextFunction): void {
|
||||
const start = Date.now();
|
||||
const correlationId = req.headers['x-correlation-id'] as string | undefined;
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
const logData = {
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
statusCode: res.statusCode,
|
||||
duration,
|
||||
correlationId,
|
||||
userAgent: req.headers['user-agent'],
|
||||
ip: req.ip,
|
||||
};
|
||||
|
||||
if (res.statusCode >= 500) {
|
||||
this.pinoChild.error(logData, 'request completed');
|
||||
} else if (res.statusCode >= 400) {
|
||||
this.pinoChild.warn(logData, 'request completed');
|
||||
} else {
|
||||
this.pinoChild.info(logData, 'request completed');
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user