apitally 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/adonisjs/index.cjs +3 -0
  2. package/dist/adonisjs/index.cjs.map +1 -1
  3. package/dist/adonisjs/index.d.cts +2 -0
  4. package/dist/adonisjs/index.d.ts +2 -0
  5. package/dist/adonisjs/index.js +2 -0
  6. package/dist/adonisjs/index.js.map +1 -1
  7. package/dist/adonisjs/middleware.cjs +6 -2
  8. package/dist/adonisjs/middleware.cjs.map +1 -1
  9. package/dist/adonisjs/middleware.d.cts +2 -0
  10. package/dist/adonisjs/middleware.d.ts +2 -0
  11. package/dist/adonisjs/middleware.js +6 -2
  12. package/dist/adonisjs/middleware.js.map +1 -1
  13. package/dist/adonisjs/provider.d.cts +2 -0
  14. package/dist/adonisjs/provider.d.ts +2 -0
  15. package/dist/adonisjs/stubs/config/apitally.stub +1 -0
  16. package/dist/common/client.cjs +4 -0
  17. package/dist/common/client.cjs.map +1 -1
  18. package/dist/common/client.d.cts +3 -0
  19. package/dist/common/client.d.ts +3 -0
  20. package/dist/common/client.js +4 -0
  21. package/dist/common/client.js.map +1 -1
  22. package/dist/common/consumerRegistry.d.cts +2 -0
  23. package/dist/common/consumerRegistry.d.ts +2 -0
  24. package/dist/common/requestCounter.d.cts +2 -0
  25. package/dist/common/requestCounter.d.ts +2 -0
  26. package/dist/common/requestLogger.cjs +11 -2
  27. package/dist/common/requestLogger.cjs.map +1 -1
  28. package/dist/common/requestLogger.d.cts +6 -1
  29. package/dist/common/requestLogger.d.ts +6 -1
  30. package/dist/common/requestLogger.js +11 -2
  31. package/dist/common/requestLogger.js.map +1 -1
  32. package/dist/common/serverErrorCounter.d.cts +2 -0
  33. package/dist/common/serverErrorCounter.d.ts +2 -0
  34. package/dist/common/spanCollector.cjs +217 -0
  35. package/dist/common/spanCollector.cjs.map +1 -0
  36. package/dist/common/spanCollector.d.cts +50 -0
  37. package/dist/common/spanCollector.d.ts +50 -0
  38. package/dist/common/spanCollector.js +194 -0
  39. package/dist/common/spanCollector.js.map +1 -0
  40. package/dist/common/types.d.cts +2 -0
  41. package/dist/common/types.d.ts +2 -0
  42. package/dist/common/validationErrorCounter.d.cts +2 -0
  43. package/dist/common/validationErrorCounter.d.ts +2 -0
  44. package/dist/elysia/index.cjs +3 -0
  45. package/dist/elysia/index.cjs.map +1 -1
  46. package/dist/elysia/index.d.cts +2 -0
  47. package/dist/elysia/index.d.ts +2 -0
  48. package/dist/elysia/index.js +2 -0
  49. package/dist/elysia/index.js.map +1 -1
  50. package/dist/elysia/plugin.cjs +9 -1
  51. package/dist/elysia/plugin.cjs.map +1 -1
  52. package/dist/elysia/plugin.d.cts +4 -0
  53. package/dist/elysia/plugin.d.ts +4 -0
  54. package/dist/elysia/plugin.js +9 -1
  55. package/dist/elysia/plugin.js.map +1 -1
  56. package/dist/elysia/utils.d.cts +2 -0
  57. package/dist/elysia/utils.d.ts +2 -0
  58. package/dist/express/index.cjs +3 -0
  59. package/dist/express/index.cjs.map +1 -1
  60. package/dist/express/index.d.cts +2 -0
  61. package/dist/express/index.d.ts +2 -0
  62. package/dist/express/index.js +2 -0
  63. package/dist/express/index.js.map +1 -1
  64. package/dist/express/middleware.cjs +85 -77
  65. package/dist/express/middleware.cjs.map +1 -1
  66. package/dist/express/middleware.d.cts +2 -0
  67. package/dist/express/middleware.d.ts +2 -0
  68. package/dist/express/middleware.js +85 -77
  69. package/dist/express/middleware.js.map +1 -1
  70. package/dist/fastify/index.cjs +3 -0
  71. package/dist/fastify/index.cjs.map +1 -1
  72. package/dist/fastify/index.d.cts +2 -0
  73. package/dist/fastify/index.d.ts +2 -0
  74. package/dist/fastify/index.js +2 -0
  75. package/dist/fastify/index.js.map +1 -1
  76. package/dist/fastify/plugin.cjs +14 -5
  77. package/dist/fastify/plugin.cjs.map +1 -1
  78. package/dist/fastify/plugin.d.cts +4 -0
  79. package/dist/fastify/plugin.d.ts +4 -0
  80. package/dist/fastify/plugin.js +14 -5
  81. package/dist/fastify/plugin.js.map +1 -1
  82. package/dist/h3/index.cjs +3 -0
  83. package/dist/h3/index.cjs.map +1 -1
  84. package/dist/h3/index.d.cts +2 -0
  85. package/dist/h3/index.d.ts +2 -0
  86. package/dist/h3/index.js +2 -0
  87. package/dist/h3/index.js.map +1 -1
  88. package/dist/h3/plugin.cjs +13 -5
  89. package/dist/h3/plugin.cjs.map +1 -1
  90. package/dist/h3/plugin.d.cts +4 -0
  91. package/dist/h3/plugin.d.ts +4 -0
  92. package/dist/h3/plugin.js +13 -5
  93. package/dist/h3/plugin.js.map +1 -1
  94. package/dist/hapi/index.cjs +3 -0
  95. package/dist/hapi/index.cjs.map +1 -1
  96. package/dist/hapi/index.d.cts +2 -0
  97. package/dist/hapi/index.d.ts +2 -0
  98. package/dist/hapi/index.js +2 -0
  99. package/dist/hapi/index.js.map +1 -1
  100. package/dist/hapi/plugin.cjs +10 -3
  101. package/dist/hapi/plugin.cjs.map +1 -1
  102. package/dist/hapi/plugin.d.cts +4 -0
  103. package/dist/hapi/plugin.d.ts +4 -0
  104. package/dist/hapi/plugin.js +10 -3
  105. package/dist/hapi/plugin.js.map +1 -1
  106. package/dist/hapi/utils.d.cts +2 -0
  107. package/dist/hapi/utils.d.ts +2 -0
  108. package/dist/hono/index.cjs +3 -0
  109. package/dist/hono/index.cjs.map +1 -1
  110. package/dist/hono/index.d.cts +2 -0
  111. package/dist/hono/index.d.ts +2 -0
  112. package/dist/hono/index.js +2 -0
  113. package/dist/hono/index.js.map +1 -1
  114. package/dist/hono/middleware.cjs +6 -2
  115. package/dist/hono/middleware.cjs.map +1 -1
  116. package/dist/hono/middleware.d.cts +2 -0
  117. package/dist/hono/middleware.d.ts +2 -0
  118. package/dist/hono/middleware.js +6 -2
  119. package/dist/hono/middleware.js.map +1 -1
  120. package/dist/hono/utils.d.cts +2 -0
  121. package/dist/hono/utils.d.ts +2 -0
  122. package/dist/koa/index.cjs +3 -0
  123. package/dist/koa/index.cjs.map +1 -1
  124. package/dist/koa/index.d.cts +2 -0
  125. package/dist/koa/index.d.ts +2 -0
  126. package/dist/koa/index.js +2 -0
  127. package/dist/koa/index.js.map +1 -1
  128. package/dist/koa/middleware.cjs +8 -4
  129. package/dist/koa/middleware.cjs.map +1 -1
  130. package/dist/koa/middleware.d.cts +2 -0
  131. package/dist/koa/middleware.d.ts +2 -0
  132. package/dist/koa/middleware.js +8 -4
  133. package/dist/koa/middleware.js.map +1 -1
  134. package/dist/loggers/console.d.cts +4 -0
  135. package/dist/loggers/console.d.ts +4 -0
  136. package/dist/loggers/hapi.d.cts +4 -0
  137. package/dist/loggers/hapi.d.ts +4 -0
  138. package/dist/loggers/index.d.cts +4 -0
  139. package/dist/loggers/index.d.ts +4 -0
  140. package/dist/loggers/nestjs.d.cts +4 -0
  141. package/dist/loggers/nestjs.d.ts +4 -0
  142. package/dist/loggers/pino.d.cts +4 -0
  143. package/dist/loggers/pino.d.ts +4 -0
  144. package/dist/loggers/winston.d.cts +4 -0
  145. package/dist/loggers/winston.d.ts +4 -0
  146. package/dist/nestjs/index.cjs +3 -0
  147. package/dist/nestjs/index.cjs.map +1 -1
  148. package/dist/nestjs/index.d.cts +2 -0
  149. package/dist/nestjs/index.d.ts +2 -0
  150. package/dist/nestjs/index.js +2 -0
  151. package/dist/nestjs/index.js.map +1 -1
  152. package/package.json +5 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fastify/plugin.ts"],"sourcesContent":["import type {\n FastifyError,\n FastifyPluginAsync,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { AsyncLocalStorage, AsyncResource } from \"node:async_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n PathInfo,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\n\nconst LOGS_SYMBOL = Symbol(\"apitally.logs\");\nconst ASYNC_RESOURCE_SYMBOL = Symbol(\"apitally.logsContextAsyncResource\");\n\ndeclare module \"fastify\" {\n interface FastifyReply {\n payload: any;\n serverError?: FastifyError;\n }\n\n interface FastifyRequest {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n\n [LOGS_SYMBOL]?: LogRecord[];\n [ASYNC_RESOURCE_SYMBOL]?: AsyncResource;\n }\n}\n\nconst apitallyPlugin: FastifyPluginAsync<ApitallyConfig> = async (\n fastify,\n config,\n) => {\n const client = new ApitallyClient(config);\n const routes: PathInfo[] = [];\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchPinoLogger(fastify.log, logsContext);\n patchNestLogger(logsContext);\n }\n\n fastify.decorateRequest(\"apitallyConsumer\", null);\n fastify.decorateRequest(\"consumerIdentifier\", null); // For backwards compatibility\n fastify.decorateReply(\"payload\", null);\n\n fastify.addHook(\"onRoute\", (routeOptions) => {\n const methods = Array.isArray(routeOptions.method)\n ? routeOptions.method\n : [routeOptions.method];\n methods.forEach((method) => {\n if (![\"HEAD\", \"OPTIONS\"].includes(method.toUpperCase())) {\n routes.push({\n method: method.toUpperCase(),\n path: routeOptions.url,\n });\n }\n });\n });\n\n fastify.addHook(\"onReady\", () => {\n client.setStartupData(getAppInfo(routes, config.appVersion));\n client.startSync();\n });\n\n fastify.addHook(\"onClose\", async () => {\n await client.handleShutdown();\n });\n\n // Establish logs context for each request\n fastify.addHook(\"onRequest\", (request, reply, done) => {\n if (client.isEnabled()) {\n const logs: LogRecord[] = [];\n request[LOGS_SYMBOL] = logs;\n logsContext.run(logs, () => {\n const asyncResource = new AsyncResource(\"ApitallyLogsContext\");\n request[ASYNC_RESOURCE_SYMBOL] = asyncResource;\n asyncResource.runInAsyncScope(done, request.raw);\n });\n } else {\n done();\n }\n });\n\n // Restore async context for later hooks and route handlers\n fastify.addHook(\"preValidation\", (request, reply, done) => {\n if (client.isEnabled() && request[ASYNC_RESOURCE_SYMBOL]) {\n const asyncResource = request[ASYNC_RESOURCE_SYMBOL];\n asyncResource.runInAsyncScope(done, request.raw);\n } else {\n done();\n }\n });\n\n fastify.addHook(\"onSend\", (request, reply, payload: any, done) => {\n const contentType = reply.getHeader(\"content-type\") as string | undefined;\n if (client.requestLogger.isSupportedContentType(contentType)) {\n reply.payload = payload;\n }\n done();\n });\n\n fastify.addHook(\"onError\", (request, reply, error, done) => {\n if (!error.statusCode || error.statusCode === 500) {\n reply.serverError = error;\n }\n done();\n });\n\n fastify.addHook(\"onResponse\", (request, reply, done) => {\n if (client.isEnabled() && request.method.toUpperCase() !== \"OPTIONS\") {\n // Get path from routeOptions if available (from v4), otherwise fallback to deprecated routerPath\n const consumer = getConsumer(request);\n const path =\n \"routeOptions\" in request\n ? (request as any).routeOptions.url\n : (request as any).routerPath;\n const requestSize = parseContentLength(request.headers[\"content-length\"]);\n const responseSize = parseContentLength(\n reply.getHeader(\"content-length\"),\n );\n const responseTime = getResponseTime(reply);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: request.method,\n path,\n statusCode: reply.statusCode,\n responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (reply.statusCode === 400 || reply.statusCode === 422) &&\n reply.payload\n ) {\n try {\n const parsedPayload = JSON.parse(reply.payload);\n const validationErrors: ValidationError[] = [];\n\n if (\n (!parsedPayload.code ||\n parsedPayload.code === \"FST_ERR_VALIDATION\") &&\n typeof parsedPayload.message === \"string\"\n ) {\n validationErrors.push(...extractAjvErrors(parsedPayload.message));\n } else if (Array.isArray(parsedPayload.message)) {\n validationErrors.push(\n ...extractNestValidationErrors(parsedPayload.message),\n );\n }\n\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n ...error,\n });\n });\n } catch (error) {} // eslint-disable-line no-empty\n }\n\n if (reply.statusCode === 500 && reply.serverError) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n type: reply.serverError.name,\n msg: reply.serverError.message,\n traceback: reply.serverError.stack || \"\",\n });\n }\n\n if (client.requestLogger.enabled) {\n const logs = request[LOGS_SYMBOL];\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: request.method,\n path,\n url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,\n headers: convertHeaders(request.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(request.body, request.headers[\"content-type\"]),\n },\n {\n statusCode: reply.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(reply.getHeaders()),\n size: responseSize,\n body: convertBody(\n reply.payload,\n reply.getHeader(\"content-type\")?.toString(),\n ),\n },\n reply.serverError,\n logs,\n );\n }\n }\n\n done();\n });\n};\n\nfunction getAppInfo(routes: PathInfo[], appVersion?: string) {\n const versions = [[\"nodejs\", process.version.replace(/^v/, \"\")]];\n const fastifyVersion = getPackageVersion(\"fastify\");\n const pinoVersion = getPackageVersion(\"pino\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (fastifyVersion) {\n versions.push([\"fastify\", fastifyVersion]);\n }\n if (pinoVersion) {\n versions.push([\"pino\", pinoVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: routes,\n versions: Object.fromEntries(versions),\n client: \"js:fastify\",\n };\n}\n\nexport function setConsumer(\n request: FastifyRequest,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n request.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(request: FastifyRequest) {\n if (request.apitallyConsumer) {\n return consumerFromStringOrObject(request.apitallyConsumer);\n } else if (request.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(request.consumerIdentifier);\n }\n return null;\n}\n\nfunction getResponseTime(reply: FastifyReply) {\n if (reply.elapsedTime !== undefined) {\n return reply.elapsedTime;\n } else if ((reply as any).getResponseTime !== undefined) {\n return (reply as any).getResponseTime();\n }\n return 0;\n}\n\nfunction extractAjvErrors(message: string): ValidationError[] {\n try {\n const regex =\n /(?<=^|, )((?:headers|params|query|querystring|body)[/.][^ ]+)(?= )/g;\n const matches: { match: string; index: number }[] = [];\n let match: RegExpExecArray | null;\n while ((match = regex.exec(message)) !== null) {\n matches.push({ match: match[0], index: match.index });\n }\n\n return matches.map((m, i) => {\n const endIndex =\n i + 1 < matches.length ? matches[i + 1].index - 2 : message.length;\n const matchSplit = m.match.split(/[/.]/);\n if (matchSplit[0] === \"querystring\") {\n matchSplit[0] = \"query\";\n }\n return {\n loc: matchSplit.join(\".\"),\n msg: message.substring(m.index, endIndex),\n type: \"\",\n };\n });\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(message: any[]): ValidationError[] {\n try {\n return message\n .filter((msg: any) => typeof msg === \"string\")\n .map((msg: any) => ({\n loc: \"\",\n msg,\n type: \"\",\n }));\n } catch (error) {\n return [];\n }\n}\n\nexport { apitallyPlugin };\n\nexport default fp(apitallyPlugin, {\n name: \"apitally\",\n});\n"],"mappings":"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","names":["LOGS_SYMBOL","Symbol","ASYNC_RESOURCE_SYMBOL","apitallyPlugin","fastify","config","client","ApitallyClient","routes","logsContext","AsyncLocalStorage","requestLogger","captureLogs","patchConsole","patchWinston","patchPinoLogger","log","patchNestLogger","decorateRequest","decorateReply","addHook","routeOptions","methods","Array","isArray","method","forEach","includes","toUpperCase","push","path","url","setStartupData","getAppInfo","appVersion","startSync","handleShutdown","request","reply","done","isEnabled","logs","run","asyncResource","AsyncResource","runInAsyncScope","raw","payload","contentType","getHeader","isSupportedContentType","error","statusCode","serverError","consumer","getConsumer","routerPath","requestSize","parseContentLength","headers","responseSize","responseTime","getResponseTime","consumerRegistry","addOrUpdateConsumer","requestCounter","addRequest","identifier","parsedPayload","JSON","parse","validationErrors","code","message","extractAjvErrors","extractNestValidationErrors","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","traceback","stack","enabled","logRequest","timestamp","Date","now","protocol","host","hostname","originalUrl","convertHeaders","size","body","convertBody","getHeaders","toString","versions","process","version","replace","fastifyVersion","getPackageVersion","pinoVersion","nestjsVersion","apitallyVersion","paths","Object","fromEntries","setConsumer","apitallyConsumer","undefined","consumerFromStringOrObject","consumerIdentifier","emitWarning","elapsedTime","regex","matches","match","exec","index","map","m","i","endIndex","length","matchSplit","split","loc","join","substring","filter","fp"]}
1
+ {"version":3,"sources":["../../src/fastify/plugin.ts"],"sourcesContent":["import type {\n FastifyError,\n FastifyPluginAsync,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { AsyncLocalStorage, AsyncResource } from \"node:async_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport type { SpanHandle } from \"../common/spanCollector.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n PathInfo,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\n\nconst LOGS_SYMBOL = Symbol(\"apitally.logs\");\nconst ASYNC_RESOURCE_SYMBOL = Symbol(\"apitally.asyncResource\");\nconst SPAN_HANDLE_SYMBOL = Symbol(\"apitally.spanHandle\");\n\ndeclare module \"fastify\" {\n interface FastifyReply {\n payload: any;\n serverError?: FastifyError;\n }\n\n interface FastifyRequest {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n\n [LOGS_SYMBOL]?: LogRecord[];\n [ASYNC_RESOURCE_SYMBOL]?: AsyncResource;\n [SPAN_HANDLE_SYMBOL]?: SpanHandle;\n }\n}\n\nconst apitallyPlugin: FastifyPluginAsync<ApitallyConfig> = async (\n fastify,\n config,\n) => {\n const client = new ApitallyClient(config);\n const routes: PathInfo[] = [];\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchPinoLogger(fastify.log, logsContext);\n patchNestLogger(logsContext);\n }\n\n fastify.decorateRequest(\"apitallyConsumer\", null);\n fastify.decorateRequest(\"consumerIdentifier\", null); // For backwards compatibility\n fastify.decorateReply(\"payload\", null);\n\n fastify.addHook(\"onRoute\", (routeOptions) => {\n const methods = Array.isArray(routeOptions.method)\n ? routeOptions.method\n : [routeOptions.method];\n methods.forEach((method) => {\n if (![\"HEAD\", \"OPTIONS\"].includes(method.toUpperCase())) {\n routes.push({\n method: method.toUpperCase(),\n path: routeOptions.url,\n });\n }\n });\n });\n\n fastify.addHook(\"onReady\", () => {\n client.setStartupData(getAppInfo(routes, config.appVersion));\n client.startSync();\n });\n\n fastify.addHook(\"onClose\", async () => {\n await client.handleShutdown();\n });\n\n // Establish logs context and start span for each request\n fastify.addHook(\"onRequest\", (request, reply, done) => {\n if (client.isEnabled()) {\n const logs: LogRecord[] = [];\n request[LOGS_SYMBOL] = logs;\n logsContext.run(logs, () => {\n const spanHandle = client.spanCollector.startSpan();\n request[SPAN_HANDLE_SYMBOL] = spanHandle;\n spanHandle.runInContext(() => {\n const asyncResource = new AsyncResource(\"ApitallyContext\");\n request[ASYNC_RESOURCE_SYMBOL] = asyncResource;\n asyncResource.runInAsyncScope(done, request.raw);\n });\n });\n } else {\n done();\n }\n });\n\n // Restore async context for later hooks and route handlers\n fastify.addHook(\"preValidation\", (request, reply, done) => {\n if (client.isEnabled() && request[ASYNC_RESOURCE_SYMBOL]) {\n const asyncResource = request[ASYNC_RESOURCE_SYMBOL];\n asyncResource.runInAsyncScope(done, request.raw);\n } else {\n done();\n }\n });\n\n fastify.addHook(\"onSend\", (request, reply, payload: any, done) => {\n const contentType = reply.getHeader(\"content-type\") as string | undefined;\n if (client.requestLogger.isSupportedContentType(contentType)) {\n reply.payload = payload;\n }\n done();\n });\n\n fastify.addHook(\"onError\", (request, reply, error, done) => {\n if (!error.statusCode || error.statusCode === 500) {\n reply.serverError = error;\n }\n done();\n });\n\n fastify.addHook(\"onResponse\", (request, reply, done) => {\n if (client.isEnabled() && request.method.toUpperCase() !== \"OPTIONS\") {\n // Get path from routeOptions if available (from v4), otherwise fallback to deprecated routerPath\n const consumer = getConsumer(request);\n const path =\n \"routeOptions\" in request\n ? (request as any).routeOptions.url\n : (request as any).routerPath;\n const requestSize = parseContentLength(request.headers[\"content-length\"]);\n const responseSize = parseContentLength(\n reply.getHeader(\"content-length\"),\n );\n const responseTime = getResponseTime(reply);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: request.method,\n path,\n statusCode: reply.statusCode,\n responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (reply.statusCode === 400 || reply.statusCode === 422) &&\n reply.payload\n ) {\n try {\n const parsedPayload = JSON.parse(reply.payload);\n const validationErrors: ValidationError[] = [];\n\n if (\n (!parsedPayload.code ||\n parsedPayload.code === \"FST_ERR_VALIDATION\") &&\n typeof parsedPayload.message === \"string\"\n ) {\n validationErrors.push(...extractAjvErrors(parsedPayload.message));\n } else if (Array.isArray(parsedPayload.message)) {\n validationErrors.push(\n ...extractNestValidationErrors(parsedPayload.message),\n );\n }\n\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n ...error,\n });\n });\n } catch (error) {} // eslint-disable-line no-empty\n }\n\n if (reply.statusCode === 500 && reply.serverError) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n type: reply.serverError.name,\n msg: reply.serverError.message,\n traceback: reply.serverError.stack || \"\",\n });\n }\n\n const spanHandle = request[SPAN_HANDLE_SYMBOL];\n spanHandle?.setName(`${request.method} ${path}`);\n const spans = spanHandle?.end();\n const traceId = spanHandle?.traceId;\n\n if (client.requestLogger.enabled) {\n const logs = request[LOGS_SYMBOL];\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: request.method,\n path,\n url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,\n headers: convertHeaders(request.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(request.body, request.headers[\"content-type\"]),\n },\n {\n statusCode: reply.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(reply.getHeaders()),\n size: responseSize,\n body: convertBody(\n reply.payload,\n reply.getHeader(\"content-type\")?.toString(),\n ),\n },\n reply.serverError,\n logs,\n spans,\n traceId,\n );\n }\n }\n\n done();\n });\n};\n\nfunction getAppInfo(routes: PathInfo[], appVersion?: string) {\n const versions = [[\"nodejs\", process.version.replace(/^v/, \"\")]];\n const fastifyVersion = getPackageVersion(\"fastify\");\n const pinoVersion = getPackageVersion(\"pino\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (fastifyVersion) {\n versions.push([\"fastify\", fastifyVersion]);\n }\n if (pinoVersion) {\n versions.push([\"pino\", pinoVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: routes,\n versions: Object.fromEntries(versions),\n client: \"js:fastify\",\n };\n}\n\nexport function setConsumer(\n request: FastifyRequest,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n request.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(request: FastifyRequest) {\n if (request.apitallyConsumer) {\n return consumerFromStringOrObject(request.apitallyConsumer);\n } else if (request.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(request.consumerIdentifier);\n }\n return null;\n}\n\nfunction getResponseTime(reply: FastifyReply) {\n if (reply.elapsedTime !== undefined) {\n return reply.elapsedTime;\n } else if ((reply as any).getResponseTime !== undefined) {\n return (reply as any).getResponseTime();\n }\n return 0;\n}\n\nfunction extractAjvErrors(message: string): ValidationError[] {\n try {\n const regex =\n /(?<=^|, )((?:headers|params|query|querystring|body)[/.][^ ]+)(?= )/g;\n const matches: { match: string; index: number }[] = [];\n let match: RegExpExecArray | null;\n while ((match = regex.exec(message)) !== null) {\n matches.push({ match: match[0], index: match.index });\n }\n\n return matches.map((m, i) => {\n const endIndex =\n i + 1 < matches.length ? matches[i + 1].index - 2 : message.length;\n const matchSplit = m.match.split(/[/.]/);\n if (matchSplit[0] === \"querystring\") {\n matchSplit[0] = \"query\";\n }\n return {\n loc: matchSplit.join(\".\"),\n msg: message.substring(m.index, endIndex),\n type: \"\",\n };\n });\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(message: any[]): ValidationError[] {\n try {\n return message\n .filter((msg: any) => typeof msg === \"string\")\n .map((msg: any) => ({\n loc: \"\",\n msg,\n type: \"\",\n }));\n } catch (error) {\n return [];\n }\n}\n\nexport { apitallyPlugin };\n\nexport default fp(apitallyPlugin, {\n name: \"apitally\",\n});\n"],"mappings":"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","names":["LOGS_SYMBOL","Symbol","ASYNC_RESOURCE_SYMBOL","SPAN_HANDLE_SYMBOL","apitallyPlugin","fastify","config","client","ApitallyClient","routes","logsContext","AsyncLocalStorage","requestLogger","captureLogs","patchConsole","patchWinston","patchPinoLogger","log","patchNestLogger","decorateRequest","decorateReply","addHook","routeOptions","methods","Array","isArray","method","forEach","includes","toUpperCase","push","path","url","setStartupData","getAppInfo","appVersion","startSync","handleShutdown","request","reply","done","isEnabled","logs","run","spanHandle","spanCollector","startSpan","runInContext","asyncResource","AsyncResource","runInAsyncScope","raw","payload","contentType","getHeader","isSupportedContentType","error","statusCode","serverError","consumer","getConsumer","routerPath","requestSize","parseContentLength","headers","responseSize","responseTime","getResponseTime","consumerRegistry","addOrUpdateConsumer","requestCounter","addRequest","identifier","parsedPayload","JSON","parse","validationErrors","code","message","extractAjvErrors","extractNestValidationErrors","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","traceback","stack","setName","spans","end","traceId","enabled","logRequest","timestamp","Date","now","protocol","host","hostname","originalUrl","convertHeaders","size","body","convertBody","getHeaders","toString","versions","process","version","replace","fastifyVersion","getPackageVersion","pinoVersion","nestjsVersion","apitallyVersion","paths","Object","fromEntries","setConsumer","apitallyConsumer","undefined","consumerFromStringOrObject","consumerIdentifier","emitWarning","elapsedTime","regex","matches","match","exec","index","map","m","i","endIndex","length","matchSplit","split","loc","join","substring","filter","fp"]}
@@ -1,7 +1,9 @@
1
+ import { SpanHandle } from '../common/spanCollector.cjs';
1
2
  import { ApitallyConsumer, ApitallyConfig } from '../common/types.cjs';
2
3
  import { FastifyError, FastifyPluginAsync, FastifyRequest } from 'fastify';
3
4
  import { AsyncResource } from 'node:async_hooks';
4
5
  import { LogRecord } from '../common/requestLogger.cjs';
6
+ import '@opentelemetry/sdk-trace-base';
5
7
  import '../common/logging.cjs';
6
8
  import 'winston';
7
9
  import 'node:buffer';
@@ -10,6 +12,7 @@ import '../common/tempGzipFile.cjs';
10
12
 
11
13
  declare const LOGS_SYMBOL: unique symbol;
12
14
  declare const ASYNC_RESOURCE_SYMBOL: unique symbol;
15
+ declare const SPAN_HANDLE_SYMBOL: unique symbol;
13
16
  declare module "fastify" {
14
17
  interface FastifyReply {
15
18
  payload: any;
@@ -20,6 +23,7 @@ declare module "fastify" {
20
23
  consumerIdentifier?: ApitallyConsumer | string | null;
21
24
  [LOGS_SYMBOL]?: LogRecord[];
22
25
  [ASYNC_RESOURCE_SYMBOL]?: AsyncResource;
26
+ [SPAN_HANDLE_SYMBOL]?: SpanHandle;
23
27
  }
24
28
  }
25
29
  declare const apitallyPlugin: FastifyPluginAsync<ApitallyConfig>;
@@ -1,7 +1,9 @@
1
+ import { SpanHandle } from '../common/spanCollector.js';
1
2
  import { ApitallyConsumer, ApitallyConfig } from '../common/types.js';
2
3
  import { FastifyError, FastifyPluginAsync, FastifyRequest } from 'fastify';
3
4
  import { AsyncResource } from 'node:async_hooks';
4
5
  import { LogRecord } from '../common/requestLogger.js';
6
+ import '@opentelemetry/sdk-trace-base';
5
7
  import '../common/logging.js';
6
8
  import 'winston';
7
9
  import 'node:buffer';
@@ -10,6 +12,7 @@ import '../common/tempGzipFile.js';
10
12
 
11
13
  declare const LOGS_SYMBOL: unique symbol;
12
14
  declare const ASYNC_RESOURCE_SYMBOL: unique symbol;
15
+ declare const SPAN_HANDLE_SYMBOL: unique symbol;
13
16
  declare module "fastify" {
14
17
  interface FastifyReply {
15
18
  payload: any;
@@ -20,6 +23,7 @@ declare module "fastify" {
20
23
  consumerIdentifier?: ApitallyConsumer | string | null;
21
24
  [LOGS_SYMBOL]?: LogRecord[];
22
25
  [ASYNC_RESOURCE_SYMBOL]?: AsyncResource;
26
+ [SPAN_HANDLE_SYMBOL]?: SpanHandle;
23
27
  }
24
28
  }
25
29
  declare const apitallyPlugin: FastifyPluginAsync<ApitallyConfig>;
@@ -9,7 +9,8 @@ import { getPackageVersion } from "../common/packageVersions.js";
9
9
  import { convertBody, convertHeaders } from "../common/requestLogger.js";
10
10
  import { patchConsole, patchNestLogger, patchPinoLogger, patchWinston } from "../loggers/index.js";
11
11
  const LOGS_SYMBOL = /* @__PURE__ */ Symbol("apitally.logs");
12
- const ASYNC_RESOURCE_SYMBOL = /* @__PURE__ */ Symbol("apitally.logsContextAsyncResource");
12
+ const ASYNC_RESOURCE_SYMBOL = /* @__PURE__ */ Symbol("apitally.asyncResource");
13
+ const SPAN_HANDLE_SYMBOL = /* @__PURE__ */ Symbol("apitally.spanHandle");
13
14
  const apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
14
15
  const client = new ApitallyClient(config);
15
16
  const routes = [];
@@ -51,9 +52,13 @@ const apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
51
52
  const logs = [];
52
53
  request[LOGS_SYMBOL] = logs;
53
54
  logsContext.run(logs, () => {
54
- const asyncResource = new AsyncResource("ApitallyLogsContext");
55
- request[ASYNC_RESOURCE_SYMBOL] = asyncResource;
56
- asyncResource.runInAsyncScope(done, request.raw);
55
+ const spanHandle = client.spanCollector.startSpan();
56
+ request[SPAN_HANDLE_SYMBOL] = spanHandle;
57
+ spanHandle.runInContext(() => {
58
+ const asyncResource = new AsyncResource("ApitallyContext");
59
+ request[ASYNC_RESOURCE_SYMBOL] = asyncResource;
60
+ asyncResource.runInAsyncScope(done, request.raw);
61
+ });
57
62
  });
58
63
  } else {
59
64
  done();
@@ -128,6 +133,10 @@ const apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
128
133
  traceback: reply.serverError.stack || ""
129
134
  });
130
135
  }
136
+ const spanHandle = request[SPAN_HANDLE_SYMBOL];
137
+ spanHandle == null ? void 0 : spanHandle.setName(`${request.method} ${path}`);
138
+ const spans = spanHandle == null ? void 0 : spanHandle.end();
139
+ const traceId = spanHandle == null ? void 0 : spanHandle.traceId;
131
140
  if (client.requestLogger.enabled) {
132
141
  const logs = request[LOGS_SYMBOL];
133
142
  client.requestLogger.logRequest({
@@ -145,7 +154,7 @@ const apitallyPlugin = /* @__PURE__ */ __name(async (fastify, config) => {
145
154
  headers: convertHeaders(reply.getHeaders()),
146
155
  size: responseSize,
147
156
  body: convertBody(reply.payload, (_a = reply.getHeader("content-type")) == null ? void 0 : _a.toString())
148
- }, reply.serverError, logs);
157
+ }, reply.serverError, logs, spans, traceId);
149
158
  }
150
159
  }
151
160
  done();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fastify/plugin.ts"],"sourcesContent":["import type {\n FastifyError,\n FastifyPluginAsync,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { AsyncLocalStorage, AsyncResource } from \"node:async_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n PathInfo,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\n\nconst LOGS_SYMBOL = Symbol(\"apitally.logs\");\nconst ASYNC_RESOURCE_SYMBOL = Symbol(\"apitally.logsContextAsyncResource\");\n\ndeclare module \"fastify\" {\n interface FastifyReply {\n payload: any;\n serverError?: FastifyError;\n }\n\n interface FastifyRequest {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n\n [LOGS_SYMBOL]?: LogRecord[];\n [ASYNC_RESOURCE_SYMBOL]?: AsyncResource;\n }\n}\n\nconst apitallyPlugin: FastifyPluginAsync<ApitallyConfig> = async (\n fastify,\n config,\n) => {\n const client = new ApitallyClient(config);\n const routes: PathInfo[] = [];\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchPinoLogger(fastify.log, logsContext);\n patchNestLogger(logsContext);\n }\n\n fastify.decorateRequest(\"apitallyConsumer\", null);\n fastify.decorateRequest(\"consumerIdentifier\", null); // For backwards compatibility\n fastify.decorateReply(\"payload\", null);\n\n fastify.addHook(\"onRoute\", (routeOptions) => {\n const methods = Array.isArray(routeOptions.method)\n ? routeOptions.method\n : [routeOptions.method];\n methods.forEach((method) => {\n if (![\"HEAD\", \"OPTIONS\"].includes(method.toUpperCase())) {\n routes.push({\n method: method.toUpperCase(),\n path: routeOptions.url,\n });\n }\n });\n });\n\n fastify.addHook(\"onReady\", () => {\n client.setStartupData(getAppInfo(routes, config.appVersion));\n client.startSync();\n });\n\n fastify.addHook(\"onClose\", async () => {\n await client.handleShutdown();\n });\n\n // Establish logs context for each request\n fastify.addHook(\"onRequest\", (request, reply, done) => {\n if (client.isEnabled()) {\n const logs: LogRecord[] = [];\n request[LOGS_SYMBOL] = logs;\n logsContext.run(logs, () => {\n const asyncResource = new AsyncResource(\"ApitallyLogsContext\");\n request[ASYNC_RESOURCE_SYMBOL] = asyncResource;\n asyncResource.runInAsyncScope(done, request.raw);\n });\n } else {\n done();\n }\n });\n\n // Restore async context for later hooks and route handlers\n fastify.addHook(\"preValidation\", (request, reply, done) => {\n if (client.isEnabled() && request[ASYNC_RESOURCE_SYMBOL]) {\n const asyncResource = request[ASYNC_RESOURCE_SYMBOL];\n asyncResource.runInAsyncScope(done, request.raw);\n } else {\n done();\n }\n });\n\n fastify.addHook(\"onSend\", (request, reply, payload: any, done) => {\n const contentType = reply.getHeader(\"content-type\") as string | undefined;\n if (client.requestLogger.isSupportedContentType(contentType)) {\n reply.payload = payload;\n }\n done();\n });\n\n fastify.addHook(\"onError\", (request, reply, error, done) => {\n if (!error.statusCode || error.statusCode === 500) {\n reply.serverError = error;\n }\n done();\n });\n\n fastify.addHook(\"onResponse\", (request, reply, done) => {\n if (client.isEnabled() && request.method.toUpperCase() !== \"OPTIONS\") {\n // Get path from routeOptions if available (from v4), otherwise fallback to deprecated routerPath\n const consumer = getConsumer(request);\n const path =\n \"routeOptions\" in request\n ? (request as any).routeOptions.url\n : (request as any).routerPath;\n const requestSize = parseContentLength(request.headers[\"content-length\"]);\n const responseSize = parseContentLength(\n reply.getHeader(\"content-length\"),\n );\n const responseTime = getResponseTime(reply);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: request.method,\n path,\n statusCode: reply.statusCode,\n responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (reply.statusCode === 400 || reply.statusCode === 422) &&\n reply.payload\n ) {\n try {\n const parsedPayload = JSON.parse(reply.payload);\n const validationErrors: ValidationError[] = [];\n\n if (\n (!parsedPayload.code ||\n parsedPayload.code === \"FST_ERR_VALIDATION\") &&\n typeof parsedPayload.message === \"string\"\n ) {\n validationErrors.push(...extractAjvErrors(parsedPayload.message));\n } else if (Array.isArray(parsedPayload.message)) {\n validationErrors.push(\n ...extractNestValidationErrors(parsedPayload.message),\n );\n }\n\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n ...error,\n });\n });\n } catch (error) {} // eslint-disable-line no-empty\n }\n\n if (reply.statusCode === 500 && reply.serverError) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n type: reply.serverError.name,\n msg: reply.serverError.message,\n traceback: reply.serverError.stack || \"\",\n });\n }\n\n if (client.requestLogger.enabled) {\n const logs = request[LOGS_SYMBOL];\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: request.method,\n path,\n url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,\n headers: convertHeaders(request.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(request.body, request.headers[\"content-type\"]),\n },\n {\n statusCode: reply.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(reply.getHeaders()),\n size: responseSize,\n body: convertBody(\n reply.payload,\n reply.getHeader(\"content-type\")?.toString(),\n ),\n },\n reply.serverError,\n logs,\n );\n }\n }\n\n done();\n });\n};\n\nfunction getAppInfo(routes: PathInfo[], appVersion?: string) {\n const versions = [[\"nodejs\", process.version.replace(/^v/, \"\")]];\n const fastifyVersion = getPackageVersion(\"fastify\");\n const pinoVersion = getPackageVersion(\"pino\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (fastifyVersion) {\n versions.push([\"fastify\", fastifyVersion]);\n }\n if (pinoVersion) {\n versions.push([\"pino\", pinoVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: routes,\n versions: Object.fromEntries(versions),\n client: \"js:fastify\",\n };\n}\n\nexport function setConsumer(\n request: FastifyRequest,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n request.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(request: FastifyRequest) {\n if (request.apitallyConsumer) {\n return consumerFromStringOrObject(request.apitallyConsumer);\n } else if (request.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(request.consumerIdentifier);\n }\n return null;\n}\n\nfunction getResponseTime(reply: FastifyReply) {\n if (reply.elapsedTime !== undefined) {\n return reply.elapsedTime;\n } else if ((reply as any).getResponseTime !== undefined) {\n return (reply as any).getResponseTime();\n }\n return 0;\n}\n\nfunction extractAjvErrors(message: string): ValidationError[] {\n try {\n const regex =\n /(?<=^|, )((?:headers|params|query|querystring|body)[/.][^ ]+)(?= )/g;\n const matches: { match: string; index: number }[] = [];\n let match: RegExpExecArray | null;\n while ((match = regex.exec(message)) !== null) {\n matches.push({ match: match[0], index: match.index });\n }\n\n return matches.map((m, i) => {\n const endIndex =\n i + 1 < matches.length ? matches[i + 1].index - 2 : message.length;\n const matchSplit = m.match.split(/[/.]/);\n if (matchSplit[0] === \"querystring\") {\n matchSplit[0] = \"query\";\n }\n return {\n loc: matchSplit.join(\".\"),\n msg: message.substring(m.index, endIndex),\n type: \"\",\n };\n });\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(message: any[]): ValidationError[] {\n try {\n return message\n .filter((msg: any) => typeof msg === \"string\")\n .map((msg: any) => ({\n loc: \"\",\n msg,\n type: \"\",\n }));\n } catch (error) {\n return [];\n }\n}\n\nexport { apitallyPlugin };\n\nexport default fp(apitallyPlugin, {\n name: \"apitally\",\n});\n"],"mappings":";;AAMA,OAAOA,QAAQ;AACf,SAASC,mBAAmBC,qBAAqB;AAEjD,SAASC,sBAAsB;AAC/B,SAASC,kCAAkC;AAC3C,SAASC,0BAA0B;AACnC,SAASC,yBAAyB;AAElC,SAASC,aAAaC,sBAAsB;AAO5C,SACEC,cACAC,iBACAC,iBACAC,oBACK;AAEP,MAAMC,cAAcC,uBAAO,eAAA;AAC3B,MAAMC,wBAAwBD,uBAAO,mCAAA;AAiBrC,MAAME,iBAAqD,8BACzDC,SACAC,WAAAA;AAEA,QAAMC,SAAS,IAAIhB,eAAee,MAAAA;AAClC,QAAME,SAAqB,CAAA;AAC3B,QAAMC,cAAc,IAAIpB,kBAAAA;AAExB,MAAIkB,OAAOG,cAAcJ,OAAOK,aAAa;AAC3Cd,iBAAaY,WAAAA;AACbT,iBAAaS,WAAAA;AACbV,oBAAgBM,QAAQO,KAAKH,WAAAA;AAC7BX,oBAAgBW,WAAAA;EAClB;AAEAJ,UAAQQ,gBAAgB,oBAAoB,IAAA;AAC5CR,UAAQQ,gBAAgB,sBAAsB,IAAA;AAC9CR,UAAQS,cAAc,WAAW,IAAA;AAEjCT,UAAQU,QAAQ,WAAW,CAACC,iBAAAA;AAC1B,UAAMC,UAAUC,MAAMC,QAAQH,aAAaI,MAAM,IAC7CJ,aAAaI,SACb;MAACJ,aAAaI;;AAClBH,YAAQI,QAAQ,CAACD,WAAAA;AACf,UAAI,CAAC;QAAC;QAAQ;QAAWE,SAASF,OAAOG,YAAW,CAAA,GAAK;AACvDf,eAAOgB,KAAK;UACVJ,QAAQA,OAAOG,YAAW;UAC1BE,MAAMT,aAAaU;QACrB,CAAA;MACF;IACF,CAAA;EACF,CAAA;AAEArB,UAAQU,QAAQ,WAAW,MAAA;AACzBR,WAAOoB,eAAeC,WAAWpB,QAAQF,OAAOuB,UAAU,CAAA;AAC1DtB,WAAOuB,UAAS;EAClB,CAAA;AAEAzB,UAAQU,QAAQ,WAAW,YAAA;AACzB,UAAMR,OAAOwB,eAAc;EAC7B,CAAA;AAGA1B,UAAQU,QAAQ,aAAa,CAACiB,SAASC,OAAOC,SAAAA;AAC5C,QAAI3B,OAAO4B,UAAS,GAAI;AACtB,YAAMC,OAAoB,CAAA;AAC1BJ,cAAQ/B,WAAAA,IAAemC;AACvB3B,kBAAY4B,IAAID,MAAM,MAAA;AACpB,cAAME,gBAAgB,IAAIhD,cAAc,qBAAA;AACxC0C,gBAAQ7B,qBAAAA,IAAyBmC;AACjCA,sBAAcC,gBAAgBL,MAAMF,QAAQQ,GAAG;MACjD,CAAA;IACF,OAAO;AACLN,WAAAA;IACF;EACF,CAAA;AAGA7B,UAAQU,QAAQ,iBAAiB,CAACiB,SAASC,OAAOC,SAAAA;AAChD,QAAI3B,OAAO4B,UAAS,KAAMH,QAAQ7B,qBAAAA,GAAwB;AACxD,YAAMmC,gBAAgBN,QAAQ7B,qBAAAA;AAC9BmC,oBAAcC,gBAAgBL,MAAMF,QAAQQ,GAAG;IACjD,OAAO;AACLN,WAAAA;IACF;EACF,CAAA;AAEA7B,UAAQU,QAAQ,UAAU,CAACiB,SAASC,OAAOQ,SAAcP,SAAAA;AACvD,UAAMQ,cAAcT,MAAMU,UAAU,cAAA;AACpC,QAAIpC,OAAOG,cAAckC,uBAAuBF,WAAAA,GAAc;AAC5DT,YAAMQ,UAAUA;IAClB;AACAP,SAAAA;EACF,CAAA;AAEA7B,UAAQU,QAAQ,WAAW,CAACiB,SAASC,OAAOY,OAAOX,SAAAA;AACjD,QAAI,CAACW,MAAMC,cAAcD,MAAMC,eAAe,KAAK;AACjDb,YAAMc,cAAcF;IACtB;AACAX,SAAAA;EACF,CAAA;AAEA7B,UAAQU,QAAQ,cAAc,CAACiB,SAASC,OAAOC,SAAAA;AA1HjD;AA2HI,QAAI3B,OAAO4B,UAAS,KAAMH,QAAQZ,OAAOG,YAAW,MAAO,WAAW;AAEpE,YAAMyB,WAAWC,YAAYjB,OAAAA;AAC7B,YAAMP,OACJ,kBAAkBO,UACbA,QAAgBhB,aAAaU,MAC7BM,QAAgBkB;AACvB,YAAMC,cAAc1D,mBAAmBuC,QAAQoB,QAAQ,gBAAA,CAAiB;AACxE,YAAMC,eAAe5D,mBACnBwC,MAAMU,UAAU,gBAAA,CAAA;AAElB,YAAMW,eAAeC,gBAAgBtB,KAAAA;AACrC1B,aAAOiD,iBAAiBC,oBAAoBT,QAAAA;AAC5CzC,aAAOmD,eAAeC,WAAW;QAC/BX,UAAUA,qCAAUY;QACpBxC,QAAQY,QAAQZ;QAChBK;QACAqB,YAAYb,MAAMa;QAClBQ;QACAH;QACAE;MACF,CAAA;AAEA,WACGpB,MAAMa,eAAe,OAAOb,MAAMa,eAAe,QAClDb,MAAMQ,SACN;AACA,YAAI;AACF,gBAAMoB,gBAAgBC,KAAKC,MAAM9B,MAAMQ,OAAO;AAC9C,gBAAMuB,mBAAsC,CAAA;AAE5C,eACG,CAACH,cAAcI,QACdJ,cAAcI,SAAS,yBACzB,OAAOJ,cAAcK,YAAY,UACjC;AACAF,6BAAiBxC,KAAI,GAAI2C,iBAAiBN,cAAcK,OAAO,CAAA;UACjE,WAAWhD,MAAMC,QAAQ0C,cAAcK,OAAO,GAAG;AAC/CF,6BAAiBxC,KAAI,GAChB4C,4BAA4BP,cAAcK,OAAO,CAAA;UAExD;AAEAF,2BAAiB3C,QAAQ,CAACwB,UAAAA;AACxBtC,mBAAO8D,uBAAuBC,mBAAmB;cAC/CtB,UAAUA,qCAAUY;cACpBxC,QAAQY,QAAQZ;cAChBK;cACA,GAAGoB;YACL,CAAA;UACF,CAAA;QACF,SAASA,OAAO;QAAC;MACnB;AAEA,UAAIZ,MAAMa,eAAe,OAAOb,MAAMc,aAAa;AACjDxC,eAAOgE,mBAAmBC,eAAe;UACvCxB,UAAUA,qCAAUY;UACpBxC,QAAQY,QAAQZ;UAChBK;UACAgD,MAAMxC,MAAMc,YAAY2B;UACxBC,KAAK1C,MAAMc,YAAYmB;UACvBU,WAAW3C,MAAMc,YAAY8B,SAAS;QACxC,CAAA;MACF;AAEA,UAAItE,OAAOG,cAAcoE,SAAS;AAChC,cAAM1C,OAAOJ,QAAQ/B,WAAAA;AACrBM,eAAOG,cAAcqE,WACnB;UACEC,WAAWC,KAAKC,IAAG,IAAK;UACxB9D,QAAQY,QAAQZ;UAChBK;UACAC,KAAK,GAAGM,QAAQmD,QAAQ,MAAMnD,QAAQoD,QAAQpD,QAAQqD,QAAQ,GAAGrD,QAAQsD,eAAetD,QAAQN,GAAG;UACnG0B,SAASxD,eAAeoC,QAAQoB,OAAO;UACvCmC,MAAMpC;UACNH,UAAUA,qCAAUY;UACpB4B,MAAM7F,YAAYqC,QAAQwD,MAAMxD,QAAQoB,QAAQ,cAAA,CAAe;QACjE,GACA;UACEN,YAAYb,MAAMa;UAClBQ,cAAcA,eAAe;UAC7BF,SAASxD,eAAeqC,MAAMwD,WAAU,CAAA;UACxCF,MAAMlC;UACNmC,MAAM7F,YACJsC,MAAMQ,UACNR,WAAMU,UAAU,cAAA,MAAhBV,mBAAiCyD,UAAAA;QAErC,GACAzD,MAAMc,aACNX,IAAAA;MAEJ;IACF;AAEAF,SAAAA;EACF,CAAA;AACF,GAnL2D;AAqL3D,SAASN,WAAWpB,QAAoBqB,YAAmB;AACzD,QAAM8D,WAAW;IAAC;MAAC;MAAUC,QAAQC,QAAQC,QAAQ,MAAM,EAAA;;;AAC3D,QAAMC,iBAAiBrG,kBAAkB,SAAA;AACzC,QAAMsG,cAActG,kBAAkB,MAAA;AACtC,QAAMuG,gBAAgBvG,kBAAkB,cAAA;AACxC,QAAMwG,kBAAkBxG,kBAAkB,OAAA;AAC1C,MAAIqG,gBAAgB;AAClBJ,aAASnE,KAAK;MAAC;MAAWuE;KAAe;EAC3C;AACA,MAAIC,aAAa;AACfL,aAASnE,KAAK;MAAC;MAAQwE;KAAY;EACrC;AACA,MAAIC,eAAe;AACjBN,aAASnE,KAAK;MAAC;MAAUyE;KAAc;EACzC;AACA,MAAIC,iBAAiB;AACnBP,aAASnE,KAAK;MAAC;MAAY0E;KAAgB;EAC7C;AACA,MAAIrE,YAAY;AACd8D,aAASnE,KAAK;MAAC;MAAOK;KAAW;EACnC;AACA,SAAO;IACLsE,OAAO3F;IACPmF,UAAUS,OAAOC,YAAYV,QAAAA;IAC7BpF,QAAQ;EACV;AACF;AA1BSqB;AA4BF,SAAS0E,YACdtE,SACAgB,UAAsD;AAEtDhB,UAAQuE,mBAAmBvD,YAAYwD;AACzC;AALgBF;AAOhB,SAASrD,YAAYjB,SAAuB;AAC1C,MAAIA,QAAQuE,kBAAkB;AAC5B,WAAO/G,2BAA2BwC,QAAQuE,gBAAgB;EAC5D,WAAWvE,QAAQyE,oBAAoB;AAErCb,YAAQc,YACN,sGACA,oBAAA;AAEF,WAAOlH,2BAA2BwC,QAAQyE,kBAAkB;EAC9D;AACA,SAAO;AACT;AAZSxD;AAcT,SAASM,gBAAgBtB,OAAmB;AAC1C,MAAIA,MAAM0E,gBAAgBH,QAAW;AACnC,WAAOvE,MAAM0E;EACf,WAAY1E,MAAcsB,oBAAoBiD,QAAW;AACvD,WAAQvE,MAAcsB,gBAAe;EACvC;AACA,SAAO;AACT;AAPSA;AAST,SAASY,iBAAiBD,SAAe;AACvC,MAAI;AACF,UAAM0C,QACJ;AACF,UAAMC,UAA8C,CAAA;AACpD,QAAIC;AACJ,YAAQA,QAAQF,MAAMG,KAAK7C,OAAAA,OAAc,MAAM;AAC7C2C,cAAQrF,KAAK;QAAEsF,OAAOA,MAAM,CAAA;QAAIE,OAAOF,MAAME;MAAM,CAAA;IACrD;AAEA,WAAOH,QAAQI,IAAI,CAACC,GAAGC,MAAAA;AACrB,YAAMC,WACJD,IAAI,IAAIN,QAAQQ,SAASR,QAAQM,IAAI,CAAA,EAAGH,QAAQ,IAAI9C,QAAQmD;AAC9D,YAAMC,aAAaJ,EAAEJ,MAAMS,MAAM,MAAA;AACjC,UAAID,WAAW,CAAA,MAAO,eAAe;AACnCA,mBAAW,CAAA,IAAK;MAClB;AACA,aAAO;QACLE,KAAKF,WAAWG,KAAK,GAAA;QACrB9C,KAAKT,QAAQwD,UAAUR,EAAEF,OAAOI,QAAAA;QAChC3C,MAAM;MACR;IACF,CAAA;EACF,SAAS5B,OAAO;AACd,WAAO,CAAA;EACT;AACF;AA1BSsB;AA4BT,SAASC,4BAA4BF,SAAc;AACjD,MAAI;AACF,WAAOA,QACJyD,OAAO,CAAChD,QAAa,OAAOA,QAAQ,QAAA,EACpCsC,IAAI,CAACtC,SAAc;MAClB6C,KAAK;MACL7C;MACAF,MAAM;IACR,EAAA;EACJ,SAAS5B,OAAO;AACd,WAAO,CAAA;EACT;AACF;AAZSuB;AAgBT,IAAA,iBAAehF,GAAGgB,gBAAgB;EAChCsE,MAAM;AACR,CAAA;","names":["fp","AsyncLocalStorage","AsyncResource","ApitallyClient","consumerFromStringOrObject","parseContentLength","getPackageVersion","convertBody","convertHeaders","patchConsole","patchNestLogger","patchPinoLogger","patchWinston","LOGS_SYMBOL","Symbol","ASYNC_RESOURCE_SYMBOL","apitallyPlugin","fastify","config","client","routes","logsContext","requestLogger","captureLogs","log","decorateRequest","decorateReply","addHook","routeOptions","methods","Array","isArray","method","forEach","includes","toUpperCase","push","path","url","setStartupData","getAppInfo","appVersion","startSync","handleShutdown","request","reply","done","isEnabled","logs","run","asyncResource","runInAsyncScope","raw","payload","contentType","getHeader","isSupportedContentType","error","statusCode","serverError","consumer","getConsumer","routerPath","requestSize","headers","responseSize","responseTime","getResponseTime","consumerRegistry","addOrUpdateConsumer","requestCounter","addRequest","identifier","parsedPayload","JSON","parse","validationErrors","code","message","extractAjvErrors","extractNestValidationErrors","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","traceback","stack","enabled","logRequest","timestamp","Date","now","protocol","host","hostname","originalUrl","size","body","getHeaders","toString","versions","process","version","replace","fastifyVersion","pinoVersion","nestjsVersion","apitallyVersion","paths","Object","fromEntries","setConsumer","apitallyConsumer","undefined","consumerIdentifier","emitWarning","elapsedTime","regex","matches","match","exec","index","map","m","i","endIndex","length","matchSplit","split","loc","join","substring","filter"]}
1
+ {"version":3,"sources":["../../src/fastify/plugin.ts"],"sourcesContent":["import type {\n FastifyError,\n FastifyPluginAsync,\n FastifyReply,\n FastifyRequest,\n} from \"fastify\";\nimport fp from \"fastify-plugin\";\nimport { AsyncLocalStorage, AsyncResource } from \"node:async_hooks\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { parseContentLength } from \"../common/headers.js\";\nimport { getPackageVersion } from \"../common/packageVersions.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertBody, convertHeaders } from \"../common/requestLogger.js\";\nimport type { SpanHandle } from \"../common/spanCollector.js\";\nimport {\n ApitallyConfig,\n ApitallyConsumer,\n PathInfo,\n ValidationError,\n} from \"../common/types.js\";\nimport {\n patchConsole,\n patchNestLogger,\n patchPinoLogger,\n patchWinston,\n} from \"../loggers/index.js\";\n\nconst LOGS_SYMBOL = Symbol(\"apitally.logs\");\nconst ASYNC_RESOURCE_SYMBOL = Symbol(\"apitally.asyncResource\");\nconst SPAN_HANDLE_SYMBOL = Symbol(\"apitally.spanHandle\");\n\ndeclare module \"fastify\" {\n interface FastifyReply {\n payload: any;\n serverError?: FastifyError;\n }\n\n interface FastifyRequest {\n apitallyConsumer?: ApitallyConsumer | string | null;\n consumerIdentifier?: ApitallyConsumer | string | null; // For backwards compatibility\n\n [LOGS_SYMBOL]?: LogRecord[];\n [ASYNC_RESOURCE_SYMBOL]?: AsyncResource;\n [SPAN_HANDLE_SYMBOL]?: SpanHandle;\n }\n}\n\nconst apitallyPlugin: FastifyPluginAsync<ApitallyConfig> = async (\n fastify,\n config,\n) => {\n const client = new ApitallyClient(config);\n const routes: PathInfo[] = [];\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\n\n if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n patchPinoLogger(fastify.log, logsContext);\n patchNestLogger(logsContext);\n }\n\n fastify.decorateRequest(\"apitallyConsumer\", null);\n fastify.decorateRequest(\"consumerIdentifier\", null); // For backwards compatibility\n fastify.decorateReply(\"payload\", null);\n\n fastify.addHook(\"onRoute\", (routeOptions) => {\n const methods = Array.isArray(routeOptions.method)\n ? routeOptions.method\n : [routeOptions.method];\n methods.forEach((method) => {\n if (![\"HEAD\", \"OPTIONS\"].includes(method.toUpperCase())) {\n routes.push({\n method: method.toUpperCase(),\n path: routeOptions.url,\n });\n }\n });\n });\n\n fastify.addHook(\"onReady\", () => {\n client.setStartupData(getAppInfo(routes, config.appVersion));\n client.startSync();\n });\n\n fastify.addHook(\"onClose\", async () => {\n await client.handleShutdown();\n });\n\n // Establish logs context and start span for each request\n fastify.addHook(\"onRequest\", (request, reply, done) => {\n if (client.isEnabled()) {\n const logs: LogRecord[] = [];\n request[LOGS_SYMBOL] = logs;\n logsContext.run(logs, () => {\n const spanHandle = client.spanCollector.startSpan();\n request[SPAN_HANDLE_SYMBOL] = spanHandle;\n spanHandle.runInContext(() => {\n const asyncResource = new AsyncResource(\"ApitallyContext\");\n request[ASYNC_RESOURCE_SYMBOL] = asyncResource;\n asyncResource.runInAsyncScope(done, request.raw);\n });\n });\n } else {\n done();\n }\n });\n\n // Restore async context for later hooks and route handlers\n fastify.addHook(\"preValidation\", (request, reply, done) => {\n if (client.isEnabled() && request[ASYNC_RESOURCE_SYMBOL]) {\n const asyncResource = request[ASYNC_RESOURCE_SYMBOL];\n asyncResource.runInAsyncScope(done, request.raw);\n } else {\n done();\n }\n });\n\n fastify.addHook(\"onSend\", (request, reply, payload: any, done) => {\n const contentType = reply.getHeader(\"content-type\") as string | undefined;\n if (client.requestLogger.isSupportedContentType(contentType)) {\n reply.payload = payload;\n }\n done();\n });\n\n fastify.addHook(\"onError\", (request, reply, error, done) => {\n if (!error.statusCode || error.statusCode === 500) {\n reply.serverError = error;\n }\n done();\n });\n\n fastify.addHook(\"onResponse\", (request, reply, done) => {\n if (client.isEnabled() && request.method.toUpperCase() !== \"OPTIONS\") {\n // Get path from routeOptions if available (from v4), otherwise fallback to deprecated routerPath\n const consumer = getConsumer(request);\n const path =\n \"routeOptions\" in request\n ? (request as any).routeOptions.url\n : (request as any).routerPath;\n const requestSize = parseContentLength(request.headers[\"content-length\"]);\n const responseSize = parseContentLength(\n reply.getHeader(\"content-length\"),\n );\n const responseTime = getResponseTime(reply);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: request.method,\n path,\n statusCode: reply.statusCode,\n responseTime,\n requestSize,\n responseSize,\n });\n\n if (\n (reply.statusCode === 400 || reply.statusCode === 422) &&\n reply.payload\n ) {\n try {\n const parsedPayload = JSON.parse(reply.payload);\n const validationErrors: ValidationError[] = [];\n\n if (\n (!parsedPayload.code ||\n parsedPayload.code === \"FST_ERR_VALIDATION\") &&\n typeof parsedPayload.message === \"string\"\n ) {\n validationErrors.push(...extractAjvErrors(parsedPayload.message));\n } else if (Array.isArray(parsedPayload.message)) {\n validationErrors.push(\n ...extractNestValidationErrors(parsedPayload.message),\n );\n }\n\n validationErrors.forEach((error) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n ...error,\n });\n });\n } catch (error) {} // eslint-disable-line no-empty\n }\n\n if (reply.statusCode === 500 && reply.serverError) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: request.method,\n path: path,\n type: reply.serverError.name,\n msg: reply.serverError.message,\n traceback: reply.serverError.stack || \"\",\n });\n }\n\n const spanHandle = request[SPAN_HANDLE_SYMBOL];\n spanHandle?.setName(`${request.method} ${path}`);\n const spans = spanHandle?.end();\n const traceId = spanHandle?.traceId;\n\n if (client.requestLogger.enabled) {\n const logs = request[LOGS_SYMBOL];\n client.requestLogger.logRequest(\n {\n timestamp: Date.now() / 1000,\n method: request.method,\n path,\n url: `${request.protocol}://${request.host ?? request.hostname}${request.originalUrl ?? request.url}`,\n headers: convertHeaders(request.headers),\n size: requestSize,\n consumer: consumer?.identifier,\n body: convertBody(request.body, request.headers[\"content-type\"]),\n },\n {\n statusCode: reply.statusCode,\n responseTime: responseTime / 1000,\n headers: convertHeaders(reply.getHeaders()),\n size: responseSize,\n body: convertBody(\n reply.payload,\n reply.getHeader(\"content-type\")?.toString(),\n ),\n },\n reply.serverError,\n logs,\n spans,\n traceId,\n );\n }\n }\n\n done();\n });\n};\n\nfunction getAppInfo(routes: PathInfo[], appVersion?: string) {\n const versions = [[\"nodejs\", process.version.replace(/^v/, \"\")]];\n const fastifyVersion = getPackageVersion(\"fastify\");\n const pinoVersion = getPackageVersion(\"pino\");\n const nestjsVersion = getPackageVersion(\"@nestjs/core\");\n const apitallyVersion = getPackageVersion(\"../..\");\n if (fastifyVersion) {\n versions.push([\"fastify\", fastifyVersion]);\n }\n if (pinoVersion) {\n versions.push([\"pino\", pinoVersion]);\n }\n if (nestjsVersion) {\n versions.push([\"nestjs\", nestjsVersion]);\n }\n if (apitallyVersion) {\n versions.push([\"apitally\", apitallyVersion]);\n }\n if (appVersion) {\n versions.push([\"app\", appVersion]);\n }\n return {\n paths: routes,\n versions: Object.fromEntries(versions),\n client: \"js:fastify\",\n };\n}\n\nexport function setConsumer(\n request: FastifyRequest,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n request.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(request: FastifyRequest) {\n if (request.apitallyConsumer) {\n return consumerFromStringOrObject(request.apitallyConsumer);\n } else if (request.consumerIdentifier) {\n // For backwards compatibility\n process.emitWarning(\n \"The consumerIdentifier property on the request object is deprecated. Use apitallyConsumer instead.\",\n \"DeprecationWarning\",\n );\n return consumerFromStringOrObject(request.consumerIdentifier);\n }\n return null;\n}\n\nfunction getResponseTime(reply: FastifyReply) {\n if (reply.elapsedTime !== undefined) {\n return reply.elapsedTime;\n } else if ((reply as any).getResponseTime !== undefined) {\n return (reply as any).getResponseTime();\n }\n return 0;\n}\n\nfunction extractAjvErrors(message: string): ValidationError[] {\n try {\n const regex =\n /(?<=^|, )((?:headers|params|query|querystring|body)[/.][^ ]+)(?= )/g;\n const matches: { match: string; index: number }[] = [];\n let match: RegExpExecArray | null;\n while ((match = regex.exec(message)) !== null) {\n matches.push({ match: match[0], index: match.index });\n }\n\n return matches.map((m, i) => {\n const endIndex =\n i + 1 < matches.length ? matches[i + 1].index - 2 : message.length;\n const matchSplit = m.match.split(/[/.]/);\n if (matchSplit[0] === \"querystring\") {\n matchSplit[0] = \"query\";\n }\n return {\n loc: matchSplit.join(\".\"),\n msg: message.substring(m.index, endIndex),\n type: \"\",\n };\n });\n } catch (error) {\n return [];\n }\n}\n\nfunction extractNestValidationErrors(message: any[]): ValidationError[] {\n try {\n return message\n .filter((msg: any) => typeof msg === \"string\")\n .map((msg: any) => ({\n loc: \"\",\n msg,\n type: \"\",\n }));\n } catch (error) {\n return [];\n }\n}\n\nexport { apitallyPlugin };\n\nexport default fp(apitallyPlugin, {\n name: \"apitally\",\n});\n"],"mappings":"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","names":["fp","AsyncLocalStorage","AsyncResource","ApitallyClient","consumerFromStringOrObject","parseContentLength","getPackageVersion","convertBody","convertHeaders","patchConsole","patchNestLogger","patchPinoLogger","patchWinston","LOGS_SYMBOL","Symbol","ASYNC_RESOURCE_SYMBOL","SPAN_HANDLE_SYMBOL","apitallyPlugin","fastify","config","client","routes","logsContext","requestLogger","captureLogs","log","decorateRequest","decorateReply","addHook","routeOptions","methods","Array","isArray","method","forEach","includes","toUpperCase","push","path","url","setStartupData","getAppInfo","appVersion","startSync","handleShutdown","request","reply","done","isEnabled","logs","run","spanHandle","spanCollector","startSpan","runInContext","asyncResource","runInAsyncScope","raw","payload","contentType","getHeader","isSupportedContentType","error","statusCode","serverError","consumer","getConsumer","routerPath","requestSize","headers","responseSize","responseTime","getResponseTime","consumerRegistry","addOrUpdateConsumer","requestCounter","addRequest","identifier","parsedPayload","JSON","parse","validationErrors","code","message","extractAjvErrors","extractNestValidationErrors","validationErrorCounter","addValidationError","serverErrorCounter","addServerError","type","name","msg","traceback","stack","setName","spans","end","traceId","enabled","logRequest","timestamp","Date","now","protocol","host","hostname","originalUrl","size","body","getHeaders","toString","versions","process","version","replace","fastifyVersion","pinoVersion","nestjsVersion","apitallyVersion","paths","Object","fromEntries","setConsumer","apitallyConsumer","undefined","consumerIdentifier","emitWarning","elapsedTime","regex","matches","match","exec","index","map","m","i","endIndex","length","matchSplit","split","loc","join","substring","filter"]}
package/dist/h3/index.cjs CHANGED
@@ -18,13 +18,16 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var h3_exports = {};
20
20
  __export(h3_exports, {
21
+ ApitallySpanProcessor: () => import_spanCollector.ApitallySpanProcessor,
21
22
  apitallyPlugin: () => import_plugin.apitallyPlugin,
22
23
  setConsumer: () => import_plugin.setConsumer
23
24
  });
24
25
  module.exports = __toCommonJS(h3_exports);
26
+ var import_spanCollector = require("../common/spanCollector.js");
25
27
  var import_plugin = require("./plugin.js");
26
28
  // Annotate the CommonJS export names for ESM import in node:
27
29
  0 && (module.exports = {
30
+ ApitallySpanProcessor,
28
31
  apitallyPlugin,
29
32
  setConsumer
30
33
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/h3/index.ts"],"sourcesContent":["export type { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nexport { apitallyPlugin, setConsumer } from \"./plugin.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AACA;;;;;;oBAA4C;","names":[]}
1
+ {"version":3,"sources":["../../src/h3/index.ts"],"sourcesContent":["export { ApitallySpanProcessor } from \"../common/spanCollector.js\";\nexport type { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nexport { apitallyPlugin, setConsumer } from \"./plugin.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;;;;;;;2BAAsC;AAEtC,oBAA4C;","names":[]}
@@ -1,5 +1,7 @@
1
+ export { ApitallySpanProcessor } from '../common/spanCollector.cjs';
1
2
  export { ApitallyConfig, ApitallyConsumer } from '../common/types.cjs';
2
3
  export { apitallyPlugin, setConsumer } from './plugin.cjs';
4
+ import '@opentelemetry/sdk-trace-base';
3
5
  import '../common/logging.cjs';
4
6
  import 'winston';
5
7
  import '../common/requestLogger.cjs';
@@ -1,5 +1,7 @@
1
+ export { ApitallySpanProcessor } from '../common/spanCollector.js';
1
2
  export { ApitallyConfig, ApitallyConsumer } from '../common/types.js';
2
3
  export { apitallyPlugin, setConsumer } from './plugin.js';
4
+ import '@opentelemetry/sdk-trace-base';
3
5
  import '../common/logging.js';
4
6
  import 'winston';
5
7
  import '../common/requestLogger.js';
package/dist/h3/index.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { ApitallySpanProcessor } from "../common/spanCollector.js";
1
2
  import { apitallyPlugin, setConsumer } from "./plugin.js";
2
3
  export {
4
+ ApitallySpanProcessor,
3
5
  apitallyPlugin,
4
6
  setConsumer
5
7
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/h3/index.ts"],"sourcesContent":["export type { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nexport { apitallyPlugin, setConsumer } from \"./plugin.js\";\n"],"mappings":"AACA,SAASA,gBAAgBC,mBAAmB;","names":["apitallyPlugin","setConsumer"]}
1
+ {"version":3,"sources":["../../src/h3/index.ts"],"sourcesContent":["export { ApitallySpanProcessor } from \"../common/spanCollector.js\";\nexport type { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nexport { apitallyPlugin, setConsumer } from \"./plugin.js\";\n"],"mappings":"AAAA,SAASA,6BAA6B;AAEtC,SAASC,gBAAgBC,mBAAmB;","names":["ApitallySpanProcessor","apitallyPlugin","setConsumer"]}
@@ -35,6 +35,7 @@ var import_loggers = require("../loggers/index.js");
35
35
  var import_utils = require("./utils.js");
36
36
  const REQUEST_TIMESTAMP_SYMBOL = /* @__PURE__ */ Symbol("apitally.requestTimestamp");
37
37
  const REQUEST_BODY_SYMBOL = /* @__PURE__ */ Symbol("apitally.requestBody");
38
+ const SPAN_HANDLE_SYMBOL = /* @__PURE__ */ Symbol("apitally.spanHandle");
38
39
  const jsonHeaders = new Headers({
39
40
  "content-type": "application/json;charset=UTF-8"
40
41
  });
@@ -56,7 +57,7 @@ const apitallyPlugin = (0, import_h3.definePlugin)((app, config) => {
56
57
  (0, import_loggers.patchWinston)(logsContext);
57
58
  }
58
59
  const handleResponse = /* @__PURE__ */ __name(async (event, response, error) => {
59
- var _a, _b;
60
+ var _a;
60
61
  if (event.req.method.toUpperCase() === "OPTIONS") {
61
62
  return response;
62
63
  }
@@ -77,6 +78,10 @@ const apitallyPlugin = (0, import_h3.definePlugin)((app, config) => {
77
78
  });
78
79
  responsePromise.then(async (capturedResponse) => {
79
80
  const responseTime = startTime ? import_node_perf_hooks.performance.now() - startTime : 0;
81
+ const spanHandle = event.context[SPAN_HANDLE_SYMBOL];
82
+ spanHandle == null ? void 0 : spanHandle.setName(`${event.req.method} ${path}`);
83
+ const spans = spanHandle == null ? void 0 : spanHandle.end();
84
+ const traceId = spanHandle == null ? void 0 : spanHandle.traceId;
80
85
  const responseSize = capturedResponse.completed ? capturedResponse.size : void 0;
81
86
  const requestSize = (0, import_headers.parseContentLength)(event.req.headers.get("content-length"));
82
87
  if (path) {
@@ -107,12 +112,12 @@ const apitallyPlugin = (0, import_h3.definePlugin)((app, config) => {
107
112
  headers: (0, import_requestLogger.convertHeaders)(Object.fromEntries(response.headers.entries())),
108
113
  size: responseSize,
109
114
  body: capturedResponse.body
110
- }, (error == null ? void 0 : error.cause) instanceof Error ? error.cause : void 0, logs);
115
+ }, (error == null ? void 0 : error.cause) instanceof Error ? error.cause : void 0, logs, spans, traceId);
111
116
  }
112
117
  });
113
- if (path && (error == null ? void 0 : error.status) === 400 && error.data && error.data.name === "ZodError") {
114
- const zodError = error.data;
115
- (_b = zodError.issues) == null ? void 0 : _b.forEach((issue) => {
118
+ if (path && (error == null ? void 0 : error.status) === 400 && error.data && Array.isArray(error.data.issues)) {
119
+ const issues = error.data.issues;
120
+ issues.forEach((issue) => {
116
121
  client.validationErrorCounter.addValidationError({
117
122
  consumer: consumer == null ? void 0 : consumer.identifier,
118
123
  method: event.req.method,
@@ -138,6 +143,9 @@ const apitallyPlugin = (0, import_h3.definePlugin)((app, config) => {
138
143
  app.use((0, import_h3.onRequest)(async (event) => {
139
144
  logsContext.enterWith([]);
140
145
  event.context[REQUEST_TIMESTAMP_SYMBOL] = import_node_perf_hooks.performance.now();
146
+ const spanHandle = client.spanCollector.startSpan();
147
+ event.context[SPAN_HANDLE_SYMBOL] = spanHandle;
148
+ spanHandle.enterContext();
141
149
  const requestContentType = event.req.headers.get("content-type");
142
150
  const requestSize = (0, import_headers.parseContentLength)(event.req.headers.get("content-length")) ?? 0;
143
151
  if (client.requestLogger.enabled && client.requestLogger.config.logRequestBody && client.requestLogger.isSupportedContentType(requestContentType) && requestSize <= client.requestLogger.maxBodySize) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/h3/plugin.ts"],"sourcesContent":["import type { H3Event, HTTPError } from \"h3\";\nimport { definePlugin, onError, onRequest, onResponse } from \"h3\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\nimport type { ZodError } from \"zod\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { mergeHeaders, parseContentLength } from \"../common/headers.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertHeaders } from \"../common/requestLogger.js\";\nimport { captureResponse } from \"../common/response.js\";\nimport { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nimport { patchConsole, patchWinston } from \"../loggers/index.js\";\nimport { getAppInfo } from \"./utils.js\";\n\nconst REQUEST_TIMESTAMP_SYMBOL = Symbol(\"apitally.requestTimestamp\");\nconst REQUEST_BODY_SYMBOL = Symbol(\"apitally.requestBody\");\n\ndeclare module \"h3\" {\n interface H3EventContext {\n apitallyConsumer?: ApitallyConsumer | string;\n\n [REQUEST_TIMESTAMP_SYMBOL]?: number;\n [REQUEST_BODY_SYMBOL]?: Buffer;\n }\n}\n\nconst jsonHeaders = new Headers({\n \"content-type\": \"application/json;charset=UTF-8\",\n});\n\nexport const apitallyPlugin = definePlugin<ApitallyConfig>((app, config) => {\n const client = new ApitallyClient(config);\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\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 if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n }\n\n const handleResponse = async (\n event: H3Event,\n response?: Response,\n error?: HTTPError,\n ) => {\n if (event.req.method.toUpperCase() === \"OPTIONS\") {\n return response;\n }\n\n const startTime = event.context[REQUEST_TIMESTAMP_SYMBOL];\n const path = event.context.matchedRoute?.route;\n\n const consumer = getConsumer(event);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n if (!response) {\n response = new Response(null, {\n status: error?.status || 500,\n statusText: error?.statusText || \"Internal Server Error\",\n headers: error?.headers\n ? mergeHeaders(jsonHeaders, error.headers)\n : jsonHeaders,\n });\n }\n\n const [newResponse, responsePromise] = captureResponse(response, {\n captureBody:\n client.requestLogger.enabled &&\n client.requestLogger.config.logResponseBody,\n maxBodySize: client.requestLogger.maxBodySize,\n });\n\n responsePromise.then(async (capturedResponse) => {\n const responseTime = startTime ? performance.now() - startTime : 0;\n const responseSize = capturedResponse.completed\n ? capturedResponse.size\n : undefined;\n const requestSize = parseContentLength(\n event.req.headers.get(\"content-length\"),\n );\n\n if (path) {\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: event.req.method,\n path,\n statusCode: response.status,\n responseTime,\n requestSize,\n responseSize,\n });\n }\n\n if (client.requestLogger.enabled) {\n const logs = logsContext.getStore();\n client.requestLogger.logRequest(\n {\n timestamp: (Date.now() - responseTime) / 1000,\n method: event.req.method,\n path,\n url: event.req.url,\n headers: convertHeaders(\n Object.fromEntries(event.req.headers.entries()),\n ),\n size: requestSize,\n consumer: consumer?.identifier,\n body: event.context[REQUEST_BODY_SYMBOL],\n },\n {\n statusCode: response.status,\n responseTime: responseTime / 1000,\n headers: convertHeaders(\n Object.fromEntries(response.headers.entries()),\n ),\n size: responseSize,\n body: capturedResponse.body,\n },\n error?.cause instanceof Error ? error.cause : undefined,\n logs,\n );\n }\n });\n\n if (\n path &&\n error?.status === 400 &&\n error.data &&\n (error.data as any).name === \"ZodError\"\n ) {\n const zodError = error.data as ZodError;\n zodError.issues?.forEach((issue) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: event.req.method,\n path,\n loc: issue.path.join(\".\"),\n msg: issue.message,\n type: issue.code,\n });\n });\n }\n\n if (path && error?.status === 500 && error.cause instanceof Error) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: event.req.method,\n path,\n type: error.cause.name,\n msg: error.cause.message,\n traceback: error.cause.stack || \"\",\n });\n }\n\n return newResponse;\n };\n\n app\n .use(\n onRequest(async (event) => {\n logsContext.enterWith([]);\n event.context[REQUEST_TIMESTAMP_SYMBOL] = performance.now();\n const requestContentType = event.req.headers.get(\"content-type\");\n const requestSize =\n parseContentLength(event.req.headers.get(\"content-length\")) ?? 0;\n\n if (\n client.requestLogger.enabled &&\n client.requestLogger.config.logRequestBody &&\n client.requestLogger.isSupportedContentType(requestContentType) &&\n requestSize <= client.requestLogger.maxBodySize\n ) {\n const clonedRequest = event.req.clone();\n const requestBody = Buffer.from(await clonedRequest.arrayBuffer());\n event.context[REQUEST_BODY_SYMBOL] = requestBody;\n }\n }),\n )\n .use(\n onResponse((response, event) => {\n if (client.isEnabled()) {\n return handleResponse(event, response, undefined);\n }\n }),\n )\n .use(\n onError((error, event) => {\n if (client.isEnabled()) {\n handleResponse(event, undefined, error);\n }\n }),\n );\n});\n\nexport function setConsumer(\n event: H3Event,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n event.context.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(event: H3Event) {\n const consumer = event.context.apitallyConsumer;\n if (consumer) {\n return consumerFromStringOrObject(consumer);\n }\n return null;\n}\n"],"mappings":"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","names":["REQUEST_TIMESTAMP_SYMBOL","Symbol","REQUEST_BODY_SYMBOL","jsonHeaders","Headers","apitallyPlugin","definePlugin","app","config","client","ApitallyClient","logsContext","AsyncLocalStorage","setStartupData","attempt","appInfo","getAppInfo","appVersion","paths","length","startSync","setTimeout","requestLogger","captureLogs","patchConsole","patchWinston","handleResponse","event","response","error","req","method","toUpperCase","startTime","context","path","matchedRoute","route","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","Response","status","statusText","headers","mergeHeaders","newResponse","responsePromise","captureResponse","captureBody","enabled","logResponseBody","maxBodySize","then","capturedResponse","responseTime","performance","now","responseSize","completed","size","undefined","requestSize","parseContentLength","get","requestCounter","addRequest","identifier","statusCode","logs","getStore","logRequest","timestamp","Date","url","convertHeaders","Object","fromEntries","entries","body","cause","Error","data","name","zodError","issues","forEach","issue","validationErrorCounter","addValidationError","loc","join","msg","message","type","code","serverErrorCounter","addServerError","traceback","stack","use","onRequest","enterWith","requestContentType","logRequestBody","isSupportedContentType","clonedRequest","clone","requestBody","Buffer","from","arrayBuffer","onResponse","isEnabled","onError","setConsumer","apitallyConsumer","consumerFromStringOrObject"]}
1
+ {"version":3,"sources":["../../src/h3/plugin.ts"],"sourcesContent":["import type { H3Event, HTTPError } from \"h3\";\nimport { definePlugin, onError, onRequest, onResponse } from \"h3\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { performance } from \"node:perf_hooks\";\nimport type { ZodError } from \"zod\";\n\nimport { ApitallyClient } from \"../common/client.js\";\nimport { consumerFromStringOrObject } from \"../common/consumerRegistry.js\";\nimport { mergeHeaders, parseContentLength } from \"../common/headers.js\";\nimport type { LogRecord } from \"../common/requestLogger.js\";\nimport { convertHeaders } from \"../common/requestLogger.js\";\nimport { captureResponse } from \"../common/response.js\";\nimport type { SpanHandle } from \"../common/spanCollector.js\";\nimport { ApitallyConfig, ApitallyConsumer } from \"../common/types.js\";\nimport { patchConsole, patchWinston } from \"../loggers/index.js\";\nimport { getAppInfo } from \"./utils.js\";\n\nconst REQUEST_TIMESTAMP_SYMBOL = Symbol(\"apitally.requestTimestamp\");\nconst REQUEST_BODY_SYMBOL = Symbol(\"apitally.requestBody\");\nconst SPAN_HANDLE_SYMBOL = Symbol(\"apitally.spanHandle\");\n\ndeclare module \"h3\" {\n interface H3EventContext {\n apitallyConsumer?: ApitallyConsumer | string;\n\n [REQUEST_TIMESTAMP_SYMBOL]?: number;\n [REQUEST_BODY_SYMBOL]?: Buffer;\n [SPAN_HANDLE_SYMBOL]?: SpanHandle;\n }\n}\n\nconst jsonHeaders = new Headers({\n \"content-type\": \"application/json;charset=UTF-8\",\n});\n\nexport const apitallyPlugin = definePlugin<ApitallyConfig>((app, config) => {\n const client = new ApitallyClient(config);\n const logsContext = new AsyncLocalStorage<LogRecord[]>();\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 if (client.requestLogger.config.captureLogs) {\n patchConsole(logsContext);\n patchWinston(logsContext);\n }\n\n const handleResponse = async (\n event: H3Event,\n response?: Response,\n error?: HTTPError,\n ) => {\n if (event.req.method.toUpperCase() === \"OPTIONS\") {\n return response;\n }\n\n const startTime = event.context[REQUEST_TIMESTAMP_SYMBOL];\n const path = event.context.matchedRoute?.route;\n\n const consumer = getConsumer(event);\n client.consumerRegistry.addOrUpdateConsumer(consumer);\n\n if (!response) {\n response = new Response(null, {\n status: error?.status || 500,\n statusText: error?.statusText || \"Internal Server Error\",\n headers: error?.headers\n ? mergeHeaders(jsonHeaders, error.headers)\n : jsonHeaders,\n });\n }\n\n const [newResponse, responsePromise] = captureResponse(response, {\n captureBody:\n client.requestLogger.enabled &&\n client.requestLogger.config.logResponseBody,\n maxBodySize: client.requestLogger.maxBodySize,\n });\n\n responsePromise.then(async (capturedResponse) => {\n const responseTime = startTime ? performance.now() - startTime : 0;\n\n const spanHandle = event.context[SPAN_HANDLE_SYMBOL];\n spanHandle?.setName(`${event.req.method} ${path}`);\n const spans = spanHandle?.end();\n const traceId = spanHandle?.traceId;\n\n const responseSize = capturedResponse.completed\n ? capturedResponse.size\n : undefined;\n const requestSize = parseContentLength(\n event.req.headers.get(\"content-length\"),\n );\n\n if (path) {\n client.requestCounter.addRequest({\n consumer: consumer?.identifier,\n method: event.req.method,\n path,\n statusCode: response.status,\n responseTime,\n requestSize,\n responseSize,\n });\n }\n\n if (client.requestLogger.enabled) {\n const logs = logsContext.getStore();\n client.requestLogger.logRequest(\n {\n timestamp: (Date.now() - responseTime) / 1000,\n method: event.req.method,\n path,\n url: event.req.url,\n headers: convertHeaders(\n Object.fromEntries(event.req.headers.entries()),\n ),\n size: requestSize,\n consumer: consumer?.identifier,\n body: event.context[REQUEST_BODY_SYMBOL],\n },\n {\n statusCode: response.status,\n responseTime: responseTime / 1000,\n headers: convertHeaders(\n Object.fromEntries(response.headers.entries()),\n ),\n size: responseSize,\n body: capturedResponse.body,\n },\n error?.cause instanceof Error ? error.cause : undefined,\n logs,\n spans,\n traceId,\n );\n }\n });\n\n if (\n path &&\n error?.status === 400 &&\n error.data &&\n Array.isArray((error.data as any).issues)\n ) {\n const issues = (error.data as any).issues as ZodError[\"issues\"];\n issues.forEach((issue) => {\n client.validationErrorCounter.addValidationError({\n consumer: consumer?.identifier,\n method: event.req.method,\n path,\n loc: issue.path.join(\".\"),\n msg: issue.message,\n type: issue.code,\n });\n });\n }\n\n if (path && error?.status === 500 && error.cause instanceof Error) {\n client.serverErrorCounter.addServerError({\n consumer: consumer?.identifier,\n method: event.req.method,\n path,\n type: error.cause.name,\n msg: error.cause.message,\n traceback: error.cause.stack || \"\",\n });\n }\n\n return newResponse;\n };\n\n app\n .use(\n onRequest(async (event) => {\n logsContext.enterWith([]);\n event.context[REQUEST_TIMESTAMP_SYMBOL] = performance.now();\n\n const spanHandle = client.spanCollector.startSpan();\n event.context[SPAN_HANDLE_SYMBOL] = spanHandle;\n spanHandle.enterContext();\n\n const requestContentType = event.req.headers.get(\"content-type\");\n const requestSize =\n parseContentLength(event.req.headers.get(\"content-length\")) ?? 0;\n\n if (\n client.requestLogger.enabled &&\n client.requestLogger.config.logRequestBody &&\n client.requestLogger.isSupportedContentType(requestContentType) &&\n requestSize <= client.requestLogger.maxBodySize\n ) {\n const clonedRequest = event.req.clone();\n const requestBody = Buffer.from(await clonedRequest.arrayBuffer());\n event.context[REQUEST_BODY_SYMBOL] = requestBody;\n }\n }),\n )\n .use(\n onResponse((response, event) => {\n if (client.isEnabled()) {\n return handleResponse(event, response, undefined);\n }\n }),\n )\n .use(\n onError((error, event) => {\n if (client.isEnabled()) {\n handleResponse(event, undefined, error);\n }\n }),\n );\n});\n\nexport function setConsumer(\n event: H3Event,\n consumer: ApitallyConsumer | string | null | undefined,\n) {\n event.context.apitallyConsumer = consumer || undefined;\n}\n\nfunction getConsumer(event: H3Event) {\n const consumer = event.context.apitallyConsumer;\n if (consumer) {\n return consumerFromStringOrObject(consumer);\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AACA;;;;;;AAAA,gBAA6D;AAC7D,8BAAkC;AAClC,6BAA4B;AAG5B,oBAA+B;AAC/B,8BAA2C;AAC3C,qBAAiD;AAEjD,2BAA+B;AAC/B,sBAAgC;AAGhC,qBAA2C;AAC3C,mBAA2B;AAE3B,MAAMA,2BAA2BC,uBAAO,2BAAA;AACxC,MAAMC,sBAAsBD,uBAAO,sBAAA;AACnC,MAAME,qBAAqBF,uBAAO,qBAAA;AAYlC,MAAMG,cAAc,IAAIC,QAAQ;EAC9B,gBAAgB;AAClB,CAAA;AAEO,MAAMC,qBAAiBC,wBAA6B,CAACC,KAAKC,WAAAA;AAC/D,QAAMC,SAAS,IAAIC,6BAAeF,MAAAA;AAClC,QAAMG,cAAc,IAAIC,0CAAAA;AAExB,QAAMC,iBAAiB,wBAACC,UAAkB,MAAC;AACzC,UAAMC,cAAUC,yBAAWT,KAAKC,OAAOS,UAAU;AACjD,QAAIF,QAAQG,MAAMC,SAAS,KAAKL,WAAW,IAAI;AAC7CL,aAAOI,eAAeE,OAAAA;AACtBN,aAAOW,UAAS;IAClB,OAAO;AACLC,iBAAW,MAAMR,eAAeC,UAAU,CAAA,GAAI,GAAA;IAChD;EACF,GARuB;AASvBO,aAAW,MAAMR,eAAAA,GAAkB,GAAA;AAEnC,MAAIJ,OAAOa,cAAcd,OAAOe,aAAa;AAC3CC,qCAAab,WAAAA;AACbc,qCAAad,WAAAA;EACf;AAEA,QAAMe,iBAAiB,8BACrBC,OACAC,UACAC,UAAAA;AAzDJ;AA2DI,QAAIF,MAAMG,IAAIC,OAAOC,YAAW,MAAO,WAAW;AAChD,aAAOJ;IACT;AAEA,UAAMK,YAAYN,MAAMO,QAAQnC,wBAAAA;AAChC,UAAMoC,QAAOR,WAAMO,QAAQE,iBAAdT,mBAA4BU;AAEzC,UAAMC,WAAWC,YAAYZ,KAAAA;AAC7BlB,WAAO+B,iBAAiBC,oBAAoBH,QAAAA;AAE5C,QAAI,CAACV,UAAU;AACbA,iBAAW,IAAIc,SAAS,MAAM;QAC5BC,SAAQd,+BAAOc,WAAU;QACzBC,aAAYf,+BAAOe,eAAc;QACjCC,UAAShB,+BAAOgB,eACZC,6BAAa3C,aAAa0B,MAAMgB,OAAO,IACvC1C;MACN,CAAA;IACF;AAEA,UAAM,CAAC4C,aAAaC,eAAAA,QAAmBC,iCAAgBrB,UAAU;MAC/DsB,aACEzC,OAAOa,cAAc6B,WACrB1C,OAAOa,cAAcd,OAAO4C;MAC9BC,aAAa5C,OAAOa,cAAc+B;IACpC,CAAA;AAEAL,oBAAgBM,KAAK,OAAOC,qBAAAA;AAC1B,YAAMC,eAAevB,YAAYwB,mCAAYC,IAAG,IAAKzB,YAAY;AAEjE,YAAM0B,aAAahC,MAAMO,QAAQhC,kBAAAA;AACjCyD,+CAAYC,QAAQ,GAAGjC,MAAMG,IAAIC,MAAM,IAAII,IAAAA;AAC3C,YAAM0B,QAAQF,yCAAYG;AAC1B,YAAMC,UAAUJ,yCAAYI;AAE5B,YAAMC,eAAeT,iBAAiBU,YAClCV,iBAAiBW,OACjBC;AACJ,YAAMC,kBAAcC,mCAClB1C,MAAMG,IAAIe,QAAQyB,IAAI,gBAAA,CAAA;AAGxB,UAAInC,MAAM;AACR1B,eAAO8D,eAAeC,WAAW;UAC/BlC,UAAUA,qCAAUmC;UACpB1C,QAAQJ,MAAMG,IAAIC;UAClBI;UACAuC,YAAY9C,SAASe;UACrBa;UACAY;UACAJ;QACF,CAAA;MACF;AAEA,UAAIvD,OAAOa,cAAc6B,SAAS;AAChC,cAAMwB,OAAOhE,YAAYiE,SAAQ;AACjCnE,eAAOa,cAAcuD,WACnB;UACEC,YAAYC,KAAKrB,IAAG,IAAKF,gBAAgB;UACzCzB,QAAQJ,MAAMG,IAAIC;UAClBI;UACA6C,KAAKrD,MAAMG,IAAIkD;UACfnC,aAASoC,qCACPC,OAAOC,YAAYxD,MAAMG,IAAIe,QAAQuC,QAAO,CAAA,CAAA;UAE9ClB,MAAME;UACN9B,UAAUA,qCAAUmC;UACpBY,MAAM1D,MAAMO,QAAQjC,mBAAAA;QACtB,GACA;UACEyE,YAAY9C,SAASe;UACrBa,cAAcA,eAAe;UAC7BX,aAASoC,qCACPC,OAAOC,YAAYvD,SAASiB,QAAQuC,QAAO,CAAA,CAAA;UAE7ClB,MAAMF;UACNqB,MAAM9B,iBAAiB8B;QACzB,IACAxD,+BAAOyD,kBAAiBC,QAAQ1D,MAAMyD,QAAQnB,QAC9CQ,MACAd,OACAE,OAAAA;MAEJ;IACF,CAAA;AAEA,QACE5B,SACAN,+BAAOc,YAAW,OAClBd,MAAM2D,QACNC,MAAMC,QAAS7D,MAAM2D,KAAaG,MAAM,GACxC;AACA,YAAMA,SAAU9D,MAAM2D,KAAaG;AACnCA,aAAOC,QAAQ,CAACC,UAAAA;AACdpF,eAAOqF,uBAAuBC,mBAAmB;UAC/CzD,UAAUA,qCAAUmC;UACpB1C,QAAQJ,MAAMG,IAAIC;UAClBI;UACA6D,KAAKH,MAAM1D,KAAK8D,KAAK,GAAA;UACrBC,KAAKL,MAAMM;UACXC,MAAMP,MAAMQ;QACd,CAAA;MACF,CAAA;IACF;AAEA,QAAIlE,SAAQN,+BAAOc,YAAW,OAAOd,MAAMyD,iBAAiBC,OAAO;AACjE9E,aAAO6F,mBAAmBC,eAAe;QACvCjE,UAAUA,qCAAUmC;QACpB1C,QAAQJ,MAAMG,IAAIC;QAClBI;QACAiE,MAAMvE,MAAMyD,MAAMkB;QAClBN,KAAKrE,MAAMyD,MAAMa;QACjBM,WAAW5E,MAAMyD,MAAMoB,SAAS;MAClC,CAAA;IACF;AAEA,WAAO3D;EACT,GA1HuB;AA4HvBxC,MACGoG,QACCC,qBAAU,OAAOjF,UAAAA;AACfhB,gBAAYkG,UAAU,CAAA,CAAE;AACxBlF,UAAMO,QAAQnC,wBAAAA,IAA4B0D,mCAAYC,IAAG;AAEzD,UAAMC,aAAalD,OAAOqG,cAAcC,UAAS;AACjDpF,UAAMO,QAAQhC,kBAAAA,IAAsByD;AACpCA,eAAWqD,aAAY;AAEvB,UAAMC,qBAAqBtF,MAAMG,IAAIe,QAAQyB,IAAI,cAAA;AACjD,UAAMF,kBACJC,mCAAmB1C,MAAMG,IAAIe,QAAQyB,IAAI,gBAAA,CAAA,KAAsB;AAEjE,QACE7D,OAAOa,cAAc6B,WACrB1C,OAAOa,cAAcd,OAAO0G,kBAC5BzG,OAAOa,cAAc6F,uBAAuBF,kBAAAA,KAC5C7C,eAAe3D,OAAOa,cAAc+B,aACpC;AACA,YAAM+D,gBAAgBzF,MAAMG,IAAIuF,MAAK;AACrC,YAAMC,cAAcC,OAAOC,KAAK,MAAMJ,cAAcK,YAAW,CAAA;AAC/D9F,YAAMO,QAAQjC,mBAAAA,IAAuBqH;IACvC;EACF,CAAA,CAAA,EAEDX,QACCe,sBAAW,CAAC9F,UAAUD,UAAAA;AACpB,QAAIlB,OAAOkH,UAAS,GAAI;AACtB,aAAOjG,eAAeC,OAAOC,UAAUuC,MAAAA;IACzC;EACF,CAAA,CAAA,EAEDwC,QACCiB,mBAAQ,CAAC/F,OAAOF,UAAAA;AACd,QAAIlB,OAAOkH,UAAS,GAAI;AACtBjG,qBAAeC,OAAOwC,QAAWtC,KAAAA;IACnC;EACF,CAAA,CAAA;AAEN,CAAA;AAEO,SAASgG,YACdlG,OACAW,UAAsD;AAEtDX,QAAMO,QAAQ4F,mBAAmBxF,YAAY6B;AAC/C;AALgB0D;AAOhB,SAAStF,YAAYZ,OAAc;AACjC,QAAMW,WAAWX,MAAMO,QAAQ4F;AAC/B,MAAIxF,UAAU;AACZ,eAAOyF,oDAA2BzF,QAAAA;EACpC;AACA,SAAO;AACT;AANSC;","names":["REQUEST_TIMESTAMP_SYMBOL","Symbol","REQUEST_BODY_SYMBOL","SPAN_HANDLE_SYMBOL","jsonHeaders","Headers","apitallyPlugin","definePlugin","app","config","client","ApitallyClient","logsContext","AsyncLocalStorage","setStartupData","attempt","appInfo","getAppInfo","appVersion","paths","length","startSync","setTimeout","requestLogger","captureLogs","patchConsole","patchWinston","handleResponse","event","response","error","req","method","toUpperCase","startTime","context","path","matchedRoute","route","consumer","getConsumer","consumerRegistry","addOrUpdateConsumer","Response","status","statusText","headers","mergeHeaders","newResponse","responsePromise","captureResponse","captureBody","enabled","logResponseBody","maxBodySize","then","capturedResponse","responseTime","performance","now","spanHandle","setName","spans","end","traceId","responseSize","completed","size","undefined","requestSize","parseContentLength","get","requestCounter","addRequest","identifier","statusCode","logs","getStore","logRequest","timestamp","Date","url","convertHeaders","Object","fromEntries","entries","body","cause","Error","data","Array","isArray","issues","forEach","issue","validationErrorCounter","addValidationError","loc","join","msg","message","type","code","serverErrorCounter","addServerError","name","traceback","stack","use","onRequest","enterWith","spanCollector","startSpan","enterContext","requestContentType","logRequestBody","isSupportedContentType","clonedRequest","clone","requestBody","Buffer","from","arrayBuffer","onResponse","isEnabled","onError","setConsumer","apitallyConsumer","consumerFromStringOrObject"]}
@@ -1,6 +1,8 @@
1
+ import { SpanHandle } from '../common/spanCollector.cjs';
1
2
  import { ApitallyConsumer, ApitallyConfig } from '../common/types.cjs';
2
3
  import * as h3 from 'h3';
3
4
  import { H3Event } from 'h3';
5
+ import '@opentelemetry/sdk-trace-base';
4
6
  import '../common/logging.cjs';
5
7
  import 'winston';
6
8
  import '../common/requestLogger.cjs';
@@ -10,11 +12,13 @@ import '../common/tempGzipFile.cjs';
10
12
 
11
13
  declare const REQUEST_TIMESTAMP_SYMBOL: unique symbol;
12
14
  declare const REQUEST_BODY_SYMBOL: unique symbol;
15
+ declare const SPAN_HANDLE_SYMBOL: unique symbol;
13
16
  declare module "h3" {
14
17
  interface H3EventContext {
15
18
  apitallyConsumer?: ApitallyConsumer | string;
16
19
  [REQUEST_TIMESTAMP_SYMBOL]?: number;
17
20
  [REQUEST_BODY_SYMBOL]?: Buffer;
21
+ [SPAN_HANDLE_SYMBOL]?: SpanHandle;
18
22
  }
19
23
  }
20
24
  declare const apitallyPlugin: (options: ApitallyConfig) => h3.H3Plugin;
@@ -1,6 +1,8 @@
1
+ import { SpanHandle } from '../common/spanCollector.js';
1
2
  import { ApitallyConsumer, ApitallyConfig } from '../common/types.js';
2
3
  import * as h3 from 'h3';
3
4
  import { H3Event } from 'h3';
5
+ import '@opentelemetry/sdk-trace-base';
4
6
  import '../common/logging.js';
5
7
  import 'winston';
6
8
  import '../common/requestLogger.js';
@@ -10,11 +12,13 @@ import '../common/tempGzipFile.js';
10
12
 
11
13
  declare const REQUEST_TIMESTAMP_SYMBOL: unique symbol;
12
14
  declare const REQUEST_BODY_SYMBOL: unique symbol;
15
+ declare const SPAN_HANDLE_SYMBOL: unique symbol;
13
16
  declare module "h3" {
14
17
  interface H3EventContext {
15
18
  apitallyConsumer?: ApitallyConsumer | string;
16
19
  [REQUEST_TIMESTAMP_SYMBOL]?: number;
17
20
  [REQUEST_BODY_SYMBOL]?: Buffer;
21
+ [SPAN_HANDLE_SYMBOL]?: SpanHandle;
18
22
  }
19
23
  }
20
24
  declare const apitallyPlugin: (options: ApitallyConfig) => h3.H3Plugin;
package/dist/h3/plugin.js CHANGED
@@ -12,6 +12,7 @@ import { patchConsole, patchWinston } from "../loggers/index.js";
12
12
  import { getAppInfo } from "./utils.js";
13
13
  const REQUEST_TIMESTAMP_SYMBOL = /* @__PURE__ */ Symbol("apitally.requestTimestamp");
14
14
  const REQUEST_BODY_SYMBOL = /* @__PURE__ */ Symbol("apitally.requestBody");
15
+ const SPAN_HANDLE_SYMBOL = /* @__PURE__ */ Symbol("apitally.spanHandle");
15
16
  const jsonHeaders = new Headers({
16
17
  "content-type": "application/json;charset=UTF-8"
17
18
  });
@@ -33,7 +34,7 @@ const apitallyPlugin = definePlugin((app, config) => {
33
34
  patchWinston(logsContext);
34
35
  }
35
36
  const handleResponse = /* @__PURE__ */ __name(async (event, response, error) => {
36
- var _a, _b;
37
+ var _a;
37
38
  if (event.req.method.toUpperCase() === "OPTIONS") {
38
39
  return response;
39
40
  }
@@ -54,6 +55,10 @@ const apitallyPlugin = definePlugin((app, config) => {
54
55
  });
55
56
  responsePromise.then(async (capturedResponse) => {
56
57
  const responseTime = startTime ? performance.now() - startTime : 0;
58
+ const spanHandle = event.context[SPAN_HANDLE_SYMBOL];
59
+ spanHandle == null ? void 0 : spanHandle.setName(`${event.req.method} ${path}`);
60
+ const spans = spanHandle == null ? void 0 : spanHandle.end();
61
+ const traceId = spanHandle == null ? void 0 : spanHandle.traceId;
57
62
  const responseSize = capturedResponse.completed ? capturedResponse.size : void 0;
58
63
  const requestSize = parseContentLength(event.req.headers.get("content-length"));
59
64
  if (path) {
@@ -84,12 +89,12 @@ const apitallyPlugin = definePlugin((app, config) => {
84
89
  headers: convertHeaders(Object.fromEntries(response.headers.entries())),
85
90
  size: responseSize,
86
91
  body: capturedResponse.body
87
- }, (error == null ? void 0 : error.cause) instanceof Error ? error.cause : void 0, logs);
92
+ }, (error == null ? void 0 : error.cause) instanceof Error ? error.cause : void 0, logs, spans, traceId);
88
93
  }
89
94
  });
90
- if (path && (error == null ? void 0 : error.status) === 400 && error.data && error.data.name === "ZodError") {
91
- const zodError = error.data;
92
- (_b = zodError.issues) == null ? void 0 : _b.forEach((issue) => {
95
+ if (path && (error == null ? void 0 : error.status) === 400 && error.data && Array.isArray(error.data.issues)) {
96
+ const issues = error.data.issues;
97
+ issues.forEach((issue) => {
93
98
  client.validationErrorCounter.addValidationError({
94
99
  consumer: consumer == null ? void 0 : consumer.identifier,
95
100
  method: event.req.method,
@@ -115,6 +120,9 @@ const apitallyPlugin = definePlugin((app, config) => {
115
120
  app.use(onRequest(async (event) => {
116
121
  logsContext.enterWith([]);
117
122
  event.context[REQUEST_TIMESTAMP_SYMBOL] = performance.now();
123
+ const spanHandle = client.spanCollector.startSpan();
124
+ event.context[SPAN_HANDLE_SYMBOL] = spanHandle;
125
+ spanHandle.enterContext();
118
126
  const requestContentType = event.req.headers.get("content-type");
119
127
  const requestSize = parseContentLength(event.req.headers.get("content-length")) ?? 0;
120
128
  if (client.requestLogger.enabled && client.requestLogger.config.logRequestBody && client.requestLogger.isSupportedContentType(requestContentType) && requestSize <= client.requestLogger.maxBodySize) {