All checks were successful
Build and Push Docker Image to Gitea / build-and-push (push) Successful in 1m50s
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>
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,
|
|
});
|
|
}
|