260223:1415 20260223 nextJS & nestJS Best pratices
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
All checks were successful
Build and Deploy / deploy (push) Successful in 4m44s
This commit is contained in:
232
.agents/skills/nestjs-best-practices/rules/devops-use-logging.md
Normal file
232
.agents/skills/nestjs-best-practices/rules/devops-use-logging.md
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
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<User> {
|
||||
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<User> {
|
||||
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<User> {
|
||||
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)
|
||||
Reference in New Issue
Block a user