UNPKG

apitally

Version:

Simple API monitoring & analytics for REST APIs built with Express, Fastify, NestJS, AdonisJS, Hono, H3, Elysia, Hapi, and Koa.

1 lines 10.2 kB
{"version":3,"sources":["../../src/hono/middleware.ts"],"sourcesContent":["import { Context, Hono } from \"hono\";\nimport { MiddlewareHandler } from \"hono/types\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertHeaders } from \"../common/requestLogger.js\";\nimport { captureResponse, getResponseJson } from \"../common/response.js\";\nimport { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nimport { patchConsole, patchWinston } from \"../loggers/index.js\";\nimport { extractZodErrors, getAppInfo } from \"./utils.js\";\n\ndeclare module \"hono\" {\n interface ContextVariableMap {\n apitallyConsumer?: ApitallyConsumer | string;\n }\n}\n\nexport function useApitally(app: Hono, config: ApitallyConfig) {\n const client = new ApitallyClient(config);\n const middleware = getMiddleware(client);\n app.use(middleware);\n\n const setStartupData = (attempt: number = 1) => {\n const appInfo = getAppInfo(app, config.appVersion);\n if (appInfo.paths.length > 0 || attempt >= 10) {\n client.setStartupData(appInfo);\n client.startSync();\n } else {\n setTimeout(() => setStartupData(attempt + 1), 500);\n }\n };\n setTimeout(() => setStartupData(), 500);\n}\n\nfunction getMiddleware(client: ApitallyClient): MiddlewareHandler {\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n }\n\n return async (c, next) => {\n if (!client.isEnabled() || c.req.method.toUpperCase() === \"OPTIONS\") {\n await next();\n return;\n }\n\n await logsContext.run([], async () => {\n const timestamp = Date.now() / 1000;\n const startTime = performance.now();\n\n await next();\n\n const [newResponse, responsePromise] = captureResponse(c.res, {\n captureBody:\n (client.requestLogger.enabled &&\n client.requestLogger.config.logResponseBody &&\n client.requestLogger.isSupportedContentType(\n c.res.headers.get(\"content-type\"),\n )) ||\n (c.res.status === 400 &&\n c.res.headers.get(\"content-type\") === \"application/json\"),\n maxBodySize: client.requestLogger.maxBodySize,\n });\n c.res = newResponse;\n\n const statusCode = c.res.status;\n const responseHeaders = c.res.headers;\n\n responsePromise.then(async (capturedResponse) => {\n const responseTime = performance.now() - startTime;\n const requestSize = parseContentLength(c.req.header(\"content-length\"));\n const responseSize = capturedResponse.completed\n ? capturedResponse.size\n : undefined;\n\n const consumer = getConsumer(c);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: c.req.method,\n path: c.req.routePath,\n statusCode,\n responseTime,\n requestSize,\n responseSize,\n });\n\n if (statusCode === 400 && capturedResponse.body) {\n const responseJson = getResponseJson(capturedResponse.body);\n const validationErrors = extractZodErrors(responseJson);\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: c.req.method,\n path: c.req.routePath,\n ...error,\n });\n });\n }\n\n if (c.error) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: c.req.method,\n path: c.req.routePath,\n type: c.error.name,\n msg: c.error.message,\n traceback: c.error.stack || \"\",\n });\n }\n\n if (client.requestLogger.enabled) {\n let requestBody;\n const responseBody = capturedResponse.body;\n const requestContentType = c.req.header(\"content-type\");\n if (\n client.requestLogger.config.logRequestBody &&\n client.requestLogger.isSupportedContentType(requestContentType)\n ) {\n requestBody = Buffer.from(await c.req.arrayBuffer());\n }\n const logs = logsContext.getStore();\n client.requestLogger.logRequest(\n {\n timestamp,\n method: c.req.method,\n path: c.req.routePath,\n url: c.req.url,\n headers: convertHeaders(c.req.header()),\n size: requestSize,\n consumer: consumer?.identifier,\n body: requestBody,\n },\n {\n statusCode: statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(responseHeaders),\n size: responseSize,\n body: responseBody,\n },\n c.error,\n logs,\n );\n }\n });\n });\n };\n}\n\nexport function setConsumer(\n c: Context,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n c.set(\"apitallyConsumer\", consumer || undefined);\n}\n\nfunction getConsumer(c: Context) {\n const consumer = c.get(\"apitallyConsumer\");\n if (consumer) {\n return consumerFromStringOrObject(consumer);\n }\n return null;\n}\n"],"mappings":"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","names":["useApitally","app","config","client","ApitallyClient","middleware","getMiddleware","use","setStartupData","attempt","appInfo","getAppInfo","appVersion","paths","length","startSync","setTimeout","logsContext","AsyncLocalStorage","requestLogger","captureLogs","patchConsole","patchWinston","c","next","isEnabled","req","method","toUpperCase","run","timestamp","Date","now","startTime","performance","newResponse","responsePromise","captureResponse","res","captureBody","enabled","logResponseBody","isSupportedContentType","headers","get","status","maxBodySize","statusCode","responseHeaders","then","capturedResponse","responseTime","requestSize","parseContentLength","header","responseSize","completed","size","undefined","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","requestCounter","addRequest","identifier","path","routePath","body","responseJson","getResponseJson","validationErrors","extractZodErrors","forEach","error","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","message","traceback","stack","requestBody","responseBody","requestContentType","logRequestBody","Buffer","from","arrayBuffer","logs","getStore","logRequest","url","convertHeaders","setConsumer","set","consumerFromStringOrObject"]}