179 lines
4.8 KiB
JavaScript
179 lines
4.8 KiB
JavaScript
const logger = require('../utils/logger');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
/**
|
|
* Request logging middleware
|
|
* Logs all HTTP requests with timing and response information
|
|
*/
|
|
const requestLogger = (req, res, next) => {
|
|
// Generate unique request ID
|
|
req.requestId = uuidv4();
|
|
|
|
// Record start time
|
|
const startTime = Date.now();
|
|
|
|
// Log incoming request
|
|
logger.info('Incoming Request', {
|
|
requestId: req.requestId,
|
|
method: req.method,
|
|
url: req.originalUrl,
|
|
ip: req.ip || req.connection.remoteAddress,
|
|
userAgent: req.get('User-Agent'),
|
|
contentType: req.get('Content-Type'),
|
|
contentLength: req.get('Content-Length'),
|
|
referer: req.get('Referer'),
|
|
body: req.method !== 'GET' && req.body ? sanitizeBody(req.body) : undefined,
|
|
query: Object.keys(req.query).length > 0 ? req.query : undefined,
|
|
params: Object.keys(req.params).length > 0 ? req.params : undefined
|
|
});
|
|
|
|
// Override res.json to capture response data
|
|
const originalJson = res.json;
|
|
res.json = function(data) {
|
|
res.responseData = data;
|
|
return originalJson.call(this, data);
|
|
};
|
|
|
|
// Override res.send to capture response data
|
|
const originalSend = res.send;
|
|
res.send = function(data) {
|
|
if (!res.responseData) {
|
|
res.responseData = data;
|
|
}
|
|
return originalSend.call(this, data);
|
|
};
|
|
|
|
// Log response when request finishes
|
|
res.on('finish', () => {
|
|
const responseTime = Date.now() - startTime;
|
|
|
|
const logData = {
|
|
requestId: req.requestId,
|
|
method: req.method,
|
|
url: req.originalUrl,
|
|
statusCode: res.statusCode,
|
|
responseTime: `${responseTime}ms`,
|
|
contentLength: res.get('Content-Length') || 0,
|
|
ip: req.ip || req.connection.remoteAddress
|
|
};
|
|
|
|
// Add response data for errors or debug mode
|
|
if (res.statusCode >= 400 || process.env.LOG_LEVEL === 'debug') {
|
|
logData.responseData = sanitizeResponseData(res.responseData);
|
|
}
|
|
|
|
// Log based on status code
|
|
if (res.statusCode >= 500) {
|
|
logger.error('Request Completed with Server Error', logData);
|
|
} else if (res.statusCode >= 400) {
|
|
logger.warn('Request Completed with Client Error', logData);
|
|
} else {
|
|
logger.http('Request Completed Successfully', logData);
|
|
}
|
|
|
|
// Log slow requests
|
|
if (responseTime > 1000) {
|
|
logger.warn('Slow Request Detected', {
|
|
...logData,
|
|
threshold: '1000ms'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Log request errors
|
|
res.on('error', (error) => {
|
|
const responseTime = Date.now() - startTime;
|
|
|
|
logger.error('Request Error', {
|
|
requestId: req.requestId,
|
|
method: req.method,
|
|
url: req.originalUrl,
|
|
error: error.message,
|
|
responseTime: `${responseTime}ms`,
|
|
ip: req.ip || req.connection.remoteAddress
|
|
});
|
|
});
|
|
|
|
next();
|
|
};
|
|
|
|
/**
|
|
* Sanitize request body to remove sensitive information
|
|
*/
|
|
const sanitizeBody = (body) => {
|
|
if (!body || typeof body !== 'object') {
|
|
return body;
|
|
}
|
|
|
|
const sensitiveFields = ['password', 'token', 'secret', 'key', 'auth'];
|
|
const sanitized = { ...body };
|
|
|
|
Object.keys(sanitized).forEach(key => {
|
|
if (sensitiveFields.some(field => key.toLowerCase().includes(field))) {
|
|
sanitized[key] = '[REDACTED]';
|
|
}
|
|
});
|
|
|
|
return sanitized;
|
|
};
|
|
|
|
/**
|
|
* Sanitize response data to remove sensitive information
|
|
*/
|
|
const sanitizeResponseData = (data) => {
|
|
if (!data) return data;
|
|
|
|
try {
|
|
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
|
|
if (typeof parsed === 'object' && parsed !== null) {
|
|
const sanitized = { ...parsed };
|
|
|
|
// Remove sensitive fields from response
|
|
const sensitiveFields = ['password', 'token', 'secret', 'key'];
|
|
|
|
const sanitizeObject = (obj) => {
|
|
if (Array.isArray(obj)) {
|
|
return obj.map(item => sanitizeObject(item));
|
|
} else if (obj && typeof obj === 'object') {
|
|
const result = {};
|
|
Object.keys(obj).forEach(key => {
|
|
if (sensitiveFields.some(field => key.toLowerCase().includes(field))) {
|
|
result[key] = '[REDACTED]';
|
|
} else {
|
|
result[key] = sanitizeObject(obj[key]);
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
return sanitizeObject(sanitized);
|
|
}
|
|
|
|
return parsed;
|
|
} catch (error) {
|
|
return '[UNPARSEABLE_RESPONSE]';
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Health check endpoint logger
|
|
* Reduces noise from health check requests
|
|
*/
|
|
const healthCheckLogger = (req, res, next) => {
|
|
// Skip detailed logging for health checks
|
|
if (req.originalUrl === '/health' || req.originalUrl === '/api/health') {
|
|
return next();
|
|
}
|
|
|
|
return requestLogger(req, res, next);
|
|
};
|
|
|
|
module.exports = {
|
|
requestLogger,
|
|
healthCheckLogger,
|
|
sanitizeBody,
|
|
sanitizeResponseData
|
|
}; |