@hacksaw/hono-google-cloud-logging
Version:
Google Cloud Logging Middleware for Hono
131 lines (130 loc) • 4.61 kB
JavaScript
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));
}
});
};