@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
233 lines • 9.32 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var RequestWatcherInterceptor_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestWatcherInterceptor = void 0;
const common_1 = require("@nestjs/common");
const operators_1 = require("rxjs/operators");
const request_watcher_service_1 = require("./request-watcher.service");
let RequestWatcherInterceptor = RequestWatcherInterceptor_1 = class RequestWatcherInterceptor {
constructor(requestWatcher) {
this.requestWatcher = requestWatcher;
this.logger = new common_1.Logger(RequestWatcherInterceptor_1.name);
}
intercept(context, next) {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
if (!this.shouldTrackRequest(request)) {
return next.handle();
}
const requestContext = this.createRequestContext(request);
return next.handle().pipe((0, operators_1.tap)(data => {
const responseContext = this.createResponseContext(response, data, requestContext);
this.trackRequest(requestContext, responseContext, null);
}), (0, operators_1.catchError)(error => {
const responseContext = this.createResponseContext(response, null, requestContext);
this.trackRequest(requestContext, responseContext, error);
throw error;
}));
}
shouldTrackRequest(request) {
const excludedPaths = this.requestWatcher.getExcludedPaths();
const path = request.path;
if (excludedPaths.some(excluded => path.startsWith(excluded))) {
return false;
}
return this.requestWatcher.shouldSampleRequest(request);
}
createRequestContext(request) {
const startTime = Date.now();
const id = this.generateRequestId();
return {
id,
startTime,
method: request.method,
url: request.originalUrl || request.url,
headers: this.sanitizeHeaders(request.headers),
query: this.sanitizeQuery(request.query),
body: this.sanitizeBody(request.body),
userAgent: request.get('user-agent') || '',
ip: this.getClientIp(request),
sessionId: this.extractSessionId(request),
userId: this.extractUserId(request),
traceId: this.extractTraceId(request)
};
}
createResponseContext(response, data, requestContext) {
const endTime = Date.now();
const duration = endTime - requestContext.startTime;
return {
statusCode: response.statusCode,
headers: this.sanitizeHeaders(response.getHeaders()),
body: this.sanitizeResponseBody(data, response.statusCode),
size: this.calculateResponseSize(data, response),
endTime,
duration
};
}
trackRequest(requestContext, responseContext, error) {
try {
this.requestWatcher.trackRequest(requestContext, responseContext, error);
}
catch (trackingError) {
this.logger.error('Failed to track request:', trackingError);
}
}
sanitizeHeaders(headers) {
const sanitized = {};
const sensitiveHeaders = [
'authorization',
'cookie',
'x-api-key',
'x-auth-token',
'x-access-token',
'x-csrf-token'
];
for (const [key, value] of Object.entries(headers)) {
const lowerKey = key.toLowerCase();
if (sensitiveHeaders.includes(lowerKey)) {
sanitized[key] = '[REDACTED]';
}
else {
sanitized[key] = Array.isArray(value) ? value.join(', ') : String(value);
}
}
return sanitized;
}
sanitizeQuery(query) {
if (!query || typeof query !== 'object') {
return {};
}
const sanitized = {};
const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth'];
for (const [key, value] of Object.entries(query)) {
const lowerKey = key.toLowerCase();
if (sensitiveKeys.some(sensitive => lowerKey.includes(sensitive))) {
sanitized[key] = '[REDACTED]';
}
else {
sanitized[key] = value;
}
}
return sanitized;
}
sanitizeBody(body) {
if (!body) {
return null;
}
if (this.requestWatcher.shouldMaskBody(body)) {
return '[MASKED]';
}
if (typeof body === 'object' && body !== null) {
const sanitized = Array.isArray(body) ? [] : {};
const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth', 'credit', 'ssn'];
for (const [key, value] of Object.entries(body)) {
const lowerKey = key.toLowerCase();
if (sensitiveKeys.some(sensitive => lowerKey.includes(sensitive))) {
sanitized[key] = '[REDACTED]';
}
else {
sanitized[key] = typeof value === 'object' ? this.sanitizeBody(value) : value;
}
}
return sanitized;
}
return body;
}
sanitizeResponseBody(data, statusCode) {
if (statusCode >= 200 && statusCode < 300) {
return this.requestWatcher.shouldLogSuccessfulResponseBodies() ? data : null;
}
if (statusCode >= 400) {
return this.sanitizeBody(data);
}
return data;
}
calculateResponseSize(data, response) {
if (data && typeof data === 'object') {
try {
return JSON.stringify(data).length;
}
catch {
return 0;
}
}
if (typeof data === 'string') {
return data.length;
}
const contentLength = response.get('content-length');
return contentLength ? parseInt(contentLength, 10) : 0;
}
getClientIp(request) {
return (request.ip ||
request.headers['x-forwarded-for'] ||
request.headers['x-real-ip'] ||
request.connection?.remoteAddress ||
'unknown');
}
extractSessionId(request) {
const sessionCookie = request.cookies?.sessionId;
if (sessionCookie) {
return sessionCookie;
}
const sessionHeader = request.headers['x-session-id'];
if (sessionHeader) {
return Array.isArray(sessionHeader) ? sessionHeader[0] : sessionHeader;
}
const authHeader = request.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
const token = authHeader.substring(7);
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
return payload.sessionId || payload.jti;
}
catch {
}
}
return undefined;
}
extractUserId(request) {
const user = request.user;
if (user) {
return user.id || user.userId || user.sub;
}
const authHeader = request.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
const token = authHeader.substring(7);
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
return payload.userId || payload.sub || payload.id;
}
catch {
}
}
return undefined;
}
extractTraceId(request) {
const traceHeader = request.headers['x-trace-id'] || request.headers['x-request-id'];
if (traceHeader) {
return Array.isArray(traceHeader) ? traceHeader[0] : traceHeader;
}
return this.generateTraceId();
}
generateRequestId() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
generateTraceId() {
return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
};
exports.RequestWatcherInterceptor = RequestWatcherInterceptor;
exports.RequestWatcherInterceptor = RequestWatcherInterceptor = RequestWatcherInterceptor_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [request_watcher_service_1.RequestWatcherService])
], RequestWatcherInterceptor);
//# sourceMappingURL=request-watcher.interceptor.js.map