deleted a lot of the stuff
This commit is contained in:
parent
d22f7aafa0
commit
3e451558ac
173 changed files with 1313 additions and 30205 deletions
|
|
@ -1,10 +1,10 @@
|
|||
/**
|
||||
* Hono middleware for request logging
|
||||
* Comprehensive Hono middleware for request logging with Pino integration
|
||||
*/
|
||||
|
||||
import type { Context, Next } from 'hono';
|
||||
import { Logger } from './logger';
|
||||
import { generateCorrelationId, createTimer } from './utils';
|
||||
import { generateCorrelationId, createTimer, sanitizeMetadata, LogThrottle } from './utils';
|
||||
|
||||
export interface LoggingMiddlewareOptions {
|
||||
logger?: Logger;
|
||||
|
|
@ -14,10 +14,18 @@ export interface LoggingMiddlewareOptions {
|
|||
logRequestBody?: boolean;
|
||||
logResponseBody?: boolean;
|
||||
maxBodySize?: number;
|
||||
enablePerformanceMetrics?: boolean;
|
||||
enableThrottling?: boolean;
|
||||
throttleConfig?: {
|
||||
maxLogs?: number;
|
||||
windowMs?: number;
|
||||
};
|
||||
sensitiveHeaders?: string[];
|
||||
redactSensitiveData?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hono middleware for HTTP request/response logging
|
||||
* Hono middleware for HTTP request/response logging with enhanced features
|
||||
*/
|
||||
export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
|
||||
const {
|
||||
|
|
@ -26,11 +34,19 @@ export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
|
|||
skipSuccessfulRequests = false,
|
||||
logRequestBody = false,
|
||||
logResponseBody = false,
|
||||
maxBodySize = 1024
|
||||
maxBodySize = 1024,
|
||||
enablePerformanceMetrics = true,
|
||||
enableThrottling = false,
|
||||
throttleConfig = { maxLogs: 100, windowMs: 60000 },
|
||||
sensitiveHeaders = ['authorization', 'cookie', 'x-api-key', 'x-auth-token'],
|
||||
redactSensitiveData = true
|
||||
} = options;
|
||||
|
||||
// Create logger if not provided
|
||||
const logger = options.logger || new Logger(serviceName);
|
||||
|
||||
// Create throttle instance if enabled
|
||||
const throttle = enableThrottling ? new LogThrottle(throttleConfig.maxLogs, throttleConfig.windowMs) : null;
|
||||
|
||||
return async (c: Context, next: Next) => {
|
||||
const url = new URL(c.req.url);
|
||||
|
|
@ -39,7 +55,15 @@ export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
|
|||
// Skip certain paths
|
||||
if (skipPaths.some(skipPath => path.startsWith(skipPath))) {
|
||||
return next();
|
||||
} // Generate correlation ID
|
||||
}
|
||||
|
||||
// Check throttling
|
||||
const throttleKey = `${c.req.method}-${path}`;
|
||||
if (throttle && !throttle.shouldLog(throttleKey)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Generate correlation ID
|
||||
const correlationId = generateCorrelationId();
|
||||
// Store correlation ID in context for later use
|
||||
c.set('correlationId', correlationId);
|
||||
|
|
@ -47,7 +71,23 @@ export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
|
|||
c.header('x-correlation-id', correlationId);
|
||||
|
||||
// Start timer
|
||||
const timer = createTimer(`${c.req.method} ${path}`); // Extract request metadata
|
||||
const timer = createTimer(`${c.req.method} ${path}`);
|
||||
|
||||
// Extract request headers (sanitized)
|
||||
const rawHeaders = Object.fromEntries(
|
||||
Array.from(c.req.raw.headers.entries())
|
||||
);
|
||||
|
||||
const headers = redactSensitiveData
|
||||
? Object.fromEntries(
|
||||
Object.entries(rawHeaders).map(([key, value]) => [
|
||||
key,
|
||||
sensitiveHeaders.some(sensitive => key.toLowerCase().includes(sensitive.toLowerCase()))
|
||||
? '[REDACTED]'
|
||||
: value
|
||||
])
|
||||
)
|
||||
: rawHeaders; // Extract request metadata following LogMetadata.request structure
|
||||
const requestMetadata: any = {
|
||||
method: c.req.method,
|
||||
url: c.req.url,
|
||||
|
|
@ -55,9 +95,9 @@ export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
|
|||
userAgent: c.req.header('user-agent'),
|
||||
contentType: c.req.header('content-type'),
|
||||
contentLength: c.req.header('content-length'),
|
||||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
|
||||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown',
|
||||
headers: headers
|
||||
};
|
||||
|
||||
// Add request body if enabled
|
||||
if (logRequestBody) {
|
||||
try {
|
||||
|
|
@ -69,8 +109,9 @@ export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
|
|||
}
|
||||
} catch (error) {
|
||||
// Body might not be available or already consumed
|
||||
requestMetadata.bodyError = 'Unable to read request body';
|
||||
}
|
||||
} // Log request start
|
||||
} // Log request start with structured data
|
||||
logger.http('HTTP Request started', {
|
||||
method: c.req.method,
|
||||
url: c.req.url,
|
||||
|
|
@ -78,71 +119,103 @@ export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
|
|||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
|
||||
});
|
||||
|
||||
// Process request
|
||||
await next();
|
||||
let error: Error | null = null;
|
||||
let responseTime: number = 0;
|
||||
|
||||
// Calculate response time
|
||||
const timing = timer.end();
|
||||
|
||||
// Get response information
|
||||
const response = c.res;
|
||||
const status = response.status;
|
||||
|
||||
// Determine log level based on status code
|
||||
const isError = status >= 400;
|
||||
const isSuccess = status >= 200 && status < 300;
|
||||
|
||||
// Skip successful requests if configured
|
||||
if (skipSuccessfulRequests && isSuccess) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Process request
|
||||
await next();
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err : new Error(String(err));
|
||||
throw error; // Re-throw to maintain error handling flow
|
||||
} finally {
|
||||
// Calculate response time
|
||||
const timing = timer.end();
|
||||
responseTime = timing.duration;
|
||||
|
||||
// Get response information
|
||||
const response = c.res;
|
||||
const status = response.status;
|
||||
|
||||
// Determine log level based on status code
|
||||
const isError = status >= 400 || error !== null;
|
||||
const isSuccess = status >= 200 && status < 300;
|
||||
const isRedirect = status >= 300 && status < 400;
|
||||
|
||||
// Skip successful requests if configured
|
||||
if (skipSuccessfulRequests && isSuccess && !error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const responseMetadata: any = {
|
||||
correlationId,
|
||||
request: requestMetadata,
|
||||
response: {
|
||||
// Extract response headers (sanitized)
|
||||
const rawResponseHeaders = Object.fromEntries(
|
||||
Array.from(response.headers.entries())
|
||||
);
|
||||
|
||||
const responseHeaders = redactSensitiveData
|
||||
? Object.fromEntries(
|
||||
Object.entries(rawResponseHeaders).map(([key, value]) => [
|
||||
key,
|
||||
sensitiveHeaders.some(sensitive => key.toLowerCase().includes(sensitive.toLowerCase()))
|
||||
? '[REDACTED]'
|
||||
: value
|
||||
])
|
||||
)
|
||||
: rawResponseHeaders;
|
||||
|
||||
const responseMetadata: any = {
|
||||
statusCode: status,
|
||||
statusText: response.statusText,
|
||||
headers: responseHeaders,
|
||||
contentLength: response.headers.get('content-length'),
|
||||
contentType: response.headers.get('content-type')
|
||||
},
|
||||
performance: timing
|
||||
};
|
||||
};
|
||||
|
||||
// Add response body if enabled
|
||||
if (logResponseBody) {
|
||||
try {
|
||||
const responseBody = await response.clone().text();
|
||||
if (responseBody) {
|
||||
responseMetadata.response.body = responseBody.length > maxBodySize
|
||||
? responseBody.substring(0, maxBodySize) + '...[truncated]'
|
||||
: responseBody;
|
||||
// Add response body if enabled and not an error
|
||||
if (logResponseBody && !isError) {
|
||||
try {
|
||||
const responseBody = await response.clone().text();
|
||||
if (responseBody) {
|
||||
responseMetadata.body = responseBody.length > maxBodySize
|
||||
? responseBody.substring(0, maxBodySize) + '...[truncated]'
|
||||
: responseBody;
|
||||
}
|
||||
} catch (bodyError) {
|
||||
responseMetadata.bodyError = 'Unable to read response body';
|
||||
}
|
||||
} catch (error) {
|
||||
// Response body might not be available
|
||||
}
|
||||
} // Log based on status code
|
||||
if (isError) {
|
||||
logger.error('HTTP Request failed', undefined, {
|
||||
} // Performance metrics matching LogMetadata.performance structure
|
||||
const performanceMetrics = {
|
||||
operation: timing.operation,
|
||||
duration: timing.duration,
|
||||
startTime: enablePerformanceMetrics ? timing.startTime : undefined,
|
||||
endTime: enablePerformanceMetrics ? timing.endTime : undefined
|
||||
}; // Create comprehensive log entry
|
||||
const logEntry: any = {
|
||||
correlationId,
|
||||
request: requestMetadata,
|
||||
response: {
|
||||
response: responseMetadata,
|
||||
performance: performanceMetrics,
|
||||
type: error ? 'custom' : isError ? 'custom' : 'http_request'
|
||||
};
|
||||
|
||||
// Sanitize if needed
|
||||
const finalLogEntry = redactSensitiveData ? sanitizeMetadata(logEntry) : logEntry; // Log based on status code and errors
|
||||
if (error) {
|
||||
logger.error('HTTP Request error', error, finalLogEntry);
|
||||
} else if (isError) {
|
||||
logger.warn('HTTP Request failed', finalLogEntry);
|
||||
} else if (isRedirect) {
|
||||
logger.info('HTTP Request redirected', finalLogEntry);
|
||||
} else {
|
||||
logger.http('HTTP Request completed', {
|
||||
method: c.req.method,
|
||||
url: c.req.url,
|
||||
statusCode: status,
|
||||
statusText: response.statusText,
|
||||
contentLength: response.headers.get('content-length'),
|
||||
contentType: response.headers.get('content-type')
|
||||
},
|
||||
performance: timing
|
||||
});
|
||||
} else {
|
||||
logger.http('HTTP Request completed', {
|
||||
method: c.req.method,
|
||||
url: c.req.url,
|
||||
statusCode: status,
|
||||
responseTime: timing.duration,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
|
||||
});
|
||||
responseTime: timing.duration,
|
||||
userAgent: c.req.header('user-agent'),
|
||||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -198,3 +271,192 @@ export function createRequestLogger(c: Context, baseLogger: Logger): Logger {
|
|||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance monitoring middleware for specific operations
|
||||
*/
|
||||
export function performanceMiddleware(operationName?: string, logger?: Logger) {
|
||||
return async (c: Context, next: Next) => {
|
||||
const perfLogger = logger || new Logger('performance');
|
||||
const operation = operationName || `${c.req.method} ${new URL(c.req.url).pathname}`;
|
||||
const timer = createTimer(operation);
|
||||
|
||||
try {
|
||||
await next();
|
||||
|
||||
const timing = timer.end();
|
||||
perfLogger.info('Operation completed', {
|
||||
type: 'performance',
|
||||
performance: timing,
|
||||
correlationId: c.get('correlationId')
|
||||
});
|
||||
} catch (error) {
|
||||
const timing = timer.end();
|
||||
perfLogger.warn('Operation failed', {
|
||||
type: 'performance',
|
||||
performance: timing,
|
||||
correlationId: c.get('correlationId'),
|
||||
error: error instanceof Error ? {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
} : { message: String(error) }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Security event logging middleware
|
||||
*/
|
||||
export function securityMiddleware(logger?: Logger) {
|
||||
return async (c: Context, next: Next) => {
|
||||
const secLogger = logger || new Logger('security');
|
||||
const url = new URL(c.req.url);
|
||||
|
||||
// Log authentication attempts
|
||||
const authHeader = c.req.header('authorization');
|
||||
if (authHeader) {
|
||||
secLogger.info('Authentication attempt', {
|
||||
type: 'security_event',
|
||||
security: {
|
||||
type: 'authentication',
|
||||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown',
|
||||
resource: url.pathname,
|
||||
action: 'access_attempt'
|
||||
},
|
||||
correlationId: c.get('correlationId')
|
||||
});
|
||||
}
|
||||
|
||||
await next();
|
||||
|
||||
// Log access control
|
||||
const status = c.res.status;
|
||||
if (status === 401 || status === 403) {
|
||||
secLogger.warn('Access denied', {
|
||||
type: 'security_event',
|
||||
security: {
|
||||
type: 'authorization',
|
||||
result: 'failure',
|
||||
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown',
|
||||
resource: url.pathname,
|
||||
action: c.req.method,
|
||||
severity: status === 401 ? 'medium' : 'high'
|
||||
},
|
||||
correlationId: c.get('correlationId')
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Business event logging middleware for trading operations
|
||||
*/
|
||||
export function businessEventMiddleware(logger?: Logger) {
|
||||
return async (c: Context, next: Next) => {
|
||||
const bizLogger = logger || new Logger('business');
|
||||
const url = new URL(c.req.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Check if this is a business-critical endpoint
|
||||
const businessEndpoints = [
|
||||
'/api/orders',
|
||||
'/api/trades',
|
||||
'/api/portfolio',
|
||||
'/api/strategies',
|
||||
'/api/signals'
|
||||
];
|
||||
|
||||
const isBusinessEndpoint = businessEndpoints.some(endpoint => path.startsWith(endpoint));
|
||||
|
||||
if (isBusinessEndpoint) {
|
||||
const timer = createTimer(`business_${c.req.method}_${path}`);
|
||||
|
||||
try {
|
||||
await next();
|
||||
|
||||
const timing = timer.end();
|
||||
const status = c.res.status;
|
||||
|
||||
bizLogger.info('Business operation completed', {
|
||||
type: 'business_event',
|
||||
business: {
|
||||
type: 'trading_operation',
|
||||
action: c.req.method,
|
||||
result: status >= 200 && status < 300 ? 'success' : 'failure'
|
||||
},
|
||||
performance: timing,
|
||||
correlationId: c.get('correlationId')
|
||||
});
|
||||
} catch (error) {
|
||||
const timing = timer.end();
|
||||
|
||||
bizLogger.error('Business operation failed', error, {
|
||||
type: 'business_event',
|
||||
business: {
|
||||
type: 'trading_operation',
|
||||
action: c.req.method,
|
||||
result: 'failure'
|
||||
},
|
||||
performance: timing,
|
||||
correlationId: c.get('correlationId')
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
await next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprehensive logging middleware that combines all logging features
|
||||
*/
|
||||
export function comprehensiveLoggingMiddleware(options: LoggingMiddlewareOptions & {
|
||||
enableSecurity?: boolean;
|
||||
enableBusiness?: boolean;
|
||||
enablePerformance?: boolean;
|
||||
} = {}) {
|
||||
const {
|
||||
enableSecurity = true,
|
||||
enableBusiness = true,
|
||||
enablePerformance = true,
|
||||
...loggingOptions
|
||||
} = options;
|
||||
return async (c: Context, next: Next) => {
|
||||
const middlewares: Array<(c: Context, next: Next) => Promise<void>> = [];
|
||||
|
||||
// Add security middleware
|
||||
if (enableSecurity) {
|
||||
middlewares.push(securityMiddleware(options.logger));
|
||||
}
|
||||
|
||||
// Add performance middleware
|
||||
if (enablePerformance) {
|
||||
middlewares.push(performanceMiddleware(undefined, options.logger));
|
||||
}
|
||||
|
||||
// Add business event middleware
|
||||
if (enableBusiness) {
|
||||
middlewares.push(businessEventMiddleware(options.logger));
|
||||
}
|
||||
|
||||
// Add main logging middleware
|
||||
middlewares.push(loggingMiddleware(loggingOptions));
|
||||
|
||||
// Execute middleware chain
|
||||
let index = 0;
|
||||
async function dispatch(i: number): Promise<void> {
|
||||
if (i >= middlewares.length) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const middleware = middlewares[i];
|
||||
return middleware(c, () => dispatch(i + 1));
|
||||
}
|
||||
|
||||
return dispatch(0);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue