From 0386785f81b357537d2b647fbb70a0268f47ad4c Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Fri, 13 Feb 2026 11:15:41 +0100 Subject: [PATCH] feat(backend): log des appels API en mode debug (#89) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout LogRequestInterceptor (méthode, URL, query, body) - Activé via LOG_API_REQUESTS=true - Masquage des champs sensibles (password, smtp_password, token...) - Enregistrement global dans main.ts, doc dans .env.example Co-authored-by: Cursor --- backend/.env.example | 3 + .../interceptors/log-request.interceptor.ts | 69 +++++++++++++++++++ backend/src/main.ts | 11 +-- 3 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 backend/src/common/interceptors/log-request.interceptor.ts diff --git a/backend/.env.example b/backend/.env.example index 002d786..67081bd 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -21,3 +21,6 @@ JWT_EXPIRATION_TIME=7d # Environnement NODE_ENV=development + +# Log de chaque appel API (mode debug) — mettre à true pour tracer les requêtes front +# LOG_API_REQUESTS=true diff --git a/backend/src/common/interceptors/log-request.interceptor.ts b/backend/src/common/interceptors/log-request.interceptor.ts new file mode 100644 index 0000000..bfb87e0 --- /dev/null +++ b/backend/src/common/interceptors/log-request.interceptor.ts @@ -0,0 +1,69 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { Request } from 'express'; + +/** Clés à masquer dans les logs (corps de requête) */ +const SENSITIVE_KEYS = [ + 'password', + 'smtp_password', + 'token', + 'accessToken', + 'refreshToken', + 'secret', +]; + +function maskBody(body: unknown): unknown { + if (body === null || body === undefined) return body; + if (typeof body !== 'object') return body; + const out: Record = {}; + for (const [key, value] of Object.entries(body)) { + const lower = key.toLowerCase(); + const isSensitive = SENSITIVE_KEYS.some((s) => lower.includes(s)); + out[key] = isSensitive ? '***' : value; + } + return out; +} + +@Injectable() +export class LogRequestInterceptor implements NestInterceptor { + private readonly enabled: boolean; + + constructor() { + this.enabled = + process.env.LOG_API_REQUESTS === 'true' || + process.env.LOG_API_REQUESTS === '1'; + } + + intercept(context: ExecutionContext, next: CallHandler): Observable { + if (!this.enabled) return next.handle(); + + const http = context.switchToHttp(); + const req = http.getRequest(); + const { method, url, body, query } = req; + const hasBody = body && Object.keys(body).length > 0; + + const logLine = [ + `[API] ${method} ${url}`, + Object.keys(query || {}).length ? `query=${JSON.stringify(query)}` : '', + hasBody ? `body=${JSON.stringify(maskBody(body))}` : '', + ] + .filter(Boolean) + .join(' '); + + console.log(logLine); + + return next.handle().pipe( + tap({ + next: () => { + // Optionnel: log du statut en fin de requête (si besoin plus tard) + }, + }), + ); + } +} diff --git a/backend/src/main.ts b/backend/src/main.ts index 3943463..6c62287 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,17 +1,18 @@ -import { NestFactory, Reflector } from '@nestjs/core'; +import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ConfigService } from '@nestjs/config'; import { SwaggerModule } from '@nestjs/swagger/dist/swagger-module'; import { DocumentBuilder } from '@nestjs/swagger'; -import { AuthGuard } from './common/guards/auth.guard'; -import { JwtService } from '@nestjs/jwt'; -import { RolesGuard } from './common/guards/roles.guard'; import { ValidationPipe } from '@nestjs/common'; +import { LogRequestInterceptor } from './common/interceptors/log-request.interceptor'; async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: ['error', 'warn', 'log', 'debug', 'verbose'] }); - + + // Log de chaque appel API si LOG_API_REQUESTS=true (mode debug) + app.useGlobalInterceptors(new LogRequestInterceptor()); + // Configuration CORS pour autoriser les requêtes depuis localhost (dev) et production app.enableCors({ origin: true, // Autorise toutes les origines (dev) - à restreindre en prod