- 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 <cursoragent@cursor.com>
70 lines
1.8 KiB
TypeScript
70 lines
1.8 KiB
TypeScript
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)
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
}
|