import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { InjectRepository } from '@nestjs/typeorm'; import { hostname } from 'node:os'; import { Repository } from 'typeorm'; import { sanitizeContext, sanitizeString } from '../application-error-log/application-error-sanitizer'; import { RequestContextService } from '../common/request-context.service'; import { ApplicationInfoLog } from './application-info-log.entity'; import { ApplicationInfoLogInput } from './application-info-log.types'; @Injectable() export class ApplicationInfoLoggerService { private readonly fallbackLogger = new Logger(ApplicationInfoLoggerService.name); constructor( @InjectRepository(ApplicationInfoLog) private readonly logs: Repository, private readonly config: ConfigService, private readonly requestContext: RequestContextService, ) {} async log(input: ApplicationInfoLogInput): Promise { const activeRequest = this.requestContext.get(); const request = { ...activeRequest, ...input.requestContext }; try { await this.logs.save( this.logs.create({ level: 'info', action: sanitizeString(input.action), category: optionalString(input.category), outcome: input.outcome ?? 'SUCCESS', message: optionalString(input.message), actorType: input.actorType, actorId: optionalString(input.actorId), userId: optionalString(input.userId ?? request.userId), clientId: optionalString(input.clientId), tenantId: optionalString(input.tenantId ?? request.tenantId), backendModule: optionalString(input.backendModule), service: optionalString(input.service), operation: optionalString(input.operation), httpMethod: optionalString(request.method), apiPath: optionalString(withoutQuery(request.path)), httpStatusCode: input.requestContext?.statusCode, correlationId: optionalString(request.correlationId), ipAddress: optionalString(input.requestContext?.ipAddress), userAgent: optionalString(input.requestContext?.userAgent), environment: this.config.get('NODE_ENV') ?? 'development', host: hostname(), context: sanitizeContext(input.context), }), ); } catch (error) { const message = error instanceof Error ? error.message : String(error); this.fallbackLogger.error( `Application info log write failed for ${sanitizeString(input.action)}: ${sanitizeString(message)}`, error instanceof Error ? error.stack : undefined, ); } } } function optionalString(value?: string): string | undefined { return value ? sanitizeString(value) : undefined; } function withoutQuery(value?: string): string | undefined { return value?.split('?', 1)[0]; }