feat(api): foundation canon JCS — Pino+CLS+RFC9457+health+ping (Frente A)
Estabelece a fundação operacional de apps/api conforme STACK.md v2.2 e
CODING-RULES.md v2.0, substituindo o hello-world scaffolded.
- tracing.ts primeiro import (PGD-OBS-001) — stub OTel ativável por env
- EnvSchema Zod 4 com fail-fast (superRefine guarda prod) + EnvModule global
- nestjs-pino com redact LGPD (*.cpf|*.cardNumber|*.password|auth|cookie)
- main.ts hardenizado: helmet, CORS por env, compression, versionamento URI
/api/v1, graceful shutdown
- ProblemDetailsFilter global (RFC 9457 application/problem+json), Zod -> 422
- Health endpoints /api/v1/health/{live,ready} com memory.checkHeap(350MB)
(ready skeleton documenta K=3 LRU pool conforme PGD-OBS-003)
- WorkspaceModule via ClsModule.forRootAsync — requestId+workspaceId no CLS,
idGenerator alinhado com pino-http para mesmo UUID em header e body
- GET /api/v1/ping retornando workspaceId+requestId (alvo de smoke test e
futuro healthcheck do docker compose)
- ZodValidationPipe (nestjs-zod) como APP_PIPE global
- tsconfig.app.json target ES2023 (alinhado ao base)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
81
apps/api/src/app/logger/logger.module.ts
Normal file
81
apps/api/src/app/logger/logger.module.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LoggerModule as PinoLoggerModule } from 'nestjs-pino';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import type { Env } from '../config/env.schema';
|
||||
|
||||
// CODING-RULES §09 (PGD-OBS-002): redact LGPD agressivo.
|
||||
// Sempre cobrir: *.cpf, *.cardNumber, *.password, req.headers.authorization, req.headers.cookie.
|
||||
const REDACT_PATHS = [
|
||||
'*.cpf',
|
||||
'*.cardNumber',
|
||||
'*.password',
|
||||
'*.passwordHash',
|
||||
'*.token',
|
||||
'*.secret',
|
||||
'req.headers.authorization',
|
||||
'req.headers.cookie',
|
||||
'req.headers["set-cookie"]',
|
||||
'res.headers["set-cookie"]',
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PinoLoggerModule.forRootAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService<Env, true>) => {
|
||||
const isProd = config.get('NODE_ENV', { infer: true }) === 'production';
|
||||
const level = config.get('LOG_LEVEL', { infer: true });
|
||||
const serviceName = config.get('OTEL_SERVICE_NAME', { infer: true });
|
||||
|
||||
return {
|
||||
pinoHttp: {
|
||||
level,
|
||||
// pretty em dev; JSON estruturado em prod (Grafana LGTM consumirá).
|
||||
transport: isProd
|
||||
? undefined
|
||||
: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'SYS:HH:MM:ss.l',
|
||||
ignore: 'pid,hostname,req,res,responseTime',
|
||||
messageFormat: '{msg} {req.method} {req.url} {res.statusCode} ({responseTime}ms)',
|
||||
},
|
||||
},
|
||||
redact: {
|
||||
paths: REDACT_PATHS,
|
||||
censor: '[REDACTED]',
|
||||
},
|
||||
// Correlation ID: respeita header upstream ou gera UUID v4.
|
||||
genReqId: (req: IncomingMessage, res: ServerResponse) => {
|
||||
const headerVal =
|
||||
req.headers['x-request-id'] ??
|
||||
req.headers['x-correlation-id'] ??
|
||||
req.headers['traceparent'];
|
||||
const id = typeof headerVal === 'string' && headerVal.length > 0 ? headerVal : randomUUID();
|
||||
res.setHeader('x-request-id', id);
|
||||
return id;
|
||||
},
|
||||
customProps: () => ({ service: serviceName }),
|
||||
// Reduz ruído em health checks.
|
||||
customLogLevel: (_req, res, err) => {
|
||||
if (err || res.statusCode >= 500) return 'error';
|
||||
if (res.statusCode >= 400) return 'warn';
|
||||
return 'info';
|
||||
},
|
||||
autoLogging: {
|
||||
ignore: (req) => {
|
||||
const url = req.url ?? '';
|
||||
return url.startsWith('/api/v1/health');
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
exports: [PinoLoggerModule],
|
||||
})
|
||||
export class LoggerModule {}
|
||||
Reference in New Issue
Block a user