hikma-engine
Version:
Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents
250 lines (249 loc) • 9.28 kB
JavaScript
"use strict";
/**
* @file Correlation and request tracking middleware.
* Enhances request ID generation and provides correlation logging throughout the request lifecycle.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.correlationMiddleware = correlationMiddleware;
exports.timingMiddleware = timingMiddleware;
exports.getRequestLogger = getRequestLogger;
exports.requestLoggingMiddleware = requestLoggingMiddleware;
exports.performanceMonitoringMiddleware = performanceMonitoringMiddleware;
const crypto_1 = require("crypto");
const logger_1 = require("../../utils/logger");
const logger = (0, logger_1.getLogger)('CorrelationMiddleware');
/**
* Generates a unique request ID with timestamp and random components.
*/
function generateRequestId() {
const timestamp = Date.now().toString(36);
const random = (0, crypto_1.randomUUID)().split('-')[0];
return `req_${timestamp}_${random}`;
}
/**
* Generates a session ID from various request headers for tracking user sessions.
*/
function generateSessionId(req) {
const userAgent = req.get('User-Agent') || '';
const ip = req.ip || req.connection.remoteAddress || 'unknown';
// Create a simple hash-like session identifier
const sessionData = `${ip}_${userAgent}`;
const hash = Buffer.from(sessionData).toString('base64').replace(/[^a-zA-Z0-9]/g, '').substring(0, 16);
return `sess_${hash}`;
}
/**
* Request correlation middleware that adds request ID and context tracking.
*/
function correlationMiddleware(req, res, next) {
const startTime = Date.now();
// Generate or use existing request ID
const requestId = req.headers['x-request-id'] || generateRequestId();
// Generate session ID for tracking user sessions
const sessionId = generateSessionId(req);
// Create request context
const context = {
requestId,
startTime,
ip: req.ip || req.connection.remoteAddress || 'unknown',
userAgent: req.get('User-Agent'),
method: req.method,
url: req.originalUrl || req.url,
path: req.path,
query: req.query,
sessionId,
};
// Attach context to request
req.context = context;
// Set request ID header for client and downstream services
req.headers['x-request-id'] = requestId;
res.setHeader('X-Request-ID', requestId);
res.setHeader('X-Session-ID', sessionId);
// Log request start
logger.info('Request started', {
requestId,
sessionId,
method: req.method,
url: req.originalUrl || req.url,
path: req.path,
ip: context.ip,
userAgent: context.userAgent,
query: Object.keys(req.query).length > 0 ? req.query : undefined,
contentLength: req.get('Content-Length'),
timestamp: new Date().toISOString(),
});
// Track request completion
const originalSend = res.send;
res.send = function (data) {
const duration = Date.now() - startTime;
const contentLength = Buffer.byteLength(data || '', 'utf8');
// Calculate high-precision timing if available
let hrDuration;
if (context.hrStartTime) {
const hrEndTime = process.hrtime.bigint();
hrDuration = Number(hrEndTime - context.hrStartTime) / 1_000_000; // Convert to milliseconds
}
logger.info('Request completed', {
requestId,
sessionId,
method: req.method,
url: req.originalUrl || req.url,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`,
contentLength: `${contentLength}B`,
ip: context.ip,
timestamp: new Date().toISOString(),
});
// Add performance headers (only if response hasn't been sent)
if (!res.headersSent) {
res.setHeader('X-Response-Time', `${duration}ms`);
res.setHeader('X-Content-Length', `${contentLength}B`);
if (hrDuration !== undefined) {
res.setHeader('X-Process-Time', `${hrDuration.toFixed(2)}ms`);
}
}
return originalSend.call(this, data);
};
// Track request errors
const originalJson = res.json;
res.json = function (data) {
const duration = Date.now() - startTime;
// Calculate high-precision timing if available
let hrDuration;
if (context.hrStartTime) {
const hrEndTime = process.hrtime.bigint();
hrDuration = Number(hrEndTime - context.hrStartTime) / 1_000_000; // Convert to milliseconds
}
// Log errors for non-success responses
if (res.statusCode >= 400) {
logger.warn('Request failed', {
requestId,
sessionId,
method: req.method,
url: req.originalUrl || req.url,
path: req.path,
statusCode: res.statusCode,
duration: `${duration}ms`,
error: data?.error,
ip: context.ip,
timestamp: new Date().toISOString(),
});
}
// Add performance headers (only if response hasn't been sent)
if (!res.headersSent) {
res.setHeader('X-Response-Time', `${duration}ms`);
if (hrDuration !== undefined) {
res.setHeader('X-Process-Time', `${hrDuration.toFixed(2)}ms`);
}
}
return originalJson.call(this, data);
};
next();
}
/**
* Middleware to add request timing information.
*/
function timingMiddleware(req, res, next) {
const startTime = process.hrtime.bigint();
// Store timing info in request context for later use
if (req.context) {
req.context.hrStartTime = startTime;
}
res.on('finish', () => {
const endTime = process.hrtime.bigint();
const duration = Number(endTime - startTime) / 1_000_000; // Convert to milliseconds
const requestId = req.context?.requestId || 'unknown';
logger.debug('Request timing', {
requestId,
method: req.method,
url: req.originalUrl || req.url,
statusCode: res.statusCode,
duration: `${duration.toFixed(2)}ms`,
timestamp: new Date().toISOString(),
});
// Note: Headers are set in correlationMiddleware before response is sent
// This event handler only logs timing information
});
next();
}
/**
* Creates a child logger with request context for correlation logging.
*/
function getRequestLogger(req) {
const context = req.context;
if (!context) {
return logger;
}
// Create a logger with request context
return {
debug: (message, meta) => logger.debug(message, {
requestId: context.requestId,
sessionId: context.sessionId,
...meta
}),
info: (message, meta) => logger.info(message, {
requestId: context.requestId,
sessionId: context.sessionId,
...meta
}),
warn: (message, meta) => logger.warn(message, {
requestId: context.requestId,
sessionId: context.sessionId,
...meta
}),
error: (message, meta) => logger.error(message, {
requestId: context.requestId,
sessionId: context.sessionId,
...meta
}),
};
}
/**
* Middleware to log request body for debugging (sanitized).
*/
function requestLoggingMiddleware(req, res, next) {
const requestLogger = getRequestLogger(req);
// Log request body for POST/PUT requests (sanitized)
if ((req.method === 'POST' || req.method === 'PUT') && req.body) {
const sanitizedBody = { ...req.body };
// Remove sensitive fields
const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth'];
sensitiveFields.forEach(field => {
if (sanitizedBody[field]) {
sanitizedBody[field] = '[REDACTED]';
}
});
requestLogger.debug('Request body received', {
method: req.method,
path: req.path,
bodySize: JSON.stringify(req.body).length,
body: Object.keys(sanitizedBody).length > 0 ? sanitizedBody : undefined,
});
}
next();
}
/**
* Performance monitoring middleware that tracks slow requests.
*/
function performanceMonitoringMiddleware(req, res, next) {
const slowRequestThreshold = parseInt(process.env.SLOW_REQUEST_THRESHOLD || '1000', 10); // 1 second default
res.on('finish', () => {
const context = req.context;
if (!context)
return;
const duration = Date.now() - context.startTime;
if (duration > slowRequestThreshold) {
const requestLogger = getRequestLogger(req);
requestLogger.warn('Slow request detected', {
method: req.method,
url: req.originalUrl || req.url,
path: req.path,
duration: `${duration}ms`,
threshold: `${slowRequestThreshold}ms`,
statusCode: res.statusCode,
query: context.query,
});
}
});
next();
}