marketing-post-generator-mcp
Version:
A powerful MCP server for AI-powered marketing blog post generation with Claude integration
181 lines • 6.66 kB
JavaScript
export class LoggingMiddleware {
logger;
config;
constructor(logger, config = {}) {
this.logger = logger;
this.config = {
skipPaths: ['/health', '/metrics', '/favicon.ico'],
logRequestBody: true,
logResponseBody: false,
logHeaders: true,
maxBodyLength: 10000,
sensitiveHeaders: ['authorization', 'cookie', 'x-api-key', 'x-auth-token'],
sensitiveBodyFields: ['password', 'token', 'secret', 'apiKey'],
...config,
};
}
createMiddleware() {
return (req, res, next) => {
const startTime = Date.now();
const requestId = req.get('X-Request-ID') || this.generateRequestId();
// Add request ID to request object for downstream usage
req.requestId = requestId;
// Skip logging for specified paths
if (this.shouldSkipLogging(req.path)) {
return next();
}
// Log request
this.logRequest(req, requestId);
// Capture response
const originalSend = res.send;
const originalJson = res.json;
let responseBody;
let responseCaptured = false;
res.send = function (body) {
if (!responseCaptured) {
responseBody = body;
responseCaptured = true;
}
return originalSend.call(this, body);
};
res.json = function (body) {
if (!responseCaptured) {
responseBody = body;
responseCaptured = true;
}
return originalJson.call(this, body);
};
// Log response when request finishes
res.on('finish', () => {
const responseTime = Date.now() - startTime;
this.logResponse(res, responseTime, requestId, responseBody);
});
next();
};
}
shouldSkipLogging(path) {
return this.config.skipPaths?.some((skipPath) => path.startsWith(skipPath)) || false;
}
generateRequestId() {
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
logRequest(req, requestId) {
const requestData = {
requestId,
method: req.method,
url: req.url,
ip: this.getClientIP(req),
timestamp: new Date().toISOString(),
};
const userAgent = req.get('User-Agent');
if (userAgent) {
requestData.userAgent = userAgent;
}
const contentLength = req.get('Content-Length');
if (contentLength) {
requestData.contentLength = parseInt(contentLength);
}
// Add headers if configured
if (this.config.logHeaders) {
requestData.headers = this.sanitizeHeaders(req.headers);
}
// Add body if configured
if (this.config.logRequestBody && req.body) {
requestData.body = this.sanitizeBody(req.body);
}
this.logger.info('HTTP Request', requestData);
}
logResponse(res, responseTime, requestId, responseBody) {
const responseData = {
requestId,
statusCode: res.statusCode,
responseTime,
timestamp: new Date().toISOString(),
};
const contentLength = res.get('Content-Length');
if (contentLength) {
responseData.contentLength = parseInt(contentLength);
}
// Add headers if configured
if (this.config.logHeaders) {
responseData.headers = this.sanitizeHeaders(res.getHeaders());
}
// Add body if configured
if (this.config.logResponseBody && responseBody) {
responseData.body = this.sanitizeBody(responseBody);
}
// Choose log level based on status code
const logLevel = this.getLogLevel(res.statusCode);
this.logger[logLevel]('HTTP Response', responseData);
}
getClientIP(req) {
return (req.get('X-Forwarded-For')?.split(',')[0] ||
req.get('X-Real-IP') ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
'unknown');
}
sanitizeHeaders(headers) {
const sanitized = {};
for (const [key, value] of Object.entries(headers)) {
const lowerKey = key.toLowerCase();
if (this.config.sensitiveHeaders?.includes(lowerKey)) {
sanitized[key] = '[REDACTED]';
}
else {
sanitized[key] = Array.isArray(value) ? value.join(', ') : String(value);
}
}
return sanitized;
}
sanitizeBody(body) {
if (!body)
return body;
// Handle string bodies
if (typeof body === 'string') {
if (body.length > (this.config.maxBodyLength || 10000)) {
// Ensure we don't break multi-byte characters
const maxLength = this.config.maxBodyLength || 10000;
const truncated = body.slice(0, maxLength);
// Remove any incomplete multi-byte sequence at the end
const cleanTruncated = truncated.replace(/[\uD800-\uDBFF]$/, '');
return cleanTruncated + '... [TRUNCATED]';
}
return body;
}
// Handle object bodies
if (typeof body === 'object') {
const sanitized = { ...body };
// Remove sensitive fields
this.config.sensitiveBodyFields?.forEach((field) => {
if (sanitized[field]) {
sanitized[field] = '[REDACTED]';
}
});
// Truncate large objects
const stringified = JSON.stringify(sanitized);
if (stringified.length > (this.config.maxBodyLength || 10000)) {
return stringified.substring(0, this.config.maxBodyLength) + '... [TRUNCATED]';
}
return sanitized;
}
return body;
}
getLogLevel(statusCode) {
if (statusCode >= 500) {
return 'error';
}
else if (statusCode >= 400) {
return 'warn';
}
else {
return 'info';
}
}
}
// Helper function to create logging middleware
export function createLoggingMiddleware(logger, config) {
const middleware = new LoggingMiddleware(logger, config);
return middleware.createMiddleware();
}
//# sourceMappingURL=LoggingMiddleware.js.map