Overview
Log all requests and responses with timing, status codes, and sensitive data masking.
Features
- Duration tracking
- Sensitive data masking
- Status-based log levels
- User context included
Installation
npx hanma add logging-interceptor --framework nest
Code
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from "@nestjs/common";
import { Observable, tap } from "rxjs";
interface LogContext {
method: string;
url: string;
ip: string;
userId?: string;
userAgent?: string;
duration: number;
statusCode: number;
}
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger("HTTP");
// Fields to mask in logs
private readonly sensitiveFields = [
"password",
"token",
"authorization",
"secret",
"apiKey",
"api_key",
"creditCard",
"ssn",
];
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const request = ctx.getRequest();
const response = ctx.getResponse();
const startTime = Date.now();
const { method, url, ip, body, query, headers } = request;
const userAgent = headers["user-agent"] || "";
const userId = request.user?.id;
// Log request (only in development or debug mode)
if (process.env.NODE_ENV === "development" || process.env.LOG_REQUESTS === "true") {
this.logger.debug(
`→ ${method} ${url}`,
this.maskSensitiveData({
body: Object.keys(body || {}).length > 0 ? body : undefined,
query: Object.keys(query || {}).length > 0 ? query : undefined,
userId,
})
);
}
return next.handle().pipe(
tap({
next: (data) => {
const duration = Date.now() - startTime;
const statusCode = response.statusCode;
const logContext: LogContext = {
method,
url,
ip: this.getClientIp(request),
userId,
userAgent,
duration,
statusCode,
};
this.logResponse(logContext, data);
},
error: (error) => {
const duration = Date.now() - startTime;
this.logger.error(
`✗ ${method} ${url} - ${error.status || 500} - ${duration}ms`,
{
error: error.message,
stack: process.env.NODE_ENV === "development" ? error.stack : undefined,
userId,
}
);
},
})
);
}
private logResponse(ctx: LogContext, data?: any): void {
const statusEmoji = ctx.statusCode < 400 ? "✓" : "✗";
const message = `${statusEmoji} ${ctx.method} ${ctx.url} - ${ctx.statusCode} - ${ctx.duration}ms`;
if (ctx.statusCode >= 500) {
this.logger.error(message, ctx);
} else if (ctx.statusCode >= 400) {
this.logger.warn(message, ctx);
} else {
this.logger.log(message);
}
}
private getClientIp(request: any): string {
return (
request.headers["x-forwarded-for"]?.split(",")[0] ||
request.ip ||
request.connection?.remoteAddress ||
"unknown"
);
}
private maskSensitiveData(obj: any): any {
if (!obj || typeof obj !== "object") return obj;
const masked = Array.isArray(obj) ? [...obj] : { ...obj };
for (const key of Object.keys(masked)) {
if (this.sensitiveFields.some((field) =>
key.toLowerCase().includes(field.toLowerCase())
)) {
masked[key] = "[REDACTED]";
} else if (typeof masked[key] === "object") {
masked[key] = this.maskSensitiveData(masked[key]);
}
}
return masked;
}
}
Usage
// Global usage
app.useGlobalInterceptors(new LoggingInterceptor());
Dependencies
No external dependencies required.
Frequently Asked Questions
How do I install Logging Interceptor?
Run the following command:
npx hanma add logging-interceptor --framework nestHow do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to
src/interceptors/logging.interceptor.ts.Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.