UNPKG

@hacksaw/hono-google-cloud-logging

Version:

Google Cloud Logging Middleware for Hono

131 lines (130 loc) 4.61 kB
import { Logging } from "@google-cloud/logging"; import { createMiddleware } from "hono/factory"; import { logDevRequest } from "./dev/index.js"; /** * Creates a Hono middleware for Google Cloud Logging * * @param options Logger configuration options * @returns Hono middleware handler */ export const logger = (options = { name: "hono-requests" }) => { // Default options const { name, resource, labels, logRequestBody = false, logResponseBody = false, maxBodySize = 10240, // 10KB severity = "DEFAULT", dev = process.env.NODE_ENV === "development", projectId, } = options; // Initialize loggers based on mode let log; if (!dev) { const logging = new Logging({ projectId, }); // Create Log instance log = logging.log(name); } return createMiddleware(async (c, next) => { if (!c || !c.req) { console.error("Logger middleware received invalid context"); return next(); } const path = c.req.path; const method = c.req.method; const startTime = Date.now(); // Clone the request for logging purposes const requestClone = c.req.raw.clone(); let requestBody; if (logRequestBody) { try { const bodyText = await requestClone.text(); requestBody = bodyText.length > maxBodySize ? `${bodyText.substring(0, maxBodySize)}... [truncated]` : bodyText; } catch (error) { // Ignore errors reading request body } } // Execute the request await next(); const endTime = Date.now(); const duration = endTime - startTime; // Get response info if (!c.res) throw new Error("No response found in Hono context"); const status = c.res.status; // Capture headers const responseHeaders = {}; for (const [key, value] of c.res.headers.entries()) { responseHeaders[key] = value; } // Capture response body if enabled let responseBody; if (logResponseBody) { try { const resClone = c.res.clone(); const bodyText = await resClone.text(); responseBody = bodyText.length > maxBodySize ? `${bodyText.substring(0, maxBodySize)}... [truncated]` : bodyText; } catch (error) { // Ignore errors reading response body } } // Prepare log entry data const logData = { httpRequest: { requestMethod: method, requestUrl: c.req.url, status: status, userAgent: c.req.header("user-agent"), remoteIp: c.req.header("x-forwarded-for") || "unknown", latency: { seconds: Math.floor(duration / 1000), nanos: (duration % 1000) * 1000000, }, }, requestHeaders: Object.fromEntries(c.req.raw.headers.entries()), responseHeaders, duration: `${duration}ms`, }; // Add request body if available if (requestBody !== undefined) { logData.requestBody = requestBody; } // Add response body if available if (responseBody !== undefined) { logData.responseBody = responseBody; } // Check if we should log this request const shouldLogRequest = options.shouldLog ? options.shouldLog({ req: c.req.raw, res: c.res, status, duration, error: c.error, path, method, severity, }) : true; if (!shouldLogRequest) { // Skip logging if the predicate returns false return; } if (dev) { logDevRequest(method, path, status, duration, requestBody, responseBody, logData.requestHeaders, logData.responseHeaders, logRequestBody, logResponseBody, severity === "DEBUG"); } else if (log) { // Log to Google Cloud Logging const metadata = { severity, resource, labels, }; // Write log entry log.write(log.entry(metadata, logData)); } }); };