diff --git a/backend/src/index.ts b/backend/src/index.ts index 8f64fc8..60de879 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -6,7 +6,6 @@ import { serve } from '@hono/node-server'; import { Hono } from 'hono'; import { cors } from 'hono/cors'; -import { logger as honoLogger } from 'hono/logger'; import { prettyJSON } from 'hono/pretty-json'; import { serveStatic } from '@hono/node-server/serve-static'; import 'dotenv/config'; @@ -14,6 +13,7 @@ import { ProxyAgent, setGlobalDispatcher } from 'undici'; import { createLogger, generateTraceId } from './lib/logger'; import { prisma } from './lib/prisma'; import { traceMiddleware } from './lib/trace-middleware'; +import { httpLogger } from './lib/http-logger'; // Create a startup logger with shared traceId for all startup logs const startupTraceId = generateTraceId(); @@ -235,7 +235,7 @@ const app = new Hono(); // Middleware // Trace middleware must be first to capture traceId for all subsequent logging app.use('*', traceMiddleware()); -app.use('*', honoLogger()); +app.use('*', httpLogger()); app.use('*', prettyJSON()); // CORS must be before all routes diff --git a/backend/src/lib/http-logger.ts b/backend/src/lib/http-logger.ts new file mode 100644 index 0000000..d549cdf --- /dev/null +++ b/backend/src/lib/http-logger.ts @@ -0,0 +1,63 @@ +/** + * HTTP Request Logger Middleware + * + * Provides structured JSON logging for HTTP requests using Pino. + * Replaces Hono's default text logger for production compatibility. + * + * Output format: + * {"level":"info","time":1735368000,"service":"m3w-backend","region":"jp", + * "source":"http.request","col1":"http","col2":"GET","method":"GET", + * "path":"/api/health","status":200,"duration":12,"msg":"GET /api/health 200 12ms"} + */ + +import type { Context, Next } from 'hono'; +import { logger } from './logger'; + +/** + * HTTP logger middleware using Pino + * + * Logs incoming requests and outgoing responses with: + * - Method, path, status code + * - Response duration in milliseconds + * - Structured fields for Loki/Grafana filtering + * + * @returns Hono middleware function + */ +export function httpLogger() { + return async (c: Context, next: Next) => { + const start = Date.now(); + const method = c.req.method; + const path = c.req.path; + + // Get traceId from context (set by traceMiddleware) + const traceId = c.get('traceId') as string | undefined; + + await next(); + + const duration = Date.now() - start; + const status = c.res.status; + + // Build log entry with structured fields + const logEntry = { + source: 'http.request', + col1: 'http', + col2: method, + method, + path, + status, + duration, + ...(traceId && { traceId }), + }; + + // Log at appropriate level based on status code + const message = `${method} ${path} ${status} ${duration}ms`; + + if (status >= 500) { + logger.error(logEntry, message); + } else if (status >= 400) { + logger.warn(logEntry, message); + } else { + logger.info(logEntry, message); + } + }; +}