Merge squash develop into master (incl. #89 log API requests debug)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d23f3c9f4f
commit
11aa66feff
@ -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
|
||||
|
||||
69
backend/src/common/interceptors/log-request.interceptor.ts
Normal file
69
backend/src/common/interceptors/log-request.interceptor.ts
Normal file
@ -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<string, unknown> = {};
|
||||
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<unknown> {
|
||||
if (!this.enabled) return next.handle();
|
||||
|
||||
const http = context.switchToHttp();
|
||||
const req = http.getRequest<Request>();
|
||||
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)
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user