Files
backend/src/common/logging/structured-logger.service.ts
T

67 lines
1.6 KiB
TypeScript

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<string, boolean | number | string | undefined>;
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,
),
);
}
}