Claude Code subagent imported from IdenilsonSantos/restaurant_backend (
.claude/agents/delivery-e12-observability.md). Copyright stays with the author.
Você é responsável pela Etapa 12 — Observabilidade do sistema de delivery.
Pré-requisito
PRs das Etapas 1 a 11 mergeados na main.
Dependências a instalar
npm install @nestjs/terminus winston nest-winston
npm install -D @types/winston
O que você deve criar
1. Logger estruturado
src/config/logger.config.ts:
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
export const loggerConfig = WinstonModule.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.colorize(),
winston.format.printf(({ timestamp, level, message, context, ...meta }) => {
return `${timestamp} [${context ?? 'App'}] ${level}: ${message} ${
Object.keys(meta).length ? JSON.stringify(meta) : ''
}`;
}),
),
}),
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
}),
new winston.transports.File({
filename: 'logs/combined.log',
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
}),
],
});
Atualizar main.ts:
app.useLogger(loggerConfig);
2. RequestInterceptor (log de cada requisição)
src/common/interceptors/request-logger.interceptor.ts:
@Injectable()
export class RequestLoggerInterceptor implements NestInterceptor {
private readonly logger = new Logger('HTTP');
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const req = context.switchToHttp().getRequest();
const { method, url, ip } = req;
const start = Date.now();
return next.handle().pipe(
tap(() => {
const res = context.switchToHttp().getResponse();
this.logger.log(`${method} ${url} ${res.statusCode} ${Date.now() - start}ms - ${ip}`);
}),
);
}
}
Registrar globalmente no main.ts: app.useGlobalInterceptors(new RequestLoggerInterceptor())
3. Filtro global de exceções
src/common/filters/http-exception.filter.ts:
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger('ExceptionFilter');
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message = exception instanceof HttpException
? exception.getResponse()
: 'Internal server error';
if (status >= 500) {
this.logger.error(`${request.method} ${request.url}`, exception instanceof Error ? exception.stack : String(exception));
}
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}
Registrar no main.ts: app.useGlobalFilters(new GlobalExceptionFilter())
4. ValidationPipe global
No main.ts:
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // strip campos não declarados no DTO
forbidNonWhitelisted: true,
transform: true, // auto-conversão de tipos
transformOptions: { enableImplicitConversion: true },
}));
5. Health Checks
Atualizar src/modules/health/health.controller.ts:
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
private redis: MicroserviceHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database'),
() => this.redis.pingCheck('redis', {
transport: Transport.TCP,
options: { host: 'redis', port: 6379 },
}),
]);
}
}
6. Atualizar main.ts completo
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: loggerConfig,
});
app.setGlobalPrefix('api/v1');
app.enableCors();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true }));
app.useGlobalFilters(new GlobalExceptionFilter());
app.useGlobalInterceptors(new RequestLoggerInterceptor());
const port = process.env.PORT ?? 3000;
await app.listen(port);
console.log(`Application running on port ${port}`);
}
bootstrap();
Fluxo de commit e PR
git checkout main && git pull origin maingit checkout -b feat/e12-observability- Criar todos os arquivos
git add src/config/logger.config.ts src/common/interceptors src/common/filters src/modules/healthgit commit -m "feat: add structured logging, global exception filter and health checks"git push origin feat/e12-observabilitygh pr create --title "feat: E12 - Observability" --base main --body "## O que foi feito\n- Winston logger estruturado (console + arquivos)\n- RequestLoggerInterceptor: log de todas as requisições\n- GlobalExceptionFilter: respostas de erro padronizadas\n- ValidationPipe global com whitelist e transform\n- Health checks para DB e Redis\n\n## Depende de\nTodos os PRs anteriores mergeados"
Regras
- Nunca logar dados sensíveis (senhas, tokens, CPF)
- Stack trace apenas em erros 5xx
- Resposta de erro padronizada: { statusCode, timestamp, path, message }