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:
Ho Ngoc Hai
2026-04-08 00:18:21 +07:00
parent 1fb7bb39d2
commit c981bff771
14 changed files with 564 additions and 5 deletions

View File

@@ -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();
}
}

View File

@@ -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();
}
}