added logger

This commit is contained in:
Bojan Kucera 2025-06-03 18:31:02 -04:00
parent dd27f3bf2c
commit 58ae897e90
13 changed files with 1493 additions and 12 deletions

View file

@ -0,0 +1,200 @@
/**
* Hono middleware for request logging
*/
import type { Context, Next } from 'hono';
import { Logger } from './logger';
import { generateCorrelationId, createTimer } from './utils';
export interface LoggingMiddlewareOptions {
logger?: Logger;
serviceName?: string;
skipPaths?: string[];
skipSuccessfulRequests?: boolean;
logRequestBody?: boolean;
logResponseBody?: boolean;
maxBodySize?: number;
}
/**
* Hono middleware for HTTP request/response logging
*/
export function loggingMiddleware(options: LoggingMiddlewareOptions = {}) {
const {
serviceName = 'unknown-service',
skipPaths = ['/health', '/metrics', '/favicon.ico'],
skipSuccessfulRequests = false,
logRequestBody = false,
logResponseBody = false,
maxBodySize = 1024
} = options;
// Create logger if not provided
const logger = options.logger || new Logger(serviceName);
return async (c: Context, next: Next) => {
const url = new URL(c.req.url);
const path = url.pathname;
// Skip certain paths
if (skipPaths.some(skipPath => path.startsWith(skipPath))) {
return next();
} // Generate correlation ID
const correlationId = generateCorrelationId();
// Store correlation ID in context for later use
c.set('correlationId', correlationId);
// Set correlation ID as response header
c.header('x-correlation-id', correlationId);
// Start timer
const timer = createTimer(`${c.req.method} ${path}`); // Extract request metadata
const requestMetadata: any = {
method: c.req.method,
url: c.req.url,
path: path,
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'
};
// Add request body if enabled
if (logRequestBody) {
try {
const body = await c.req.text();
if (body) {
requestMetadata.body = body.length > maxBodySize
? body.substring(0, maxBodySize) + '...[truncated]'
: body;
}
} catch (error) {
// Body might not be available or already consumed
}
} // Log request start
logger.http('HTTP Request started', {
method: c.req.method,
url: c.req.url,
userAgent: c.req.header('user-agent'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
});
// Process request
await next();
// 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;
}
const responseMetadata: any = {
correlationId,
request: requestMetadata,
response: {
statusCode: status,
statusText: response.statusText,
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;
}
} catch (error) {
// Response body might not be available
}
} // Log based on status code
if (isError) {
logger.error('HTTP Request failed', undefined, {
correlationId,
request: requestMetadata,
response: {
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'
});
}
};
}
/**
* Error logging middleware for Hono
*/
export function errorLoggingMiddleware(logger?: Logger) {
return async (c: Context, next: Next) => {
const errorLogger = logger || new Logger('error-handler');
try {
await next();
} catch (err) {
const correlationId = c.get('correlationId') || c.req.header('x-correlation-id');
const url = new URL(c.req.url);
const requestMetadata = {
method: c.req.method,
url: c.req.url,
path: url.pathname,
userAgent: c.req.header('user-agent'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
};
errorLogger.error('Unhandled HTTP error', err instanceof Error ? err : new Error(String(err)), {
correlationId,
request: requestMetadata,
response: {
statusCode: c.res.status
}
});
// Re-throw the error so Hono can handle it
throw err;
}
};
}
/**
* Create a child logger with request context for Hono
*/
export function createRequestLogger(c: Context, baseLogger: Logger): Logger {
const correlationId = c.get('correlationId') || c.req.header('x-correlation-id');
const url = new URL(c.req.url);
return baseLogger.child({
correlationId,
requestId: correlationId,
method: c.req.method,
path: url.pathname,
userAgent: c.req.header('user-agent'),
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
});
}