import { Injectable, OnApplicationShutdown } from '@nestjs/common'; import { PinoLogger } from 'nestjs-pino'; import { RequestContextService } from './request-context.service'; type LogLevel = 'debug' | 'error' | 'info' | 'warn'; type LogContext = Record; const allowedFields = new Set([ 'attempts', 'brokerageAccountId', 'clientRequestId', 'code', 'count', 'durationMs', 'environment', 'found', 'grpcCode', 'method', 'operation', 'requestId', 'route', 'service', 'status', 'userId', ]); @Injectable() export class StructuredLogger implements OnApplicationShutdown { constructor( private readonly requestContext: RequestContextService, private readonly pino: PinoLogger, ) {} info(message: string, context?: LogContext): void { this.write('info', message, context); } event(level: LogLevel, message: string, context?: LogContext): void { this.write(level, message, context); } onApplicationShutdown(): void { this.info('application.stopped'); } private write( level: LogLevel, message: string, context: LogContext = {}, ): void { // eslint-disable-next-line security/detect-object-injection -- The log level is restricted to the internal LogLevel union. this.pino[level]( this.safeContext({ ...this.requestContext.get(), ...context }), message, ); } private safeContext(context: LogContext): LogContext { return Object.fromEntries( Object.entries(context).filter( ([key, value]) => allowedFields.has(key) && value !== undefined, ), ); } }