Files
supervisor-ui/lib/utils/api-logger.ts
Sebastian Krüger b252a0b3bf
All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 1m50s
feat: implement comprehensive logging infrastructure (Phases 1-3)
Added production-ready logging using Pino with structured JSON output,
pretty printing in development, and automatic sensitive data redaction.

## Phase 1: Core Logger Setup
- Installed pino, pino-http, and pino-pretty dependencies
- Created logger utility (lib/utils/logger.ts):
  - Environment-based log levels (debug in dev, info in prod)
  - Pretty printing with colors in development
  - JSON structured logs in production
  - Sensitive data redaction (passwords, tokens, auth headers)
  - Custom serializers for errors and requests
  - Helper functions for child loggers and timing
- Added LOG_LEVEL environment variable to .env.example
- Configured Next.js for Turbopack with external pino packages

## Phase 2: API Request Logging
- Created API logger wrapper (lib/utils/api-logger.ts):
  - withLogging() HOF for wrapping API route handlers
  - Automatic request/response logging with timing
  - Correlation ID generation (X-Request-ID header)
  - Error catching and structured error responses
  - logPerformance() helper for timing operations
  - createApiLogger() for manual logging in routes

## Phase 3: Supervisor Client Logging
- Updated lib/supervisor/client.ts:
  - Added logger instance to SupervisorClient class
  - Comprehensive XML-RPC call logging (method, params, duration)
  - Error logging with full context and stack traces
  - Success logging with result size tracking
  - DEBUG level logs for all XML-RPC operations
  - Constructor logging for client initialization

## Configuration Changes
- Updated next.config.ts for Turbopack compatibility
- Added serverComponentsExternalPackages for pino
- Removed null-loader workaround (not needed)

## Features Implemented
 Request correlation IDs for tracing
 Performance timing for all operations
 Sensitive data redaction (passwords, auth)
 Environment-based log formatting
 Structured JSON logs for production
 Pretty colored logs for development
 Error serialization with stack traces
 Ready for log aggregation (stdout/JSON)

## Next Steps (Phases 4-7)
- Update ~30 API routes with logging
- Add React Query/hooks error logging
- Implement client-side error boundary
- Add documentation and testing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-23 20:44:46 +01:00

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,
});
}