--- title: Use Structured Logging impact: MEDIUM-HIGH impactDescription: Structured logging enables effective debugging and monitoring tags: devops, logging, structured-logs, pino --- ## Use Structured Logging Use NestJS Logger with structured JSON output in production. Include contextual information (request ID, user ID, operation) to trace requests across services. Avoid console.log and implement proper log levels. **Incorrect (using console.log in production):** ```typescript // Use console.log in production @Injectable() export class UsersService { async createUser(dto: CreateUserDto): Promise { console.log('Creating user:', dto); // Not structured, no levels, lost in production logs try { const user = await this.repo.save(dto); console.log('User created:', user.id); return user; } catch (error) { console.log('Error:', error); // Using log for errors throw error; } } } // Log sensitive data console.log('Login attempt:', { email, password }); // SECURITY RISK! // Inconsistent log format logger.log('User ' + userId + ' created at ' + new Date()); // Hard to parse, no structure ``` **Correct (use structured logging with context):** ```typescript // Configure logger in main.ts async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: process.env.NODE_ENV === 'production' ? ['error', 'warn', 'log'] : ['error', 'warn', 'log', 'debug', 'verbose'], }); } // Use NestJS Logger with context @Injectable() export class UsersService { private readonly logger = new Logger(UsersService.name); async createUser(dto: CreateUserDto): Promise { this.logger.log('Creating user', { email: dto.email }); try { const user = await this.repo.save(dto); this.logger.log('User created', { userId: user.id }); return user; } catch (error) { this.logger.error('Failed to create user', error.stack, { email: dto.email, }); throw error; } } } // Custom logger for JSON output @Injectable() export class JsonLogger implements LoggerService { log(message: string, context?: object): void { console.log( JSON.stringify({ level: 'info', timestamp: new Date().toISOString(), message, ...context, }), ); } error(message: string, trace?: string, context?: object): void { console.error( JSON.stringify({ level: 'error', timestamp: new Date().toISOString(), message, trace, ...context, }), ); } warn(message: string, context?: object): void { console.warn( JSON.stringify({ level: 'warn', timestamp: new Date().toISOString(), message, ...context, }), ); } debug(message: string, context?: object): void { console.debug( JSON.stringify({ level: 'debug', timestamp: new Date().toISOString(), message, ...context, }), ); } } // Request context logging with ClsModule import { ClsModule, ClsService } from 'nestjs-cls'; @Module({ imports: [ ClsModule.forRoot({ global: true, middleware: { mount: true, generateId: true, }, }), ], }) export class AppModule {} // Middleware to set request context @Injectable() export class RequestContextMiddleware implements NestMiddleware { constructor(private cls: ClsService) {} use(req: Request, res: Response, next: NextFunction): void { const requestId = req.headers['x-request-id'] || randomUUID(); this.cls.set('requestId', requestId); this.cls.set('userId', req.user?.id); res.setHeader('x-request-id', requestId); next(); } } // Logger that includes request context @Injectable() export class ContextLogger { constructor(private cls: ClsService) {} log(message: string, data?: object): void { console.log( JSON.stringify({ level: 'info', timestamp: new Date().toISOString(), requestId: this.cls.get('requestId'), userId: this.cls.get('userId'), message, ...data, }), ); } error(message: string, error: Error, data?: object): void { console.error( JSON.stringify({ level: 'error', timestamp: new Date().toISOString(), requestId: this.cls.get('requestId'), userId: this.cls.get('userId'), message, error: error.message, stack: error.stack, ...data, }), ); } } // Pino integration for high-performance logging import { LoggerModule } from 'nestjs-pino'; @Module({ imports: [ LoggerModule.forRoot({ pinoHttp: { level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined, redact: ['req.headers.authorization', 'req.body.password'], serializers: { req: (req) => ({ method: req.method, url: req.url, query: req.query, }), res: (res) => ({ statusCode: res.statusCode, }), }, }, }), ], }) export class AppModule {} // Usage with Pino @Injectable() export class UsersService { constructor(private logger: PinoLogger) { this.logger.setContext(UsersService.name); } async findOne(id: string): Promise { this.logger.info({ userId: id }, 'Finding user'); // Pino uses first arg for data, second for message } } ``` Reference: [NestJS Logger](https://docs.nestjs.com/techniques/logger)