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,56 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { ErrorCode } from './error-codes';
export interface ErrorResponseBody {
statusCode: number;
errorCode: ErrorCode;
message: string;
details?: Record<string, unknown>;
correlationId?: string;
timestamp: string;
}
export class DomainException extends HttpException {
constructor(
public readonly errorCode: ErrorCode,
message: string,
statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR,
public readonly details?: Record<string, unknown>,
) {
super(message, statusCode);
}
}
export class NotFoundException extends DomainException {
constructor(entity: string, id?: string) {
super(
ErrorCode.NOT_FOUND,
id ? `${entity} with id '${id}' not found` : `${entity} not found`,
HttpStatus.NOT_FOUND,
);
}
}
export class ValidationException extends DomainException {
constructor(message: string, details?: Record<string, unknown>) {
super(ErrorCode.VALIDATION_FAILED, message, HttpStatus.BAD_REQUEST, details);
}
}
export class ConflictException extends DomainException {
constructor(message: string) {
super(ErrorCode.CONFLICT, message, HttpStatus.CONFLICT);
}
}
export class UnauthorizedException extends DomainException {
constructor(message = 'Unauthorized') {
super(ErrorCode.UNAUTHORIZED, message, HttpStatus.UNAUTHORIZED);
}
}
export class ForbiddenException extends DomainException {
constructor(message = 'Forbidden') {
super(ErrorCode.FORBIDDEN, message, HttpStatus.FORBIDDEN);
}
}