171 lines
4.3 KiB
TypeScript
171 lines
4.3 KiB
TypeScript
|
|
import { NextRequest, NextResponse } from 'next/server';
|
||
|
|
import { createLogger, generateRequestId, formatError } from './logger';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* API Route Handler type
|
||
|
|
*/
|
||
|
|
type ApiRouteHandler = (
|
||
|
|
request: NextRequest,
|
||
|
|
context: any
|
||
|
|
) => Promise<NextResponse> | NextResponse;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Wrap an API route handler with logging
|
||
|
|
* Automatically logs requests, responses, errors, and timing
|
||
|
|
*
|
||
|
|
* @param handler - The API route handler function
|
||
|
|
* @param operationName - Optional operation name for logging context
|
||
|
|
* @returns Wrapped handler with logging
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* ```typescript
|
||
|
|
* export const GET = withLogging(async (request) => {
|
||
|
|
* const data = await fetchData();
|
||
|
|
* return NextResponse.json(data);
|
||
|
|
* }, 'getAllProcesses');
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export function withLogging(
|
||
|
|
handler: ApiRouteHandler,
|
||
|
|
operationName?: string
|
||
|
|
): ApiRouteHandler {
|
||
|
|
return async (request: NextRequest, context: any) => {
|
||
|
|
const requestId = generateRequestId();
|
||
|
|
const startTime = Date.now();
|
||
|
|
|
||
|
|
// Extract request information
|
||
|
|
const method = request.method;
|
||
|
|
const url = request.url;
|
||
|
|
const pathname = new URL(url).pathname;
|
||
|
|
|
||
|
|
// Create logger with request context
|
||
|
|
const logger = createLogger({
|
||
|
|
requestId,
|
||
|
|
method,
|
||
|
|
path: pathname,
|
||
|
|
operation: operationName || pathname.split('/').pop(),
|
||
|
|
});
|
||
|
|
|
||
|
|
// Log incoming request
|
||
|
|
logger.info({
|
||
|
|
userAgent: request.headers.get('user-agent'),
|
||
|
|
remoteAddress: request.headers.get('x-forwarded-for') || 'unknown',
|
||
|
|
}, `Incoming ${method} request`);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Execute the handler
|
||
|
|
const response = await handler(request, context);
|
||
|
|
|
||
|
|
// Calculate duration
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
|
||
|
|
// Log successful response
|
||
|
|
logger.info({
|
||
|
|
statusCode: response.status,
|
||
|
|
duration,
|
||
|
|
}, `Request completed successfully in ${duration}ms`);
|
||
|
|
|
||
|
|
// Add request ID to response headers
|
||
|
|
response.headers.set('X-Request-ID', requestId);
|
||
|
|
|
||
|
|
return response;
|
||
|
|
} catch (error: unknown) {
|
||
|
|
// Calculate duration even for errors
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
|
||
|
|
// Format and log error
|
||
|
|
const errorInfo = formatError(error);
|
||
|
|
logger.error({
|
||
|
|
error: errorInfo,
|
||
|
|
duration,
|
||
|
|
}, `Request failed after ${duration}ms: ${errorInfo.message}`);
|
||
|
|
|
||
|
|
// Return error response
|
||
|
|
return NextResponse.json(
|
||
|
|
{
|
||
|
|
error: errorInfo.message,
|
||
|
|
requestId,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
status: 500,
|
||
|
|
headers: {
|
||
|
|
'X-Request-ID': requestId,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log performance timing for an operation within a request
|
||
|
|
*
|
||
|
|
* @param logger - Logger instance
|
||
|
|
* @param operation - Operation name
|
||
|
|
* @param fn - Async function to time
|
||
|
|
* @returns Result of the function
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* ```typescript
|
||
|
|
* const data = await logPerformance(logger, 'fetchData', async () => {
|
||
|
|
* return await supervisorClient.getAllProcessInfo();
|
||
|
|
* });
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export async function logPerformance<T>(
|
||
|
|
logger: ReturnType<typeof createLogger>,
|
||
|
|
operation: string,
|
||
|
|
fn: () => Promise<T>
|
||
|
|
): Promise<T> {
|
||
|
|
const startTime = Date.now();
|
||
|
|
|
||
|
|
try {
|
||
|
|
const result = await fn();
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
|
||
|
|
logger.debug({
|
||
|
|
operation,
|
||
|
|
duration,
|
||
|
|
}, `${operation} completed in ${duration}ms`);
|
||
|
|
|
||
|
|
return result;
|
||
|
|
} catch (error) {
|
||
|
|
const duration = Date.now() - startTime;
|
||
|
|
const errorInfo = formatError(error);
|
||
|
|
|
||
|
|
logger.error({
|
||
|
|
operation,
|
||
|
|
duration,
|
||
|
|
error: errorInfo,
|
||
|
|
}, `${operation} failed after ${duration}ms: ${errorInfo.message}`);
|
||
|
|
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a logger for a specific API operation
|
||
|
|
* Use this for manual logging within API routes
|
||
|
|
*
|
||
|
|
* @param request - Next.js request object
|
||
|
|
* @param operation - Operation name
|
||
|
|
* @returns Logger instance with request context
|
||
|
|
*
|
||
|
|
* @example
|
||
|
|
* ```typescript
|
||
|
|
* const logger = createApiLogger(request, 'startProcess');
|
||
|
|
* logger.info({ processName }, 'Starting process');
|
||
|
|
* ```
|
||
|
|
*/
|
||
|
|
export function createApiLogger(request: NextRequest, operation: string) {
|
||
|
|
const requestId = request.headers.get('x-request-id') || generateRequestId();
|
||
|
|
const pathname = new URL(request.url).pathname;
|
||
|
|
|
||
|
|
return createLogger({
|
||
|
|
requestId,
|
||
|
|
method: request.method,
|
||
|
|
path: pathname,
|
||
|
|
operation,
|
||
|
|
});
|
||
|
|
}
|