apitally 1.0.0-beta.2 → 1.0.0-beta.4

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 (74) hide show
  1. package/README.md +7 -25
  2. package/dist/activation.cjs +2 -1
  3. package/dist/activation.cjs.map +1 -1
  4. package/dist/activation.d.cts +1 -0
  5. package/dist/activation.d.ts +1 -0
  6. package/dist/activation.js +2 -1
  7. package/dist/activation.js.map +1 -1
  8. package/dist/consumers.cjs +96 -0
  9. package/dist/consumers.cjs.map +1 -0
  10. package/dist/consumers.d.cts +13 -0
  11. package/dist/consumers.d.ts +13 -0
  12. package/dist/consumers.js +93 -0
  13. package/dist/consumers.js.map +1 -0
  14. package/dist/context.cjs +2 -8
  15. package/dist/context.cjs.map +1 -1
  16. package/dist/context.d.cts +5 -5
  17. package/dist/context.d.ts +5 -5
  18. package/dist/context.js +3 -7
  19. package/dist/context.js.map +1 -1
  20. package/dist/exportWorker.cjs +6 -5
  21. package/dist/exportWorker.cjs.map +1 -1
  22. package/dist/exportWorker.js +6 -5
  23. package/dist/exportWorker.js.map +1 -1
  24. package/dist/index.cjs +2 -2
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/metrics.cjs +164 -32
  31. package/dist/metrics.cjs.map +1 -1
  32. package/dist/metrics.d.cts +4 -6
  33. package/dist/metrics.d.ts +4 -6
  34. package/dist/metrics.js +165 -33
  35. package/dist/metrics.js.map +1 -1
  36. package/dist/nestjs/index.cjs +6 -0
  37. package/dist/nestjs/index.cjs.map +1 -1
  38. package/dist/nestjs/index.js +6 -0
  39. package/dist/nestjs/index.js.map +1 -1
  40. package/dist/providers.cjs +0 -8
  41. package/dist/providers.cjs.map +1 -1
  42. package/dist/providers.d.cts +0 -2
  43. package/dist/providers.d.ts +0 -2
  44. package/dist/providers.js +1 -8
  45. package/dist/providers.js.map +1 -1
  46. package/dist/requestObservation.cjs +6 -4
  47. package/dist/requestObservation.cjs.map +1 -1
  48. package/dist/requestObservation.js +7 -5
  49. package/dist/requestObservation.js.map +1 -1
  50. package/dist/serverErrors.cjs +30 -28
  51. package/dist/serverErrors.cjs.map +1 -1
  52. package/dist/serverErrors.js +30 -28
  53. package/dist/serverErrors.js.map +1 -1
  54. package/dist/spanProcessor.cjs +0 -2
  55. package/dist/spanProcessor.cjs.map +1 -1
  56. package/dist/spanProcessor.js +0 -2
  57. package/dist/spanProcessor.js.map +1 -1
  58. package/dist/spool.cjs +5 -0
  59. package/dist/spool.cjs.map +1 -1
  60. package/dist/spool.d.cts +2 -1
  61. package/dist/spool.d.ts +2 -1
  62. package/dist/spool.js +5 -0
  63. package/dist/spool.js.map +1 -1
  64. package/dist/validationErrors.cjs +29 -26
  65. package/dist/validationErrors.cjs.map +1 -1
  66. package/dist/validationErrors.js +29 -26
  67. package/dist/validationErrors.js.map +1 -1
  68. package/package.json +2 -2
  69. package/dist/consumer.cjs +0 -61
  70. package/dist/consumer.cjs.map +0 -1
  71. package/dist/consumer.d.cts +0 -12
  72. package/dist/consumer.d.ts +0 -12
  73. package/dist/consumer.js +0 -59
  74. package/dist/consumer.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["resolvePackageVersion","resolvePackageEntryPath","createRequire","getRequestRecord"],"sources":["../../src/nestjs/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport type {\n CallHandler,\n ExecutionContext,\n INestApplication,\n NestInterceptor,\n} from \"@nestjs/common\";\nimport type { Express } from \"express\";\nimport type { FastifyInstance } from \"fastify\";\nimport type * as rxjs from \"rxjs\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { getRequestRecord } from \"../context.js\";\nimport { captureException } from \"../exceptions.js\";\nimport { installExpressIntegration } from \"../express/install.js\";\nimport { installFastifyIntegration } from \"../fastify/install.js\";\nimport { resolvePackageEntryPath, resolvePackageVersion } from \"../packageVersion.js\";\n\nexport type { ApitallyOptions };\n\nconst INTERCEPTOR_MARKER = Symbol.for(\"apitally.nestInterceptor\");\n\nexport function useApitally(app: INestApplication, options?: ApitallyOptions): void {\n const httpAdapter = app.getHttpAdapter();\n const adapterType = httpAdapter.getType();\n const adapterInstance = httpAdapter.getInstance() as object;\n const frameworkInfo = {\n framework: \"nestjs\",\n frameworkVersion: resolvePackageVersion(\"@nestjs/core\"),\n };\n\n if (adapterType === \"express\") {\n installExpressIntegration(adapterInstance as Express, options, frameworkInfo);\n } else if (adapterType === \"fastify\") {\n installFastifyIntegration(adapterInstance as FastifyInstance, options, frameworkInfo);\n } else {\n throw new TypeError(\n `Unsupported NestJS HTTP adapter ${JSON.stringify(adapterType)}. Supported adapters: express and fastify.`,\n );\n }\n\n const markedInstance = adapterInstance as Record<symbol, boolean | undefined>;\n if (markedInstance[INTERCEPTOR_MARKER] !== true) {\n app.useGlobalInterceptors(createExceptionInterceptor());\n markedInstance[INTERCEPTOR_MARKER] = true;\n }\n}\n\nfunction createExceptionInterceptor(): NestInterceptor {\n const commonEntryPath = resolvePackageEntryPath(\"@nestjs/common\");\n const { catchError, throwError } = createRequire(commonEntryPath)(\"rxjs\") as Pick<\n typeof rxjs,\n \"catchError\" | \"throwError\"\n >;\n\n return {\n intercept(executionContext: ExecutionContext, next: CallHandler) {\n if (executionContext.getType() !== \"http\") {\n return next.handle();\n }\n return next.handle().pipe(\n catchError((exception: unknown) => {\n const status = getExceptionStatus(exception);\n if (status === undefined || status >= 500) {\n captureException(exception);\n }\n const requestRecord = getRequestRecord();\n const messages = getExceptionResponseMessages(exception);\n if (requestRecord && messages) {\n requestRecord.validationErrors = messages.map((message) => ({\n source: \"\",\n field: \"\",\n message,\n type: \"\",\n }));\n }\n return throwError(() => exception);\n }),\n );\n },\n };\n}\n\nfunction getExceptionStatus(exception: unknown): number | undefined {\n if ((typeof exception !== \"object\" && typeof exception !== \"function\") || exception === null) {\n return undefined;\n }\n try {\n const candidate = exception as {\n getStatus?: () => unknown;\n status?: unknown;\n statusCode?: unknown;\n };\n if (typeof candidate.getStatus === \"function\") {\n const status = candidate.getStatus();\n return typeof status === \"number\" ? status : undefined;\n }\n if (typeof candidate.status === \"number\") {\n return candidate.status;\n }\n return typeof candidate.statusCode === \"number\" ? candidate.statusCode : undefined;\n } catch {\n return undefined;\n }\n}\n\n// The ValidationPipe throws a BadRequestException whose response carries the\n// class-validator messages as an array of strings.\nfunction getExceptionResponseMessages(exception: unknown): string[] | undefined {\n const getResponse = (exception as { getResponse?: unknown } | null)?.getResponse;\n if (typeof getResponse !== \"function\") {\n return undefined;\n }\n const response = getResponse.call(exception) as { message?: unknown } | null;\n const messages = response?.message;\n return Array.isArray(messages) && messages.every((message) => typeof message === \"string\")\n ? messages\n : undefined;\n}\n"],"mappings":";;;;;;;;AAmBA,MAAM,qBAAqB,OAAO,IAAI,0BAA0B;AAEhE,SAAgB,YAAY,KAAuB,SAAiC;CAClF,MAAM,cAAc,IAAI,eAAe;CACvC,MAAM,cAAc,YAAY,QAAQ;CACxC,MAAM,kBAAkB,YAAY,YAAY;CAChD,MAAM,gBAAgB;EACpB,WAAW;EACX,kBAAkBA,uBAAAA,sBAAsB,cAAc;CACxD;CAEA,IAAI,gBAAgB,WAClB,wBAAA,0BAA0B,iBAA4B,SAAS,aAAa;MACvE,IAAI,gBAAgB,WACzB,wBAAA,0BAA0B,iBAAoC,SAAS,aAAa;MAEpF,MAAM,IAAI,UACR,mCAAmC,KAAK,UAAU,WAAW,EAAE,2CACjE;CAGF,MAAM,iBAAiB;CACvB,IAAI,eAAe,wBAAwB,MAAM;EAC/C,IAAI,sBAAsB,2BAA2B,CAAC;EACtD,eAAe,sBAAsB;CACvC;AACF;AAEA,SAAS,6BAA8C;CACrD,MAAM,kBAAkBC,uBAAAA,wBAAwB,gBAAgB;CAChE,MAAM,EAAE,YAAY,gBAAA,GAAeC,YAAAA,cAAAA,CAAc,eAAe,CAAC,CAAC,MAAM;CAKxE,OAAO,EACL,UAAU,kBAAoC,MAAmB;EAC/D,IAAI,iBAAiB,QAAQ,MAAM,QACjC,OAAO,KAAK,OAAO;EAErB,OAAO,KAAK,OAAO,CAAC,CAAC,KACnB,YAAY,cAAuB;GACjC,MAAM,SAAS,mBAAmB,SAAS;GAC3C,IAAI,WAAW,KAAA,KAAa,UAAU,KACpC,mBAAA,iBAAiB,SAAS;GAE5B,MAAM,gBAAgBC,gBAAAA,iBAAiB;GACvC,MAAM,WAAW,6BAA6B,SAAS;GACvD,IAAI,iBAAiB,UACnB,cAAc,mBAAmB,SAAS,KAAK,aAAa;IAC1D,QAAQ;IACR,OAAO;IACP;IACA,MAAM;GACR,EAAE;GAEJ,OAAO,iBAAiB,SAAS;EACnC,CAAC,CACH;CACF,EACF;AACF;AAEA,SAAS,mBAAmB,WAAwC;CAClE,IAAK,OAAO,cAAc,YAAY,OAAO,cAAc,cAAe,cAAc,MACtF;CAEF,IAAI;EACF,MAAM,YAAY;EAKlB,IAAI,OAAO,UAAU,cAAc,YAAY;GAC7C,MAAM,SAAS,UAAU,UAAU;GACnC,OAAO,OAAO,WAAW,WAAW,SAAS,KAAA;EAC/C;EACA,IAAI,OAAO,UAAU,WAAW,UAC9B,OAAO,UAAU;EAEnB,OAAO,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA;CAC3E,QAAQ;EACN;CACF;AACF;AAIA,SAAS,6BAA6B,WAA0C;CAC9E,MAAM,cAAe,WAAgD;CACrE,IAAI,OAAO,gBAAgB,YACzB;CAGF,MAAM,WADW,YAAY,KAAK,SACV,CAAC,EAAE;CAC3B,OAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,OAAO,YAAY,OAAO,YAAY,QAAQ,IACrF,WACA,KAAA;AACN"}
1
+ {"version":3,"file":"index.cjs","names":["resolvePackageVersion","shutdown","resolvePackageEntryPath","createRequire","getRequestRecord"],"sources":["../../src/nestjs/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport type {\n CallHandler,\n ExecutionContext,\n INestApplication,\n NestInterceptor,\n} from \"@nestjs/common\";\nimport type { Express } from \"express\";\nimport type { FastifyInstance } from \"fastify\";\nimport type * as rxjs from \"rxjs\";\nimport { shutdown } from \"../activation.js\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { getRequestRecord } from \"../context.js\";\nimport { captureException } from \"../exceptions.js\";\nimport { installExpressIntegration } from \"../express/install.js\";\nimport { installFastifyIntegration } from \"../fastify/install.js\";\nimport { resolvePackageEntryPath, resolvePackageVersion } from \"../packageVersion.js\";\n\nexport type { ApitallyOptions };\n\nconst INTERCEPTOR_MARKER = Symbol.for(\"apitally.nestInterceptor\");\n\nexport function useApitally(app: INestApplication, options?: ApitallyOptions): void {\n const httpAdapter = app.getHttpAdapter();\n const adapterType = httpAdapter.getType();\n const adapterInstance = httpAdapter.getInstance() as object;\n const frameworkInfo = {\n framework: \"nestjs\",\n frameworkVersion: resolvePackageVersion(\"@nestjs/core\"),\n };\n\n if (adapterType === \"express\") {\n installExpressIntegration(adapterInstance as Express, options, frameworkInfo);\n } else if (adapterType === \"fastify\") {\n installFastifyIntegration(adapterInstance as FastifyInstance, options, frameworkInfo);\n } else {\n throw new TypeError(\n `Unsupported NestJS HTTP adapter ${JSON.stringify(adapterType)}. Supported adapters: express and fastify.`,\n );\n }\n\n const markedInstance = adapterInstance as Record<symbol, boolean | undefined>;\n if (markedInstance[INTERCEPTOR_MARKER] !== true) {\n app.useGlobalInterceptors(createExceptionInterceptor());\n // Nest awaits adapter.close() before terminating the process on a shutdown signal.\n const close = httpAdapter.close.bind(httpAdapter);\n httpAdapter.close = async () => {\n await close();\n await shutdown();\n };\n markedInstance[INTERCEPTOR_MARKER] = true;\n }\n}\n\nfunction createExceptionInterceptor(): NestInterceptor {\n const commonEntryPath = resolvePackageEntryPath(\"@nestjs/common\");\n const { catchError, throwError } = createRequire(commonEntryPath)(\"rxjs\") as Pick<\n typeof rxjs,\n \"catchError\" | \"throwError\"\n >;\n\n return {\n intercept(executionContext: ExecutionContext, next: CallHandler) {\n if (executionContext.getType() !== \"http\") {\n return next.handle();\n }\n return next.handle().pipe(\n catchError((exception: unknown) => {\n const status = getExceptionStatus(exception);\n if (status === undefined || status >= 500) {\n captureException(exception);\n }\n const requestRecord = getRequestRecord();\n const messages = getExceptionResponseMessages(exception);\n if (requestRecord && messages) {\n requestRecord.validationErrors = messages.map((message) => ({\n source: \"\",\n field: \"\",\n message,\n type: \"\",\n }));\n }\n return throwError(() => exception);\n }),\n );\n },\n };\n}\n\nfunction getExceptionStatus(exception: unknown): number | undefined {\n if ((typeof exception !== \"object\" && typeof exception !== \"function\") || exception === null) {\n return undefined;\n }\n try {\n const candidate = exception as {\n getStatus?: () => unknown;\n status?: unknown;\n statusCode?: unknown;\n };\n if (typeof candidate.getStatus === \"function\") {\n const status = candidate.getStatus();\n return typeof status === \"number\" ? status : undefined;\n }\n if (typeof candidate.status === \"number\") {\n return candidate.status;\n }\n return typeof candidate.statusCode === \"number\" ? candidate.statusCode : undefined;\n } catch {\n return undefined;\n }\n}\n\n// The ValidationPipe throws a BadRequestException whose response carries the\n// class-validator messages as an array of strings.\nfunction getExceptionResponseMessages(exception: unknown): string[] | undefined {\n const getResponse = (exception as { getResponse?: unknown } | null)?.getResponse;\n if (typeof getResponse !== \"function\") {\n return undefined;\n }\n const response = getResponse.call(exception) as { message?: unknown } | null;\n const messages = response?.message;\n return Array.isArray(messages) && messages.every((message) => typeof message === \"string\")\n ? messages\n : undefined;\n}\n"],"mappings":";;;;;;;;;AAoBA,MAAM,qBAAqB,OAAO,IAAI,0BAA0B;AAEhE,SAAgB,YAAY,KAAuB,SAAiC;CAClF,MAAM,cAAc,IAAI,eAAe;CACvC,MAAM,cAAc,YAAY,QAAQ;CACxC,MAAM,kBAAkB,YAAY,YAAY;CAChD,MAAM,gBAAgB;EACpB,WAAW;EACX,kBAAkBA,uBAAAA,sBAAsB,cAAc;CACxD;CAEA,IAAI,gBAAgB,WAClB,wBAAA,0BAA0B,iBAA4B,SAAS,aAAa;MACvE,IAAI,gBAAgB,WACzB,wBAAA,0BAA0B,iBAAoC,SAAS,aAAa;MAEpF,MAAM,IAAI,UACR,mCAAmC,KAAK,UAAU,WAAW,EAAE,2CACjE;CAGF,MAAM,iBAAiB;CACvB,IAAI,eAAe,wBAAwB,MAAM;EAC/C,IAAI,sBAAsB,2BAA2B,CAAC;EAEtD,MAAM,QAAQ,YAAY,MAAM,KAAK,WAAW;EAChD,YAAY,QAAQ,YAAY;GAC9B,MAAM,MAAM;GACZ,MAAMC,mBAAAA,SAAS;EACjB;EACA,eAAe,sBAAsB;CACvC;AACF;AAEA,SAAS,6BAA8C;CACrD,MAAM,kBAAkBC,uBAAAA,wBAAwB,gBAAgB;CAChE,MAAM,EAAE,YAAY,gBAAA,GAAeC,YAAAA,cAAAA,CAAc,eAAe,CAAC,CAAC,MAAM;CAKxE,OAAO,EACL,UAAU,kBAAoC,MAAmB;EAC/D,IAAI,iBAAiB,QAAQ,MAAM,QACjC,OAAO,KAAK,OAAO;EAErB,OAAO,KAAK,OAAO,CAAC,CAAC,KACnB,YAAY,cAAuB;GACjC,MAAM,SAAS,mBAAmB,SAAS;GAC3C,IAAI,WAAW,KAAA,KAAa,UAAU,KACpC,mBAAA,iBAAiB,SAAS;GAE5B,MAAM,gBAAgBC,gBAAAA,iBAAiB;GACvC,MAAM,WAAW,6BAA6B,SAAS;GACvD,IAAI,iBAAiB,UACnB,cAAc,mBAAmB,SAAS,KAAK,aAAa;IAC1D,QAAQ;IACR,OAAO;IACP;IACA,MAAM;GACR,EAAE;GAEJ,OAAO,iBAAiB,SAAS;EACnC,CAAC,CACH;CACF,EACF;AACF;AAEA,SAAS,mBAAmB,WAAwC;CAClE,IAAK,OAAO,cAAc,YAAY,OAAO,cAAc,cAAe,cAAc,MACtF;CAEF,IAAI;EACF,MAAM,YAAY;EAKlB,IAAI,OAAO,UAAU,cAAc,YAAY;GAC7C,MAAM,SAAS,UAAU,UAAU;GACnC,OAAO,OAAO,WAAW,WAAW,SAAS,KAAA;EAC/C;EACA,IAAI,OAAO,UAAU,WAAW,UAC9B,OAAO,UAAU;EAEnB,OAAO,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA;CAC3E,QAAQ;EACN;CACF;AACF;AAIA,SAAS,6BAA6B,WAA0C;CAC9E,MAAM,cAAe,WAAgD;CACrE,IAAI,OAAO,gBAAgB,YACzB;CAGF,MAAM,WADW,YAAY,KAAK,SACV,CAAC,EAAE;CAC3B,OAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,OAAO,YAAY,OAAO,YAAY,QAAQ,IACrF,WACA,KAAA;AACN"}
@@ -1,6 +1,7 @@
1
1
  import { resolvePackageEntryPath, resolvePackageVersion } from "../packageVersion.js";
2
2
  import { getRequestRecord } from "../context.js";
3
3
  import { captureException } from "../exceptions.js";
4
+ import { shutdown } from "../activation.js";
4
5
  import { installExpressIntegration } from "../express/install.js";
5
6
  import { installFastifyIntegration } from "../fastify/install.js";
6
7
  import { createRequire } from "node:module";
@@ -20,6 +21,11 @@ function useApitally(app, options) {
20
21
  const markedInstance = adapterInstance;
21
22
  if (markedInstance[INTERCEPTOR_MARKER] !== true) {
22
23
  app.useGlobalInterceptors(createExceptionInterceptor());
24
+ const close = httpAdapter.close.bind(httpAdapter);
25
+ httpAdapter.close = async () => {
26
+ await close();
27
+ await shutdown();
28
+ };
23
29
  markedInstance[INTERCEPTOR_MARKER] = true;
24
30
  }
25
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/nestjs/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport type {\n CallHandler,\n ExecutionContext,\n INestApplication,\n NestInterceptor,\n} from \"@nestjs/common\";\nimport type { Express } from \"express\";\nimport type { FastifyInstance } from \"fastify\";\nimport type * as rxjs from \"rxjs\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { getRequestRecord } from \"../context.js\";\nimport { captureException } from \"../exceptions.js\";\nimport { installExpressIntegration } from \"../express/install.js\";\nimport { installFastifyIntegration } from \"../fastify/install.js\";\nimport { resolvePackageEntryPath, resolvePackageVersion } from \"../packageVersion.js\";\n\nexport type { ApitallyOptions };\n\nconst INTERCEPTOR_MARKER = Symbol.for(\"apitally.nestInterceptor\");\n\nexport function useApitally(app: INestApplication, options?: ApitallyOptions): void {\n const httpAdapter = app.getHttpAdapter();\n const adapterType = httpAdapter.getType();\n const adapterInstance = httpAdapter.getInstance() as object;\n const frameworkInfo = {\n framework: \"nestjs\",\n frameworkVersion: resolvePackageVersion(\"@nestjs/core\"),\n };\n\n if (adapterType === \"express\") {\n installExpressIntegration(adapterInstance as Express, options, frameworkInfo);\n } else if (adapterType === \"fastify\") {\n installFastifyIntegration(adapterInstance as FastifyInstance, options, frameworkInfo);\n } else {\n throw new TypeError(\n `Unsupported NestJS HTTP adapter ${JSON.stringify(adapterType)}. Supported adapters: express and fastify.`,\n );\n }\n\n const markedInstance = adapterInstance as Record<symbol, boolean | undefined>;\n if (markedInstance[INTERCEPTOR_MARKER] !== true) {\n app.useGlobalInterceptors(createExceptionInterceptor());\n markedInstance[INTERCEPTOR_MARKER] = true;\n }\n}\n\nfunction createExceptionInterceptor(): NestInterceptor {\n const commonEntryPath = resolvePackageEntryPath(\"@nestjs/common\");\n const { catchError, throwError } = createRequire(commonEntryPath)(\"rxjs\") as Pick<\n typeof rxjs,\n \"catchError\" | \"throwError\"\n >;\n\n return {\n intercept(executionContext: ExecutionContext, next: CallHandler) {\n if (executionContext.getType() !== \"http\") {\n return next.handle();\n }\n return next.handle().pipe(\n catchError((exception: unknown) => {\n const status = getExceptionStatus(exception);\n if (status === undefined || status >= 500) {\n captureException(exception);\n }\n const requestRecord = getRequestRecord();\n const messages = getExceptionResponseMessages(exception);\n if (requestRecord && messages) {\n requestRecord.validationErrors = messages.map((message) => ({\n source: \"\",\n field: \"\",\n message,\n type: \"\",\n }));\n }\n return throwError(() => exception);\n }),\n );\n },\n };\n}\n\nfunction getExceptionStatus(exception: unknown): number | undefined {\n if ((typeof exception !== \"object\" && typeof exception !== \"function\") || exception === null) {\n return undefined;\n }\n try {\n const candidate = exception as {\n getStatus?: () => unknown;\n status?: unknown;\n statusCode?: unknown;\n };\n if (typeof candidate.getStatus === \"function\") {\n const status = candidate.getStatus();\n return typeof status === \"number\" ? status : undefined;\n }\n if (typeof candidate.status === \"number\") {\n return candidate.status;\n }\n return typeof candidate.statusCode === \"number\" ? candidate.statusCode : undefined;\n } catch {\n return undefined;\n }\n}\n\n// The ValidationPipe throws a BadRequestException whose response carries the\n// class-validator messages as an array of strings.\nfunction getExceptionResponseMessages(exception: unknown): string[] | undefined {\n const getResponse = (exception as { getResponse?: unknown } | null)?.getResponse;\n if (typeof getResponse !== \"function\") {\n return undefined;\n }\n const response = getResponse.call(exception) as { message?: unknown } | null;\n const messages = response?.message;\n return Array.isArray(messages) && messages.every((message) => typeof message === \"string\")\n ? messages\n : undefined;\n}\n"],"mappings":";;;;;;;AAmBA,MAAM,qBAAqB,OAAO,IAAI,0BAA0B;AAEhE,SAAgB,YAAY,KAAuB,SAAiC;CAClF,MAAM,cAAc,IAAI,eAAe;CACvC,MAAM,cAAc,YAAY,QAAQ;CACxC,MAAM,kBAAkB,YAAY,YAAY;CAChD,MAAM,gBAAgB;EACpB,WAAW;EACX,kBAAkB,sBAAsB,cAAc;CACxD;CAEA,IAAI,gBAAgB,WAClB,0BAA0B,iBAA4B,SAAS,aAAa;MACvE,IAAI,gBAAgB,WACzB,0BAA0B,iBAAoC,SAAS,aAAa;MAEpF,MAAM,IAAI,UACR,mCAAmC,KAAK,UAAU,WAAW,EAAE,2CACjE;CAGF,MAAM,iBAAiB;CACvB,IAAI,eAAe,wBAAwB,MAAM;EAC/C,IAAI,sBAAsB,2BAA2B,CAAC;EACtD,eAAe,sBAAsB;CACvC;AACF;AAEA,SAAS,6BAA8C;CACrD,MAAM,kBAAkB,wBAAwB,gBAAgB;CAChE,MAAM,EAAE,YAAY,eAAe,cAAc,eAAe,CAAC,CAAC,MAAM;CAKxE,OAAO,EACL,UAAU,kBAAoC,MAAmB;EAC/D,IAAI,iBAAiB,QAAQ,MAAM,QACjC,OAAO,KAAK,OAAO;EAErB,OAAO,KAAK,OAAO,CAAC,CAAC,KACnB,YAAY,cAAuB;GACjC,MAAM,SAAS,mBAAmB,SAAS;GAC3C,IAAI,WAAW,KAAA,KAAa,UAAU,KACpC,iBAAiB,SAAS;GAE5B,MAAM,gBAAgB,iBAAiB;GACvC,MAAM,WAAW,6BAA6B,SAAS;GACvD,IAAI,iBAAiB,UACnB,cAAc,mBAAmB,SAAS,KAAK,aAAa;IAC1D,QAAQ;IACR,OAAO;IACP;IACA,MAAM;GACR,EAAE;GAEJ,OAAO,iBAAiB,SAAS;EACnC,CAAC,CACH;CACF,EACF;AACF;AAEA,SAAS,mBAAmB,WAAwC;CAClE,IAAK,OAAO,cAAc,YAAY,OAAO,cAAc,cAAe,cAAc,MACtF;CAEF,IAAI;EACF,MAAM,YAAY;EAKlB,IAAI,OAAO,UAAU,cAAc,YAAY;GAC7C,MAAM,SAAS,UAAU,UAAU;GACnC,OAAO,OAAO,WAAW,WAAW,SAAS,KAAA;EAC/C;EACA,IAAI,OAAO,UAAU,WAAW,UAC9B,OAAO,UAAU;EAEnB,OAAO,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA;CAC3E,QAAQ;EACN;CACF;AACF;AAIA,SAAS,6BAA6B,WAA0C;CAC9E,MAAM,cAAe,WAAgD;CACrE,IAAI,OAAO,gBAAgB,YACzB;CAGF,MAAM,WADW,YAAY,KAAK,SACV,CAAC,EAAE;CAC3B,OAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,OAAO,YAAY,OAAO,YAAY,QAAQ,IACrF,WACA,KAAA;AACN"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/nestjs/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\nimport type {\n CallHandler,\n ExecutionContext,\n INestApplication,\n NestInterceptor,\n} from \"@nestjs/common\";\nimport type { Express } from \"express\";\nimport type { FastifyInstance } from \"fastify\";\nimport type * as rxjs from \"rxjs\";\nimport { shutdown } from \"../activation.js\";\nimport type { ApitallyOptions } from \"../config.js\";\nimport { getRequestRecord } from \"../context.js\";\nimport { captureException } from \"../exceptions.js\";\nimport { installExpressIntegration } from \"../express/install.js\";\nimport { installFastifyIntegration } from \"../fastify/install.js\";\nimport { resolvePackageEntryPath, resolvePackageVersion } from \"../packageVersion.js\";\n\nexport type { ApitallyOptions };\n\nconst INTERCEPTOR_MARKER = Symbol.for(\"apitally.nestInterceptor\");\n\nexport function useApitally(app: INestApplication, options?: ApitallyOptions): void {\n const httpAdapter = app.getHttpAdapter();\n const adapterType = httpAdapter.getType();\n const adapterInstance = httpAdapter.getInstance() as object;\n const frameworkInfo = {\n framework: \"nestjs\",\n frameworkVersion: resolvePackageVersion(\"@nestjs/core\"),\n };\n\n if (adapterType === \"express\") {\n installExpressIntegration(adapterInstance as Express, options, frameworkInfo);\n } else if (adapterType === \"fastify\") {\n installFastifyIntegration(adapterInstance as FastifyInstance, options, frameworkInfo);\n } else {\n throw new TypeError(\n `Unsupported NestJS HTTP adapter ${JSON.stringify(adapterType)}. Supported adapters: express and fastify.`,\n );\n }\n\n const markedInstance = adapterInstance as Record<symbol, boolean | undefined>;\n if (markedInstance[INTERCEPTOR_MARKER] !== true) {\n app.useGlobalInterceptors(createExceptionInterceptor());\n // Nest awaits adapter.close() before terminating the process on a shutdown signal.\n const close = httpAdapter.close.bind(httpAdapter);\n httpAdapter.close = async () => {\n await close();\n await shutdown();\n };\n markedInstance[INTERCEPTOR_MARKER] = true;\n }\n}\n\nfunction createExceptionInterceptor(): NestInterceptor {\n const commonEntryPath = resolvePackageEntryPath(\"@nestjs/common\");\n const { catchError, throwError } = createRequire(commonEntryPath)(\"rxjs\") as Pick<\n typeof rxjs,\n \"catchError\" | \"throwError\"\n >;\n\n return {\n intercept(executionContext: ExecutionContext, next: CallHandler) {\n if (executionContext.getType() !== \"http\") {\n return next.handle();\n }\n return next.handle().pipe(\n catchError((exception: unknown) => {\n const status = getExceptionStatus(exception);\n if (status === undefined || status >= 500) {\n captureException(exception);\n }\n const requestRecord = getRequestRecord();\n const messages = getExceptionResponseMessages(exception);\n if (requestRecord && messages) {\n requestRecord.validationErrors = messages.map((message) => ({\n source: \"\",\n field: \"\",\n message,\n type: \"\",\n }));\n }\n return throwError(() => exception);\n }),\n );\n },\n };\n}\n\nfunction getExceptionStatus(exception: unknown): number | undefined {\n if ((typeof exception !== \"object\" && typeof exception !== \"function\") || exception === null) {\n return undefined;\n }\n try {\n const candidate = exception as {\n getStatus?: () => unknown;\n status?: unknown;\n statusCode?: unknown;\n };\n if (typeof candidate.getStatus === \"function\") {\n const status = candidate.getStatus();\n return typeof status === \"number\" ? status : undefined;\n }\n if (typeof candidate.status === \"number\") {\n return candidate.status;\n }\n return typeof candidate.statusCode === \"number\" ? candidate.statusCode : undefined;\n } catch {\n return undefined;\n }\n}\n\n// The ValidationPipe throws a BadRequestException whose response carries the\n// class-validator messages as an array of strings.\nfunction getExceptionResponseMessages(exception: unknown): string[] | undefined {\n const getResponse = (exception as { getResponse?: unknown } | null)?.getResponse;\n if (typeof getResponse !== \"function\") {\n return undefined;\n }\n const response = getResponse.call(exception) as { message?: unknown } | null;\n const messages = response?.message;\n return Array.isArray(messages) && messages.every((message) => typeof message === \"string\")\n ? messages\n : undefined;\n}\n"],"mappings":";;;;;;;;AAoBA,MAAM,qBAAqB,OAAO,IAAI,0BAA0B;AAEhE,SAAgB,YAAY,KAAuB,SAAiC;CAClF,MAAM,cAAc,IAAI,eAAe;CACvC,MAAM,cAAc,YAAY,QAAQ;CACxC,MAAM,kBAAkB,YAAY,YAAY;CAChD,MAAM,gBAAgB;EACpB,WAAW;EACX,kBAAkB,sBAAsB,cAAc;CACxD;CAEA,IAAI,gBAAgB,WAClB,0BAA0B,iBAA4B,SAAS,aAAa;MACvE,IAAI,gBAAgB,WACzB,0BAA0B,iBAAoC,SAAS,aAAa;MAEpF,MAAM,IAAI,UACR,mCAAmC,KAAK,UAAU,WAAW,EAAE,2CACjE;CAGF,MAAM,iBAAiB;CACvB,IAAI,eAAe,wBAAwB,MAAM;EAC/C,IAAI,sBAAsB,2BAA2B,CAAC;EAEtD,MAAM,QAAQ,YAAY,MAAM,KAAK,WAAW;EAChD,YAAY,QAAQ,YAAY;GAC9B,MAAM,MAAM;GACZ,MAAM,SAAS;EACjB;EACA,eAAe,sBAAsB;CACvC;AACF;AAEA,SAAS,6BAA8C;CACrD,MAAM,kBAAkB,wBAAwB,gBAAgB;CAChE,MAAM,EAAE,YAAY,eAAe,cAAc,eAAe,CAAC,CAAC,MAAM;CAKxE,OAAO,EACL,UAAU,kBAAoC,MAAmB;EAC/D,IAAI,iBAAiB,QAAQ,MAAM,QACjC,OAAO,KAAK,OAAO;EAErB,OAAO,KAAK,OAAO,CAAC,CAAC,KACnB,YAAY,cAAuB;GACjC,MAAM,SAAS,mBAAmB,SAAS;GAC3C,IAAI,WAAW,KAAA,KAAa,UAAU,KACpC,iBAAiB,SAAS;GAE5B,MAAM,gBAAgB,iBAAiB;GACvC,MAAM,WAAW,6BAA6B,SAAS;GACvD,IAAI,iBAAiB,UACnB,cAAc,mBAAmB,SAAS,KAAK,aAAa;IAC1D,QAAQ;IACR,OAAO;IACP;IACA,MAAM;GACR,EAAE;GAEJ,OAAO,iBAAiB,SAAS;EACnC,CAAC,CACH;CACF,EACF;AACF;AAEA,SAAS,mBAAmB,WAAwC;CAClE,IAAK,OAAO,cAAc,YAAY,OAAO,cAAc,cAAe,cAAc,MACtF;CAEF,IAAI;EACF,MAAM,YAAY;EAKlB,IAAI,OAAO,UAAU,cAAc,YAAY;GAC7C,MAAM,SAAS,UAAU,UAAU;GACnC,OAAO,OAAO,WAAW,WAAW,SAAS,KAAA;EAC/C;EACA,IAAI,OAAO,UAAU,WAAW,UAC9B,OAAO,UAAU;EAEnB,OAAO,OAAO,UAAU,eAAe,WAAW,UAAU,aAAa,KAAA;CAC3E,QAAQ;EACN;CACF;AACF;AAIA,SAAS,6BAA6B,WAA0C;CAC9E,MAAM,cAAe,WAAgD;CACrE,IAAI,OAAO,gBAAgB,YACzB;CAGF,MAAM,WADW,YAAY,KAAK,SACV,CAAC,EAAE;CAC3B,OAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,OAAO,YAAY,OAAO,YAAY,QAAQ,IACrF,WACA,KAAA;AACN"}
@@ -6,7 +6,6 @@ let _opentelemetry_api = require("@opentelemetry/api");
6
6
  let _opentelemetry_sdk_logs = require("@opentelemetry/sdk-logs");
7
7
  let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
8
8
  let _opentelemetry_core = require("@opentelemetry/core");
9
- let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
10
9
  let node_crypto = require("node:crypto");
11
10
  let _opentelemetry_context_async_hooks = require("@opentelemetry/context-async-hooks");
12
11
  let _opentelemetry_resources = require("@opentelemetry/resources");
@@ -58,12 +57,6 @@ function setupTracerProvider(resource, spanProcessors) {
58
57
  _opentelemetry_api.propagation.setGlobalPropagator(new _opentelemetry_core.W3CTraceContextPropagator());
59
58
  return provider;
60
59
  }
61
- function createMeterProvider(resource, readers) {
62
- return new _opentelemetry_sdk_metrics.MeterProvider({
63
- resource,
64
- readers
65
- });
66
- }
67
60
  function createLoggerProvider(resource, processors) {
68
61
  return new _opentelemetry_sdk_logs.LoggerProvider({
69
62
  resource,
@@ -82,7 +75,6 @@ function warnIfContextDoesNotPropagate() {
82
75
  }
83
76
  //#endregion
84
77
  exports.createLoggerProvider = createLoggerProvider;
85
- exports.createMeterProvider = createMeterProvider;
86
78
  exports.resolveEnvAndCreateResource = resolveEnvAndCreateResource;
87
79
  exports.setupTracerProvider = setupTracerProvider;
88
80
 
@@ -1 +1 @@
1
- {"version":3,"file":"providers.cjs","names":["trace","SpanKind","TraceFlags","SamplingDecision","detectResources","envDetector","getConfig","defaultResource","resourceFromAttributes","randomUUID","getDistroVersion","NodeTracerProvider","AsyncLocalStorageContextManager","context","W3CTraceContextPropagator","MeterProvider","LoggerProvider","createContextKey"],"sources":["../src/providers.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n type Context,\n context,\n createContextKey,\n propagation,\n SpanKind,\n TraceFlags,\n trace,\n} from \"@opentelemetry/api\";\nimport { AsyncLocalStorageContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\nimport {\n defaultResource,\n detectResources,\n envDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { LoggerProvider, type LogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { type IMetricReader, MeterProvider } from \"@opentelemetry/sdk-metrics\";\nimport {\n type Sampler,\n SamplingDecision,\n type SamplingResult,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { getConfig } from \"./config.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\n\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 65_536;\nconst DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\";\n\nclass RequestRootedSampler implements Sampler {\n shouldSample(\n parentContext: Context,\n _traceId: string,\n _spanName: string,\n spanKind: SpanKind,\n ): SamplingResult {\n const parent = trace.getSpanContext(parentContext);\n const shouldRecord =\n spanKind === SpanKind.SERVER ||\n (parent !== undefined && !parent.isRemote && (parent.traceFlags & TraceFlags.SAMPLED) !== 0);\n return {\n decision: shouldRecord ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,\n };\n }\n\n toString(): string {\n return \"RequestRootedSampler\";\n }\n}\n\n// Apitally-Env and every signal's resource use the configured environment.\nexport function resolveEnvAndCreateResource(): { env: string; resource: Resource } {\n const environmentResource = detectResources({ detectors: [envDetector] });\n const env = getConfig().env;\n // defaultResource() ignores environment variables. Apitally attributes merge\n // last to keep the resource aligned with Apitally-Env.\n const resource = defaultResource()\n .merge(environmentResource)\n .merge(\n resourceFromAttributes({\n \"service.instance.id\": randomUUID(),\n [DEPLOYMENT_ENVIRONMENT_NAME]: env,\n \"telemetry.distro.name\": \"apitally-js\",\n \"telemetry.distro.version\": getDistroVersion(),\n }),\n );\n return { env, resource };\n}\n\nexport function setupTracerProvider(\n resource: Resource,\n spanProcessors: SpanProcessor[],\n): NodeTracerProvider | undefined {\n // Explicit sampler and length limits prevent OTel environment variables from\n // dropping upstream-unsampled SERVER spans or truncating long attributes.\n const provider = new NodeTracerProvider({\n sampler: new RequestRootedSampler(),\n resource,\n spanProcessors,\n generalLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n spanLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n });\n // OTel global setters reject duplicate or version-mismatched registrations,\n // preserving existing registrations.\n if (!trace.setGlobalTracerProvider(provider)) {\n return undefined;\n }\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n if (!context.setGlobalContextManager(contextManager)) {\n try {\n contextManager.disable();\n } catch (error) {\n logDebug(`Error disabling an unused OpenTelemetry context manager: ${String(error)}`);\n }\n warnIfContextDoesNotPropagate();\n }\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n return provider;\n}\n\n// Meter and logger providers remain private because global registration could\n// replace or race a user's metrics or logs pipeline.\nexport function createMeterProvider(resource: Resource, readers: IMetricReader[]): MeterProvider {\n return new MeterProvider({ resource, readers });\n}\n\nexport function createLoggerProvider(\n resource: Resource,\n processors: LogRecordProcessor[],\n): LoggerProvider {\n return new LoggerProvider({ resource, processors });\n}\n\n// OTel exposes no context-manager getter, so a propagated probe verifies\n// registration. Without propagation, request contexts and suppression fail without an error.\nfunction warnIfContextDoesNotPropagate(): void {\n let isPropagated = false;\n try {\n const probeKey = createContextKey(\"apitally-context-probe\");\n isPropagated = context.with(\n context.active().setValue(probeKey, true),\n () => context.active().getValue(probeKey) === true,\n );\n } catch (error) {\n logDebug(`Error probing OpenTelemetry context propagation: ${String(error)}`);\n }\n if (!isPropagated) {\n logWarning(\n \"OpenTelemetry context propagation is not working, so Apitally cannot associate telemetry with requests. This can happen when conflicting versions of @opentelemetry/api are installed.\",\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAgCA,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AAEpC,IAAM,uBAAN,MAA8C;CAC5C,aACE,eACA,UACA,WACA,UACgB;EAChB,MAAM,SAASA,mBAAAA,MAAM,eAAe,aAAa;EAIjD,OAAO,EACL,UAHA,aAAaC,mBAAAA,SAAS,UACrB,WAAW,KAAA,KAAa,CAAC,OAAO,aAAa,OAAO,aAAaC,mBAAAA,WAAW,aAAa,IAEjEC,8BAAAA,iBAAiB,qBAAqBA,8BAAAA,iBAAiB,WAClF;CACF;CAEA,WAAmB;EACjB,OAAO;CACT;AACF;AAGA,SAAgB,8BAAmE;CACjF,MAAM,uBAAA,GAAsBC,yBAAAA,gBAAAA,CAAgB,EAAE,WAAW,CAACC,yBAAAA,WAAW,EAAE,CAAC;CACxE,MAAM,MAAMC,eAAAA,UAAU,CAAC,CAAC;CAaxB,OAAO;EAAE;EAAK,WAAA,GAVGC,yBAAAA,gBAAAA,CAAgB,CAAC,CAC/B,MAAM,mBAAmB,CAAC,CAC1B,OAAA,GACCC,yBAAAA,uBAAAA,CAAuB;GACrB,wBAAA,GAAuBC,YAAAA,WAAAA,CAAW;IACjC,8BAA8B;GAC/B,yBAAyB;GACzB,4BAA4BC,uBAAAA,iBAAiB;EAC/C,CAAC,CAEgB;CAAE;AACzB;AAEA,SAAgB,oBACd,UACA,gBACgC;CAGhC,MAAM,WAAW,IAAIC,8BAAAA,mBAAmB;EACtC,SAAS,IAAI,qBAAqB;EAClC;EACA;EACA,eAAe,EAAE,2BAA2B,2BAA2B;EACvE,YAAY,EAAE,2BAA2B,2BAA2B;CACtE,CAAC;CAGD,IAAI,CAACX,mBAAAA,MAAM,wBAAwB,QAAQ,GACzC;CAEF,MAAM,iBAAiB,IAAIY,mCAAAA,gCAAgC;CAC3D,eAAe,OAAO;CACtB,IAAI,CAACC,mBAAAA,QAAQ,wBAAwB,cAAc,GAAG;EACpD,IAAI;GACF,eAAe,QAAQ;EACzB,SAAS,OAAO;GACd,eAAA,SAAS,4DAA4D,OAAO,KAAK,GAAG;EACtF;EACA,8BAA8B;CAChC;CACA,mBAAA,YAAY,oBAAoB,IAAIC,oBAAAA,0BAA0B,CAAC;CAC/D,OAAO;AACT;AAIA,SAAgB,oBAAoB,UAAoB,SAAyC;CAC/F,OAAO,IAAIC,2BAAAA,cAAc;EAAE;EAAU;CAAQ,CAAC;AAChD;AAEA,SAAgB,qBACd,UACA,YACgB;CAChB,OAAO,IAAIC,wBAAAA,eAAe;EAAE;EAAU;CAAW,CAAC;AACpD;AAIA,SAAS,gCAAsC;CAC7C,IAAI,eAAe;CACnB,IAAI;EACF,MAAM,YAAA,GAAWC,mBAAAA,iBAAAA,CAAiB,wBAAwB;EAC1D,eAAeJ,mBAAAA,QAAQ,KACrBA,mBAAAA,QAAQ,OAAO,CAAC,CAAC,SAAS,UAAU,IAAI,SAClCA,mBAAAA,QAAQ,OAAO,CAAC,CAAC,SAAS,QAAQ,MAAM,IAChD;CACF,SAAS,OAAO;EACd,eAAA,SAAS,oDAAoD,OAAO,KAAK,GAAG;CAC9E;CACA,IAAI,CAAC,cACH,eAAA,WACE,wLACF;AAEJ"}
1
+ {"version":3,"file":"providers.cjs","names":["trace","SpanKind","TraceFlags","SamplingDecision","detectResources","envDetector","getConfig","defaultResource","resourceFromAttributes","randomUUID","getDistroVersion","NodeTracerProvider","AsyncLocalStorageContextManager","context","W3CTraceContextPropagator","LoggerProvider","createContextKey"],"sources":["../src/providers.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n type Context,\n context,\n createContextKey,\n propagation,\n SpanKind,\n TraceFlags,\n trace,\n} from \"@opentelemetry/api\";\nimport { AsyncLocalStorageContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\nimport {\n defaultResource,\n detectResources,\n envDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { LoggerProvider, type LogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport {\n type Sampler,\n SamplingDecision,\n type SamplingResult,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { getConfig } from \"./config.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\n\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 65_536;\nconst DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\";\n\nclass RequestRootedSampler implements Sampler {\n shouldSample(\n parentContext: Context,\n _traceId: string,\n _spanName: string,\n spanKind: SpanKind,\n ): SamplingResult {\n const parent = trace.getSpanContext(parentContext);\n const shouldRecord =\n spanKind === SpanKind.SERVER ||\n (parent !== undefined && !parent.isRemote && (parent.traceFlags & TraceFlags.SAMPLED) !== 0);\n return {\n decision: shouldRecord ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,\n };\n }\n\n toString(): string {\n return \"RequestRootedSampler\";\n }\n}\n\n// Apitally-Env and every signal's resource use the configured environment.\nexport function resolveEnvAndCreateResource(): { env: string; resource: Resource } {\n const environmentResource = detectResources({ detectors: [envDetector] });\n const env = getConfig().env;\n // defaultResource() ignores environment variables. Apitally attributes merge\n // last to keep the resource aligned with Apitally-Env.\n const resource = defaultResource()\n .merge(environmentResource)\n .merge(\n resourceFromAttributes({\n \"service.instance.id\": randomUUID(),\n [DEPLOYMENT_ENVIRONMENT_NAME]: env,\n \"telemetry.distro.name\": \"apitally-js\",\n \"telemetry.distro.version\": getDistroVersion(),\n }),\n );\n return { env, resource };\n}\n\nexport function setupTracerProvider(\n resource: Resource,\n spanProcessors: SpanProcessor[],\n): NodeTracerProvider | undefined {\n // Explicit sampler and length limits prevent OTel environment variables from\n // dropping upstream-unsampled SERVER spans or truncating long attributes.\n const provider = new NodeTracerProvider({\n sampler: new RequestRootedSampler(),\n resource,\n spanProcessors,\n generalLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n spanLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n });\n // OTel global setters reject duplicate or version-mismatched registrations,\n // preserving existing registrations.\n if (!trace.setGlobalTracerProvider(provider)) {\n return undefined;\n }\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n if (!context.setGlobalContextManager(contextManager)) {\n try {\n contextManager.disable();\n } catch (error) {\n logDebug(`Error disabling an unused OpenTelemetry context manager: ${String(error)}`);\n }\n warnIfContextDoesNotPropagate();\n }\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n return provider;\n}\n\n// The logger provider remains private because global registration could replace\n// or race a user's logs pipeline.\nexport function createLoggerProvider(\n resource: Resource,\n processors: LogRecordProcessor[],\n): LoggerProvider {\n return new LoggerProvider({ resource, processors });\n}\n\n// OTel exposes no context-manager getter, so a propagated probe verifies\n// registration. Without propagation, request contexts and suppression fail without an error.\nfunction warnIfContextDoesNotPropagate(): void {\n let isPropagated = false;\n try {\n const probeKey = createContextKey(\"apitally-context-probe\");\n isPropagated = context.with(\n context.active().setValue(probeKey, true),\n () => context.active().getValue(probeKey) === true,\n );\n } catch (error) {\n logDebug(`Error probing OpenTelemetry context propagation: ${String(error)}`);\n }\n if (!isPropagated) {\n logWarning(\n \"OpenTelemetry context propagation is not working, so Apitally cannot associate telemetry with requests. This can happen when conflicting versions of @opentelemetry/api are installed.\",\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA+BA,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AAEpC,IAAM,uBAAN,MAA8C;CAC5C,aACE,eACA,UACA,WACA,UACgB;EAChB,MAAM,SAASA,mBAAAA,MAAM,eAAe,aAAa;EAIjD,OAAO,EACL,UAHA,aAAaC,mBAAAA,SAAS,UACrB,WAAW,KAAA,KAAa,CAAC,OAAO,aAAa,OAAO,aAAaC,mBAAAA,WAAW,aAAa,IAEjEC,8BAAAA,iBAAiB,qBAAqBA,8BAAAA,iBAAiB,WAClF;CACF;CAEA,WAAmB;EACjB,OAAO;CACT;AACF;AAGA,SAAgB,8BAAmE;CACjF,MAAM,uBAAA,GAAsBC,yBAAAA,gBAAAA,CAAgB,EAAE,WAAW,CAACC,yBAAAA,WAAW,EAAE,CAAC;CACxE,MAAM,MAAMC,eAAAA,UAAU,CAAC,CAAC;CAaxB,OAAO;EAAE;EAAK,WAAA,GAVGC,yBAAAA,gBAAAA,CAAgB,CAAC,CAC/B,MAAM,mBAAmB,CAAC,CAC1B,OAAA,GACCC,yBAAAA,uBAAAA,CAAuB;GACrB,wBAAA,GAAuBC,YAAAA,WAAAA,CAAW;IACjC,8BAA8B;GAC/B,yBAAyB;GACzB,4BAA4BC,uBAAAA,iBAAiB;EAC/C,CAAC,CAEgB;CAAE;AACzB;AAEA,SAAgB,oBACd,UACA,gBACgC;CAGhC,MAAM,WAAW,IAAIC,8BAAAA,mBAAmB;EACtC,SAAS,IAAI,qBAAqB;EAClC;EACA;EACA,eAAe,EAAE,2BAA2B,2BAA2B;EACvE,YAAY,EAAE,2BAA2B,2BAA2B;CACtE,CAAC;CAGD,IAAI,CAACX,mBAAAA,MAAM,wBAAwB,QAAQ,GACzC;CAEF,MAAM,iBAAiB,IAAIY,mCAAAA,gCAAgC;CAC3D,eAAe,OAAO;CACtB,IAAI,CAACC,mBAAAA,QAAQ,wBAAwB,cAAc,GAAG;EACpD,IAAI;GACF,eAAe,QAAQ;EACzB,SAAS,OAAO;GACd,eAAA,SAAS,4DAA4D,OAAO,KAAK,GAAG;EACtF;EACA,8BAA8B;CAChC;CACA,mBAAA,YAAY,oBAAoB,IAAIC,oBAAAA,0BAA0B,CAAC;CAC/D,OAAO;AACT;AAIA,SAAgB,qBACd,UACA,YACgB;CAChB,OAAO,IAAIC,wBAAAA,eAAe;EAAE;EAAU;CAAW,CAAC;AACpD;AAIA,SAAS,gCAAsC;CAC7C,IAAI,eAAe;CACnB,IAAI;EACF,MAAM,YAAA,GAAWC,mBAAAA,iBAAAA,CAAiB,wBAAwB;EAC1D,eAAeH,mBAAAA,QAAQ,KACrBA,mBAAAA,QAAQ,OAAO,CAAC,CAAC,SAAS,UAAU,IAAI,SAClCA,mBAAAA,QAAQ,OAAO,CAAC,CAAC,SAAS,QAAQ,MAAM,IAChD;CACF,SAAS,OAAO;EACd,eAAA,SAAS,oDAAoD,OAAO,KAAK,GAAG;CAC9E;CACA,IAAI,CAAC,cACH,eAAA,WACE,wLACF;AAEJ"}
@@ -2,14 +2,12 @@ import { LogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
2
2
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
3
3
  import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
4
4
  import { Resource } from "@opentelemetry/resources";
5
- import { IMetricReader, MeterProvider } from "@opentelemetry/sdk-metrics";
6
5
  //#region src/providers.d.ts
7
6
  export declare function resolveEnvAndCreateResource(): {
8
7
  env: string;
9
8
  resource: Resource;
10
9
  };
11
10
  export declare function setupTracerProvider(resource: Resource, spanProcessors: SpanProcessor[]): NodeTracerProvider | undefined;
12
- export declare function createMeterProvider(resource: Resource, readers: IMetricReader[]): MeterProvider;
13
11
  export declare function createLoggerProvider(resource: Resource, processors: LogRecordProcessor[]): LoggerProvider;
14
12
  //#endregion
15
13
  //# sourceMappingURL=providers.d.cts.map
@@ -1,6 +1,5 @@
1
1
  import { LogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
2
2
  import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
3
- import { IMetricReader, MeterProvider } from "@opentelemetry/sdk-metrics";
4
3
  import { Resource } from "@opentelemetry/resources";
5
4
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
6
5
  //#region src/providers.d.ts
@@ -9,7 +8,6 @@ export declare function resolveEnvAndCreateResource(): {
9
8
  resource: Resource;
10
9
  };
11
10
  export declare function setupTracerProvider(resource: Resource, spanProcessors: SpanProcessor[]): NodeTracerProvider | undefined;
12
- export declare function createMeterProvider(resource: Resource, readers: IMetricReader[]): MeterProvider;
13
11
  export declare function createLoggerProvider(resource: Resource, processors: LogRecordProcessor[]): LoggerProvider;
14
12
  //#endregion
15
13
  //# sourceMappingURL=providers.d.ts.map
package/dist/providers.js CHANGED
@@ -5,7 +5,6 @@ import { SpanKind, TraceFlags, context, createContextKey, propagation, trace } f
5
5
  import { LoggerProvider } from "@opentelemetry/sdk-logs";
6
6
  import { SamplingDecision } from "@opentelemetry/sdk-trace-base";
7
7
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
8
- import { MeterProvider } from "@opentelemetry/sdk-metrics";
9
8
  import { randomUUID } from "node:crypto";
10
9
  import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
11
10
  import { defaultResource, detectResources, envDetector, resourceFromAttributes } from "@opentelemetry/resources";
@@ -57,12 +56,6 @@ function setupTracerProvider(resource, spanProcessors) {
57
56
  propagation.setGlobalPropagator(new W3CTraceContextPropagator());
58
57
  return provider;
59
58
  }
60
- function createMeterProvider(resource, readers) {
61
- return new MeterProvider({
62
- resource,
63
- readers
64
- });
65
- }
66
59
  function createLoggerProvider(resource, processors) {
67
60
  return new LoggerProvider({
68
61
  resource,
@@ -80,6 +73,6 @@ function warnIfContextDoesNotPropagate() {
80
73
  if (!isPropagated) logWarning("OpenTelemetry context propagation is not working, so Apitally cannot associate telemetry with requests. This can happen when conflicting versions of @opentelemetry/api are installed.");
81
74
  }
82
75
  //#endregion
83
- export { createLoggerProvider, createMeterProvider, resolveEnvAndCreateResource, setupTracerProvider };
76
+ export { createLoggerProvider, resolveEnvAndCreateResource, setupTracerProvider };
84
77
 
85
78
  //# sourceMappingURL=providers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.js","names":[],"sources":["../src/providers.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n type Context,\n context,\n createContextKey,\n propagation,\n SpanKind,\n TraceFlags,\n trace,\n} from \"@opentelemetry/api\";\nimport { AsyncLocalStorageContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\nimport {\n defaultResource,\n detectResources,\n envDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { LoggerProvider, type LogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { type IMetricReader, MeterProvider } from \"@opentelemetry/sdk-metrics\";\nimport {\n type Sampler,\n SamplingDecision,\n type SamplingResult,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { getConfig } from \"./config.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\n\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 65_536;\nconst DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\";\n\nclass RequestRootedSampler implements Sampler {\n shouldSample(\n parentContext: Context,\n _traceId: string,\n _spanName: string,\n spanKind: SpanKind,\n ): SamplingResult {\n const parent = trace.getSpanContext(parentContext);\n const shouldRecord =\n spanKind === SpanKind.SERVER ||\n (parent !== undefined && !parent.isRemote && (parent.traceFlags & TraceFlags.SAMPLED) !== 0);\n return {\n decision: shouldRecord ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,\n };\n }\n\n toString(): string {\n return \"RequestRootedSampler\";\n }\n}\n\n// Apitally-Env and every signal's resource use the configured environment.\nexport function resolveEnvAndCreateResource(): { env: string; resource: Resource } {\n const environmentResource = detectResources({ detectors: [envDetector] });\n const env = getConfig().env;\n // defaultResource() ignores environment variables. Apitally attributes merge\n // last to keep the resource aligned with Apitally-Env.\n const resource = defaultResource()\n .merge(environmentResource)\n .merge(\n resourceFromAttributes({\n \"service.instance.id\": randomUUID(),\n [DEPLOYMENT_ENVIRONMENT_NAME]: env,\n \"telemetry.distro.name\": \"apitally-js\",\n \"telemetry.distro.version\": getDistroVersion(),\n }),\n );\n return { env, resource };\n}\n\nexport function setupTracerProvider(\n resource: Resource,\n spanProcessors: SpanProcessor[],\n): NodeTracerProvider | undefined {\n // Explicit sampler and length limits prevent OTel environment variables from\n // dropping upstream-unsampled SERVER spans or truncating long attributes.\n const provider = new NodeTracerProvider({\n sampler: new RequestRootedSampler(),\n resource,\n spanProcessors,\n generalLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n spanLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n });\n // OTel global setters reject duplicate or version-mismatched registrations,\n // preserving existing registrations.\n if (!trace.setGlobalTracerProvider(provider)) {\n return undefined;\n }\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n if (!context.setGlobalContextManager(contextManager)) {\n try {\n contextManager.disable();\n } catch (error) {\n logDebug(`Error disabling an unused OpenTelemetry context manager: ${String(error)}`);\n }\n warnIfContextDoesNotPropagate();\n }\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n return provider;\n}\n\n// Meter and logger providers remain private because global registration could\n// replace or race a user's metrics or logs pipeline.\nexport function createMeterProvider(resource: Resource, readers: IMetricReader[]): MeterProvider {\n return new MeterProvider({ resource, readers });\n}\n\nexport function createLoggerProvider(\n resource: Resource,\n processors: LogRecordProcessor[],\n): LoggerProvider {\n return new LoggerProvider({ resource, processors });\n}\n\n// OTel exposes no context-manager getter, so a propagated probe verifies\n// registration. Without propagation, request contexts and suppression fail without an error.\nfunction warnIfContextDoesNotPropagate(): void {\n let isPropagated = false;\n try {\n const probeKey = createContextKey(\"apitally-context-probe\");\n isPropagated = context.with(\n context.active().setValue(probeKey, true),\n () => context.active().getValue(probeKey) === true,\n );\n } catch (error) {\n logDebug(`Error probing OpenTelemetry context propagation: ${String(error)}`);\n }\n if (!isPropagated) {\n logWarning(\n \"OpenTelemetry context propagation is not working, so Apitally cannot associate telemetry with requests. This can happen when conflicting versions of @opentelemetry/api are installed.\",\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAgCA,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AAEpC,IAAM,uBAAN,MAA8C;CAC5C,aACE,eACA,UACA,WACA,UACgB;EAChB,MAAM,SAAS,MAAM,eAAe,aAAa;EAIjD,OAAO,EACL,UAHA,aAAa,SAAS,UACrB,WAAW,KAAA,KAAa,CAAC,OAAO,aAAa,OAAO,aAAa,WAAW,aAAa,IAEjE,iBAAiB,qBAAqB,iBAAiB,WAClF;CACF;CAEA,WAAmB;EACjB,OAAO;CACT;AACF;AAGA,SAAgB,8BAAmE;CACjF,MAAM,sBAAsB,gBAAgB,EAAE,WAAW,CAAC,WAAW,EAAE,CAAC;CACxE,MAAM,MAAM,UAAU,CAAC,CAAC;CAaxB,OAAO;EAAE;EAAK,UAVG,gBAAgB,CAAC,CAC/B,MAAM,mBAAmB,CAAC,CAC1B,MACC,uBAAuB;GACrB,uBAAuB,WAAW;IACjC,8BAA8B;GAC/B,yBAAyB;GACzB,4BAA4B,iBAAiB;EAC/C,CAAC,CAEgB;CAAE;AACzB;AAEA,SAAgB,oBACd,UACA,gBACgC;CAGhC,MAAM,WAAW,IAAI,mBAAmB;EACtC,SAAS,IAAI,qBAAqB;EAClC;EACA;EACA,eAAe,EAAE,2BAA2B,2BAA2B;EACvE,YAAY,EAAE,2BAA2B,2BAA2B;CACtE,CAAC;CAGD,IAAI,CAAC,MAAM,wBAAwB,QAAQ,GACzC;CAEF,MAAM,iBAAiB,IAAI,gCAAgC;CAC3D,eAAe,OAAO;CACtB,IAAI,CAAC,QAAQ,wBAAwB,cAAc,GAAG;EACpD,IAAI;GACF,eAAe,QAAQ;EACzB,SAAS,OAAO;GACd,SAAS,4DAA4D,OAAO,KAAK,GAAG;EACtF;EACA,8BAA8B;CAChC;CACA,YAAY,oBAAoB,IAAI,0BAA0B,CAAC;CAC/D,OAAO;AACT;AAIA,SAAgB,oBAAoB,UAAoB,SAAyC;CAC/F,OAAO,IAAI,cAAc;EAAE;EAAU;CAAQ,CAAC;AAChD;AAEA,SAAgB,qBACd,UACA,YACgB;CAChB,OAAO,IAAI,eAAe;EAAE;EAAU;CAAW,CAAC;AACpD;AAIA,SAAS,gCAAsC;CAC7C,IAAI,eAAe;CACnB,IAAI;EACF,MAAM,WAAW,iBAAiB,wBAAwB;EAC1D,eAAe,QAAQ,KACrB,QAAQ,OAAO,CAAC,CAAC,SAAS,UAAU,IAAI,SAClC,QAAQ,OAAO,CAAC,CAAC,SAAS,QAAQ,MAAM,IAChD;CACF,SAAS,OAAO;EACd,SAAS,oDAAoD,OAAO,KAAK,GAAG;CAC9E;CACA,IAAI,CAAC,cACH,WACE,wLACF;AAEJ"}
1
+ {"version":3,"file":"providers.js","names":[],"sources":["../src/providers.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport {\n type Context,\n context,\n createContextKey,\n propagation,\n SpanKind,\n TraceFlags,\n trace,\n} from \"@opentelemetry/api\";\nimport { AsyncLocalStorageContextManager } from \"@opentelemetry/context-async-hooks\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\nimport {\n defaultResource,\n detectResources,\n envDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { LoggerProvider, type LogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport {\n type Sampler,\n SamplingDecision,\n type SamplingResult,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { NodeTracerProvider } from \"@opentelemetry/sdk-trace-node\";\nimport { getConfig } from \"./config.js\";\nimport { logDebug, logWarning } from \"./logger.js\";\nimport { getDistroVersion } from \"./packageVersion.js\";\n\nconst MAX_ATTRIBUTE_VALUE_LENGTH = 65_536;\nconst DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\";\n\nclass RequestRootedSampler implements Sampler {\n shouldSample(\n parentContext: Context,\n _traceId: string,\n _spanName: string,\n spanKind: SpanKind,\n ): SamplingResult {\n const parent = trace.getSpanContext(parentContext);\n const shouldRecord =\n spanKind === SpanKind.SERVER ||\n (parent !== undefined && !parent.isRemote && (parent.traceFlags & TraceFlags.SAMPLED) !== 0);\n return {\n decision: shouldRecord ? SamplingDecision.RECORD_AND_SAMPLED : SamplingDecision.NOT_RECORD,\n };\n }\n\n toString(): string {\n return \"RequestRootedSampler\";\n }\n}\n\n// Apitally-Env and every signal's resource use the configured environment.\nexport function resolveEnvAndCreateResource(): { env: string; resource: Resource } {\n const environmentResource = detectResources({ detectors: [envDetector] });\n const env = getConfig().env;\n // defaultResource() ignores environment variables. Apitally attributes merge\n // last to keep the resource aligned with Apitally-Env.\n const resource = defaultResource()\n .merge(environmentResource)\n .merge(\n resourceFromAttributes({\n \"service.instance.id\": randomUUID(),\n [DEPLOYMENT_ENVIRONMENT_NAME]: env,\n \"telemetry.distro.name\": \"apitally-js\",\n \"telemetry.distro.version\": getDistroVersion(),\n }),\n );\n return { env, resource };\n}\n\nexport function setupTracerProvider(\n resource: Resource,\n spanProcessors: SpanProcessor[],\n): NodeTracerProvider | undefined {\n // Explicit sampler and length limits prevent OTel environment variables from\n // dropping upstream-unsampled SERVER spans or truncating long attributes.\n const provider = new NodeTracerProvider({\n sampler: new RequestRootedSampler(),\n resource,\n spanProcessors,\n generalLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n spanLimits: { attributeValueLengthLimit: MAX_ATTRIBUTE_VALUE_LENGTH },\n });\n // OTel global setters reject duplicate or version-mismatched registrations,\n // preserving existing registrations.\n if (!trace.setGlobalTracerProvider(provider)) {\n return undefined;\n }\n const contextManager = new AsyncLocalStorageContextManager();\n contextManager.enable();\n if (!context.setGlobalContextManager(contextManager)) {\n try {\n contextManager.disable();\n } catch (error) {\n logDebug(`Error disabling an unused OpenTelemetry context manager: ${String(error)}`);\n }\n warnIfContextDoesNotPropagate();\n }\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n return provider;\n}\n\n// The logger provider remains private because global registration could replace\n// or race a user's logs pipeline.\nexport function createLoggerProvider(\n resource: Resource,\n processors: LogRecordProcessor[],\n): LoggerProvider {\n return new LoggerProvider({ resource, processors });\n}\n\n// OTel exposes no context-manager getter, so a propagated probe verifies\n// registration. Without propagation, request contexts and suppression fail without an error.\nfunction warnIfContextDoesNotPropagate(): void {\n let isPropagated = false;\n try {\n const probeKey = createContextKey(\"apitally-context-probe\");\n isPropagated = context.with(\n context.active().setValue(probeKey, true),\n () => context.active().getValue(probeKey) === true,\n );\n } catch (error) {\n logDebug(`Error probing OpenTelemetry context propagation: ${String(error)}`);\n }\n if (!isPropagated) {\n logWarning(\n \"OpenTelemetry context propagation is not working, so Apitally cannot associate telemetry with requests. This can happen when conflicting versions of @opentelemetry/api are installed.\",\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;AA+BA,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AAEpC,IAAM,uBAAN,MAA8C;CAC5C,aACE,eACA,UACA,WACA,UACgB;EAChB,MAAM,SAAS,MAAM,eAAe,aAAa;EAIjD,OAAO,EACL,UAHA,aAAa,SAAS,UACrB,WAAW,KAAA,KAAa,CAAC,OAAO,aAAa,OAAO,aAAa,WAAW,aAAa,IAEjE,iBAAiB,qBAAqB,iBAAiB,WAClF;CACF;CAEA,WAAmB;EACjB,OAAO;CACT;AACF;AAGA,SAAgB,8BAAmE;CACjF,MAAM,sBAAsB,gBAAgB,EAAE,WAAW,CAAC,WAAW,EAAE,CAAC;CACxE,MAAM,MAAM,UAAU,CAAC,CAAC;CAaxB,OAAO;EAAE;EAAK,UAVG,gBAAgB,CAAC,CAC/B,MAAM,mBAAmB,CAAC,CAC1B,MACC,uBAAuB;GACrB,uBAAuB,WAAW;IACjC,8BAA8B;GAC/B,yBAAyB;GACzB,4BAA4B,iBAAiB;EAC/C,CAAC,CAEgB;CAAE;AACzB;AAEA,SAAgB,oBACd,UACA,gBACgC;CAGhC,MAAM,WAAW,IAAI,mBAAmB;EACtC,SAAS,IAAI,qBAAqB;EAClC;EACA;EACA,eAAe,EAAE,2BAA2B,2BAA2B;EACvE,YAAY,EAAE,2BAA2B,2BAA2B;CACtE,CAAC;CAGD,IAAI,CAAC,MAAM,wBAAwB,QAAQ,GACzC;CAEF,MAAM,iBAAiB,IAAI,gCAAgC;CAC3D,eAAe,OAAO;CACtB,IAAI,CAAC,QAAQ,wBAAwB,cAAc,GAAG;EACpD,IAAI;GACF,eAAe,QAAQ;EACzB,SAAS,OAAO;GACd,SAAS,4DAA4D,OAAO,KAAK,GAAG;EACtF;EACA,8BAA8B;CAChC;CACA,YAAY,oBAAoB,IAAI,0BAA0B,CAAC;CAC/D,OAAO;AACT;AAIA,SAAgB,qBACd,UACA,YACgB;CAChB,OAAO,IAAI,eAAe;EAAE;EAAU;CAAW,CAAC;AACpD;AAIA,SAAS,gCAAsC;CAC7C,IAAI,eAAe;CACnB,IAAI;EACF,MAAM,WAAW,iBAAiB,wBAAwB;EAC1D,eAAe,QAAQ,KACrB,QAAQ,OAAO,CAAC,CAAC,SAAS,UAAU,IAAI,SAClC,QAAQ,OAAO,CAAC,CAAC,SAAS,QAAQ,MAAM,IAChD;CACF,SAAS,OAAO;EACd,SAAS,oDAAoD,OAAO,KAAK,GAAG;CAC9E;CACA,IAAI,CAAC,cACH,WACE,wLACF;AAEJ"}
@@ -7,6 +7,7 @@ const require_exceptions = require("./exceptions.cjs");
7
7
  const require_serverErrors = require("./serverErrors.cjs");
8
8
  const require_spanProcessor = require("./spanProcessor.cjs");
9
9
  const require_validationErrors = require("./validationErrors.cjs");
10
+ const require_consumers = require("./consumers.cjs");
10
11
  let _opentelemetry_api = require("@opentelemetry/api");
11
12
  let _opentelemetry_core = require("@opentelemetry/core");
12
13
  //#region src/requestObservation.ts
@@ -29,7 +30,6 @@ function startRequestObservation(options) {
29
30
  const { activeContext, extractedContext, tracerName, method, startAttributes, requestBodyCapture } = options;
30
31
  const requestRecord = { attributes: {} };
31
32
  const spanHandle = {};
32
- const consumerHolder = require_context.getConsumerHolder(activeContext) ?? {};
33
33
  Object.assign(requestRecord.attributes, startAttributes);
34
34
  const activeSpan = _opentelemetry_api.trace.getSpan(activeContext);
35
35
  const resolveUnavailableSpanDropReason = () => require_spanProcessor.getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ?? "sampled-out";
@@ -39,13 +39,13 @@ function startRequestObservation(options) {
39
39
  requestRecord.serverSpanId = activeSpan.spanContext().spanId;
40
40
  require_spanProcessor.getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);
41
41
  if (require_spanProcessor.getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) requestRecord.dropReason = resolveUnavailableSpanDropReason();
42
- requestContext = require_context.withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);
42
+ requestContext = require_context.withRequestHolders(activeContext, spanHandle, requestRecord);
43
43
  } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {
44
44
  requestRecord.dropReason = resolveUnavailableSpanDropReason();
45
45
  if (requestRecord.dropReason === "sampled-out") warnAboutNonRecordingServerSpan();
46
- requestContext = require_context.withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);
46
+ requestContext = require_context.withRequestHolders(activeContext, spanHandle, requestRecord);
47
47
  } else {
48
- requestContext = require_context.withRequestHolders(extractedContext, spanHandle, requestRecord, consumerHolder);
48
+ requestContext = require_context.withRequestHolders(extractedContext, spanHandle, requestRecord);
49
49
  const ownSpan = _opentelemetry_api.trace.getTracer(tracerName).startSpan(method, {
50
50
  kind: _opentelemetry_api.SpanKind.SERVER,
51
51
  attributes: startAttributes
@@ -122,6 +122,7 @@ function finalizeRequestObservation(options) {
122
122
  if (Object.keys(stash).length > 0) require_spanProcessor.getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);
123
123
  }
124
124
  ownSpan?.end(options.completedAtMillis);
125
+ require_consumers.emitConsumerUpdateIfChanged(requestRecord);
125
126
  require_spanProcessor.getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);
126
127
  }
127
128
  function finalizeRequestObservationWithError(options) {
@@ -132,6 +133,7 @@ function finalizeRequestObservationWithError(options) {
132
133
  spanHandle.ownSpan.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
133
134
  spanHandle.ownSpan.end();
134
135
  }
136
+ require_consumers.emitConsumerUpdateIfChanged(requestRecord);
135
137
  require_spanProcessor.getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);
136
138
  }
137
139
  function getHeaderValue(headers, name) {
@@ -1 +1 @@
1
- {"version":3,"file":"requestObservation.cjs","names":["getConsumerHolder","trace","getActiveSpanPipeline","SpanKind","withRequestHolders","isApitallySpanProcessorDeclared","getRPCMetadata","RPCType","setRPCMetadata","SpanStatusCode","isValidationResponseStatus","getConfig","coerceToException"],"sources":["../src/requestObservation.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType, setRPCMetadata } from \"@opentelemetry/core\";\nimport type { BodyCapture, CapturedBody } from \"./bodyCapture.js\";\nimport { getConfig } from \"./config.js\";\nimport {\n attachRequestRecordToRpcMetadata,\n getConsumerHolder,\n type RequestRecord,\n type SpanHandle,\n withRequestHolders,\n} from \"./context.js\";\nimport { coerceToException } from \"./exceptions.js\";\nimport { logWarning } from \"./logger.js\";\nimport { writeRequestAttribute } from \"./requestAttributes.js\";\nimport { addServerError } from \"./serverErrors.js\";\nimport {\n getActiveSpanPipeline,\n isApitallySpanProcessorDeclared,\n type RequestStash,\n} from \"./spanProcessor.js\";\nimport { addValidationErrors, isValidationResponseStatus } from \"./validationErrors.js\";\n\ninterface StartRequestObservationOptions {\n activeContext: Context;\n extractedContext: Context;\n tracerName: string;\n method: string;\n startAttributes: Attributes;\n requestBodyCapture: BodyCapture;\n}\n\nexport interface StartedRequestObservation {\n requestRecord: RequestRecord;\n requestContext: Context;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n}\n\nexport interface HttpRequestStartAttributeInput {\n method: string;\n path?: string;\n query?: string;\n scheme?: string;\n serverAddress?: string;\n fullUrl?: string;\n clientAddress?: string;\n userAgent?: string;\n requestBodySize?: number;\n}\n\nexport function resolveHttpRequestStartAttributes(\n input: HttpRequestStartAttributeInput,\n): Attributes {\n const attributes: Attributes = { \"http.request.method\": input.method };\n const optionalAttributes: [string, string | number | undefined][] = [\n [\"url.path\", input.path],\n [\"url.query\", input.query],\n [\"url.scheme\", input.scheme],\n [\"url.full\", input.fullUrl],\n [\"server.address\", input.serverAddress],\n [\"client.address\", input.clientAddress],\n [\"user_agent.original\", input.userAgent],\n [\"http.request.body.size\", input.requestBodySize],\n ];\n for (const [name, value] of optionalAttributes) {\n if (value !== undefined) {\n attributes[name] = value;\n }\n }\n return attributes;\n}\n\nexport function startRequestObservation(\n options: StartRequestObservationOptions,\n): StartedRequestObservation {\n const {\n activeContext,\n extractedContext,\n tracerName,\n method,\n startAttributes,\n requestBodyCapture,\n } = options;\n const requestRecord: RequestRecord = { attributes: {} };\n const spanHandle: SpanHandle = {};\n const consumerHolder = getConsumerHolder(activeContext) ?? {};\n // Metrics and the exported span copy read from the request record, so the\n // start attributes are mirrored into it on every path, span or no span.\n Object.assign(requestRecord.attributes, startAttributes);\n const activeSpan = trace.getSpan(activeContext);\n const resolveUnavailableSpanDropReason = (): RequestRecord[\"dropReason\"] =>\n getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ??\n \"sampled-out\";\n let requestContext: Context;\n // Span kind is not part of the OTel API surface, so read the SDK-level\n // property from whichever package copy produced the span.\n if (activeSpan?.isRecording() && (activeSpan as { kind?: unknown }).kind === SpanKind.SERVER) {\n // A SERVER span produced by the user's own instrumentation is adopted:\n // no second span, and the request runs under the user's context.\n spanHandle.span = activeSpan;\n requestRecord.serverSpanId = activeSpan.spanContext().spanId;\n getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);\n if (getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);\n } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);\n } else {\n requestContext = withRequestHolders(\n extractedContext,\n spanHandle,\n requestRecord,\n consumerHolder,\n );\n const ownSpan = trace\n .getTracer(tracerName)\n .startSpan(method, { kind: SpanKind.SERVER, attributes: startAttributes }, requestContext);\n if (!ownSpan.isRecording()) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n } else {\n spanHandle.span = ownSpan;\n spanHandle.ownSpan = ownSpan;\n if (isApitallySpanProcessorDeclared() && requestRecord.serverSpanId === undefined) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutUnattachedSpanProcessor();\n }\n }\n }\n // A non-recording span still carries a valid span context, so user child\n // spans parent to it and parent-based samplers drop them consistently.\n requestContext = trace.setSpan(requestContext, ownSpan);\n }\n\n // Middleware-based span producers inspect OTel RPC metadata to demote duplicate\n // transport spans; completion adds the route.\n let rpcMetadata = getRPCMetadata(requestContext);\n if (!rpcMetadata && spanHandle.span) {\n rpcMetadata = { type: RPCType.HTTP, span: spanHandle.span };\n requestContext = setRPCMetadata(requestContext, rpcMetadata);\n }\n attachRequestRecordToRpcMetadata(rpcMetadata, requestRecord);\n if (requestRecord.dropReason !== undefined) {\n requestBodyCapture.stopBuffering();\n }\n return { requestRecord, requestContext, spanHandle, rpcMetadata };\n}\n\nexport interface RequestObservation {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n method: string;\n startTimeMillis: number;\n}\n\nexport interface FinalizeRequestObservationOptions {\n observation: RequestObservation;\n completedAtMillis: number;\n statusCode: number;\n route?: string;\n clientAddress?: string;\n requestHeaders: Headers | Record<string, string | string[] | undefined>;\n responseHeaders: Headers | Record<string, string | number | string[] | undefined>;\n capturedRequestBody?: CapturedBody;\n capturedResponseBody?: CapturedBody;\n}\n\nexport function finalizeRequestObservation(options: FinalizeRequestObservationOptions): void {\n const { observation, statusCode, route } = options;\n const { requestRecord, spanHandle, rpcMetadata, method } = observation;\n requestRecord.durationSeconds = (options.completedAtMillis - observation.startTimeMillis) / 1000;\n const { span, ownSpan } = spanHandle;\n if (options.clientAddress !== undefined) {\n writeRequestAttribute(span, requestRecord, \"client.address\", options.clientAddress);\n }\n writeRequestAttribute(span, requestRecord, \"http.response.status_code\", statusCode);\n const requestBodySize = options.capturedRequestBody?.size;\n if (requestBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.request.body.size\", requestBodySize);\n }\n const responseBodySize = options.capturedResponseBody?.size;\n if (responseBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.response.body.size\", responseBodySize);\n }\n if (route !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.route\", route);\n if (rpcMetadata) {\n rpcMetadata.route = route;\n }\n if (ownSpan?.isRecording()) {\n ownSpan.updateName(`${method} ${route}`);\n }\n } else {\n // An empty route clears one set by the producing instrumentation; request\n // metrics omit empty routes.\n requestRecord.attributes[\"http.route\"] = \"\";\n }\n if (ownSpan && statusCode >= 500) {\n ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n }\n // Validation and server errors are counted for every routed request,\n // including requests dropped from tracing.\n const consumer = requestRecord.attributes[\"apitally.consumer.identifier\"];\n const consumerIdentifier = typeof consumer === \"string\" ? consumer : undefined;\n if (requestRecord.validationErrors && isValidationResponseStatus(statusCode)) {\n addValidationErrors(consumerIdentifier, method, route ?? \"\", requestRecord.validationErrors);\n }\n if (statusCode === 500 && requestRecord.exception !== undefined) {\n const sentryEventId = requestRecord.attributes[\"apitally.exception.sentry_event_id\"];\n addServerError(\n consumerIdentifier,\n method,\n route ?? \"\",\n requestRecord.exception,\n typeof sentryEventId === \"string\" ? sentryEventId : undefined,\n );\n }\n // A dropped request's spans are never released, so a stash entry for it\n // would sit unconsumed until the cap evicts it.\n if (requestRecord.serverSpanId !== undefined && requestRecord.dropReason === undefined) {\n const config = getConfig();\n const stash: RequestStash = {};\n if (config.captureRequestHeaders) {\n stash.requestHeaders = normalizeHeaders(options.requestHeaders);\n }\n if (config.captureResponseHeaders) {\n stash.responseHeaders = normalizeHeaders(options.responseHeaders);\n }\n const requestBody = options.capturedRequestBody?.body;\n if (requestBody) {\n stash.requestBody = requestBody;\n stash.requestContentEncoding = getHeaderValue(options.requestHeaders, \"content-encoding\");\n }\n const responseBody = options.capturedResponseBody?.body;\n if (responseBody && config.captureResponseBody) {\n stash.responseBody = responseBody;\n stash.responseContentEncoding = getHeaderValue(options.responseHeaders, \"content-encoding\");\n }\n if (Object.keys(stash).length > 0) {\n getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);\n }\n }\n ownSpan?.end(options.completedAtMillis);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nexport interface FinalizeRequestObservationWithErrorOptions {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n error: unknown;\n durationSeconds: number;\n}\n\nexport function finalizeRequestObservationWithError(\n options: FinalizeRequestObservationWithErrorOptions,\n): void {\n const { requestRecord, spanHandle, error, durationSeconds } = options;\n requestRecord.durationSeconds = durationSeconds;\n if (spanHandle.span?.isRecording()) {\n spanHandle.span.recordException(coerceToException(error));\n }\n if (spanHandle.ownSpan) {\n spanHandle.ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n spanHandle.ownSpan.end();\n }\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nfunction getHeaderValue(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n name: string,\n): string | undefined {\n if (isWebHeaders(headers)) {\n return headers.get(name) ?? undefined;\n }\n for (const [headerName, value] of Object.entries(headers)) {\n if (headerName.toLowerCase() === name) {\n return Array.isArray(value) ? value.join(\",\") : value?.toString();\n }\n }\n return undefined;\n}\n\n// Values remain raw so all redaction happens at the export boundary.\nfunction normalizeHeaders(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n): Record<string, string | string[]> {\n const normalized: Record<string, string | string[]> = {};\n if (isWebHeaders(headers)) {\n // The Headers API combines repeated values except Set-Cookie.\n for (const [name, value] of headers) {\n const existing = normalized[name];\n if (existing === undefined) {\n normalized[name] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n normalized[name] = [existing, value];\n }\n }\n return normalized;\n }\n for (const [name, value] of Object.entries(headers)) {\n if (value !== undefined) {\n normalized[name.toLowerCase()] = Array.isArray(value) ? value : String(value);\n }\n }\n return normalized;\n}\n\n// Duck-typed on iterability: a plain header record has no Symbol.iterator, and\n// the Headers instance may come from another realm's implementation.\nfunction isWebHeaders(headers: object): headers is Headers {\n return typeof (headers as Headers)[Symbol.iterator] === \"function\";\n}\n\nfunction warnAboutNonRecordingServerSpan(): void {\n logWarning(\n \"The OpenTelemetry sampler did not sample the SERVER span of a request. Only sampled requests are exported to Apitally as traces and request logs. Request metrics include all requests.\",\n );\n}\n\nfunction warnAboutUnattachedSpanProcessor(): void {\n logWarning(\n \"The recording OpenTelemetry SERVER span created for a request did not reach ApitallySpanProcessor. Traces and request logs are unavailable while request metrics continue. To resolve this, add the ApitallySpanProcessor instance to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"requestObservation.cjs","names":["trace","getActiveSpanPipeline","SpanKind","withRequestHolders","isApitallySpanProcessorDeclared","getRPCMetadata","RPCType","setRPCMetadata","SpanStatusCode","isValidationResponseStatus","getConfig","coerceToException"],"sources":["../src/requestObservation.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType, setRPCMetadata } from \"@opentelemetry/core\";\nimport type { BodyCapture, CapturedBody } from \"./bodyCapture.js\";\nimport { getConfig } from \"./config.js\";\nimport { emitConsumerUpdateIfChanged } from \"./consumers.js\";\nimport {\n attachRequestRecordToRpcMetadata,\n type RequestRecord,\n type SpanHandle,\n withRequestHolders,\n} from \"./context.js\";\nimport { coerceToException } from \"./exceptions.js\";\nimport { logWarning } from \"./logger.js\";\nimport { writeRequestAttribute } from \"./requestAttributes.js\";\nimport { addServerError } from \"./serverErrors.js\";\nimport {\n getActiveSpanPipeline,\n isApitallySpanProcessorDeclared,\n type RequestStash,\n} from \"./spanProcessor.js\";\nimport { addValidationErrors, isValidationResponseStatus } from \"./validationErrors.js\";\n\ninterface StartRequestObservationOptions {\n activeContext: Context;\n extractedContext: Context;\n tracerName: string;\n method: string;\n startAttributes: Attributes;\n requestBodyCapture: BodyCapture;\n}\n\nexport interface StartedRequestObservation {\n requestRecord: RequestRecord;\n requestContext: Context;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n}\n\nexport interface HttpRequestStartAttributeInput {\n method: string;\n path?: string;\n query?: string;\n scheme?: string;\n serverAddress?: string;\n fullUrl?: string;\n clientAddress?: string;\n userAgent?: string;\n requestBodySize?: number;\n}\n\nexport function resolveHttpRequestStartAttributes(\n input: HttpRequestStartAttributeInput,\n): Attributes {\n const attributes: Attributes = { \"http.request.method\": input.method };\n const optionalAttributes: [string, string | number | undefined][] = [\n [\"url.path\", input.path],\n [\"url.query\", input.query],\n [\"url.scheme\", input.scheme],\n [\"url.full\", input.fullUrl],\n [\"server.address\", input.serverAddress],\n [\"client.address\", input.clientAddress],\n [\"user_agent.original\", input.userAgent],\n [\"http.request.body.size\", input.requestBodySize],\n ];\n for (const [name, value] of optionalAttributes) {\n if (value !== undefined) {\n attributes[name] = value;\n }\n }\n return attributes;\n}\n\nexport function startRequestObservation(\n options: StartRequestObservationOptions,\n): StartedRequestObservation {\n const {\n activeContext,\n extractedContext,\n tracerName,\n method,\n startAttributes,\n requestBodyCapture,\n } = options;\n const requestRecord: RequestRecord = { attributes: {} };\n const spanHandle: SpanHandle = {};\n // Metrics and the exported span copy read from the request record, so the\n // start attributes are mirrored into it on every path, span or no span.\n Object.assign(requestRecord.attributes, startAttributes);\n const activeSpan = trace.getSpan(activeContext);\n const resolveUnavailableSpanDropReason = (): RequestRecord[\"dropReason\"] =>\n getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ??\n \"sampled-out\";\n let requestContext: Context;\n // Span kind is not part of the OTel API surface, so read the SDK-level\n // property from whichever package copy produced the span.\n if (activeSpan?.isRecording() && (activeSpan as { kind?: unknown }).kind === SpanKind.SERVER) {\n // A SERVER span produced by the user's own instrumentation is adopted:\n // no second span, and the request runs under the user's context.\n spanHandle.span = activeSpan;\n requestRecord.serverSpanId = activeSpan.spanContext().spanId;\n getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);\n if (getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);\n } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);\n } else {\n requestContext = withRequestHolders(extractedContext, spanHandle, requestRecord);\n const ownSpan = trace\n .getTracer(tracerName)\n .startSpan(method, { kind: SpanKind.SERVER, attributes: startAttributes }, requestContext);\n if (!ownSpan.isRecording()) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n } else {\n spanHandle.span = ownSpan;\n spanHandle.ownSpan = ownSpan;\n if (isApitallySpanProcessorDeclared() && requestRecord.serverSpanId === undefined) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutUnattachedSpanProcessor();\n }\n }\n }\n // A non-recording span still carries a valid span context, so user child\n // spans parent to it and parent-based samplers drop them consistently.\n requestContext = trace.setSpan(requestContext, ownSpan);\n }\n\n // Middleware-based span producers inspect OTel RPC metadata to demote duplicate\n // transport spans; completion adds the route.\n let rpcMetadata = getRPCMetadata(requestContext);\n if (!rpcMetadata && spanHandle.span) {\n rpcMetadata = { type: RPCType.HTTP, span: spanHandle.span };\n requestContext = setRPCMetadata(requestContext, rpcMetadata);\n }\n attachRequestRecordToRpcMetadata(rpcMetadata, requestRecord);\n if (requestRecord.dropReason !== undefined) {\n requestBodyCapture.stopBuffering();\n }\n return { requestRecord, requestContext, spanHandle, rpcMetadata };\n}\n\nexport interface RequestObservation {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n method: string;\n startTimeMillis: number;\n}\n\nexport interface FinalizeRequestObservationOptions {\n observation: RequestObservation;\n completedAtMillis: number;\n statusCode: number;\n route?: string;\n clientAddress?: string;\n requestHeaders: Headers | Record<string, string | string[] | undefined>;\n responseHeaders: Headers | Record<string, string | number | string[] | undefined>;\n capturedRequestBody?: CapturedBody;\n capturedResponseBody?: CapturedBody;\n}\n\nexport function finalizeRequestObservation(options: FinalizeRequestObservationOptions): void {\n const { observation, statusCode, route } = options;\n const { requestRecord, spanHandle, rpcMetadata, method } = observation;\n requestRecord.durationSeconds = (options.completedAtMillis - observation.startTimeMillis) / 1000;\n const { span, ownSpan } = spanHandle;\n if (options.clientAddress !== undefined) {\n writeRequestAttribute(span, requestRecord, \"client.address\", options.clientAddress);\n }\n writeRequestAttribute(span, requestRecord, \"http.response.status_code\", statusCode);\n const requestBodySize = options.capturedRequestBody?.size;\n if (requestBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.request.body.size\", requestBodySize);\n }\n const responseBodySize = options.capturedResponseBody?.size;\n if (responseBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.response.body.size\", responseBodySize);\n }\n if (route !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.route\", route);\n if (rpcMetadata) {\n rpcMetadata.route = route;\n }\n if (ownSpan?.isRecording()) {\n ownSpan.updateName(`${method} ${route}`);\n }\n } else {\n // An empty route clears one set by the producing instrumentation; request\n // metrics omit empty routes.\n requestRecord.attributes[\"http.route\"] = \"\";\n }\n if (ownSpan && statusCode >= 500) {\n ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n }\n // Validation and server errors are counted for every routed request,\n // including requests dropped from tracing.\n const consumer = requestRecord.attributes[\"apitally.consumer.identifier\"];\n const consumerIdentifier = typeof consumer === \"string\" ? consumer : undefined;\n if (requestRecord.validationErrors && isValidationResponseStatus(statusCode)) {\n addValidationErrors(consumerIdentifier, method, route ?? \"\", requestRecord.validationErrors);\n }\n if (statusCode === 500 && requestRecord.exception !== undefined) {\n const sentryEventId = requestRecord.attributes[\"apitally.exception.sentry_event_id\"];\n addServerError(\n consumerIdentifier,\n method,\n route ?? \"\",\n requestRecord.exception,\n typeof sentryEventId === \"string\" ? sentryEventId : undefined,\n );\n }\n // A dropped request's spans are never released, so a stash entry for it\n // would sit unconsumed until the cap evicts it.\n if (requestRecord.serverSpanId !== undefined && requestRecord.dropReason === undefined) {\n const config = getConfig();\n const stash: RequestStash = {};\n if (config.captureRequestHeaders) {\n stash.requestHeaders = normalizeHeaders(options.requestHeaders);\n }\n if (config.captureResponseHeaders) {\n stash.responseHeaders = normalizeHeaders(options.responseHeaders);\n }\n const requestBody = options.capturedRequestBody?.body;\n if (requestBody) {\n stash.requestBody = requestBody;\n stash.requestContentEncoding = getHeaderValue(options.requestHeaders, \"content-encoding\");\n }\n const responseBody = options.capturedResponseBody?.body;\n if (responseBody && config.captureResponseBody) {\n stash.responseBody = responseBody;\n stash.responseContentEncoding = getHeaderValue(options.responseHeaders, \"content-encoding\");\n }\n if (Object.keys(stash).length > 0) {\n getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);\n }\n }\n ownSpan?.end(options.completedAtMillis);\n emitConsumerUpdateIfChanged(requestRecord);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nexport interface FinalizeRequestObservationWithErrorOptions {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n error: unknown;\n durationSeconds: number;\n}\n\nexport function finalizeRequestObservationWithError(\n options: FinalizeRequestObservationWithErrorOptions,\n): void {\n const { requestRecord, spanHandle, error, durationSeconds } = options;\n requestRecord.durationSeconds = durationSeconds;\n if (spanHandle.span?.isRecording()) {\n spanHandle.span.recordException(coerceToException(error));\n }\n if (spanHandle.ownSpan) {\n spanHandle.ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n spanHandle.ownSpan.end();\n }\n emitConsumerUpdateIfChanged(requestRecord);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nfunction getHeaderValue(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n name: string,\n): string | undefined {\n if (isWebHeaders(headers)) {\n return headers.get(name) ?? undefined;\n }\n for (const [headerName, value] of Object.entries(headers)) {\n if (headerName.toLowerCase() === name) {\n return Array.isArray(value) ? value.join(\",\") : value?.toString();\n }\n }\n return undefined;\n}\n\n// Values remain raw so all redaction happens at the export boundary.\nfunction normalizeHeaders(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n): Record<string, string | string[]> {\n const normalized: Record<string, string | string[]> = {};\n if (isWebHeaders(headers)) {\n // The Headers API combines repeated values except Set-Cookie.\n for (const [name, value] of headers) {\n const existing = normalized[name];\n if (existing === undefined) {\n normalized[name] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n normalized[name] = [existing, value];\n }\n }\n return normalized;\n }\n for (const [name, value] of Object.entries(headers)) {\n if (value !== undefined) {\n normalized[name.toLowerCase()] = Array.isArray(value) ? value : String(value);\n }\n }\n return normalized;\n}\n\n// Duck-typed on iterability: a plain header record has no Symbol.iterator, and\n// the Headers instance may come from another realm's implementation.\nfunction isWebHeaders(headers: object): headers is Headers {\n return typeof (headers as Headers)[Symbol.iterator] === \"function\";\n}\n\nfunction warnAboutNonRecordingServerSpan(): void {\n logWarning(\n \"The OpenTelemetry sampler did not sample the SERVER span of a request. Only sampled requests are exported to Apitally as traces and request logs. Request metrics include all requests.\",\n );\n}\n\nfunction warnAboutUnattachedSpanProcessor(): void {\n logWarning(\n \"The recording OpenTelemetry SERVER span created for a request did not reach ApitallySpanProcessor. Traces and request logs are unavailable while request metrics continue. To resolve this, add the ApitallySpanProcessor instance to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n}\n"],"mappings":"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"}
@@ -1,11 +1,12 @@
1
1
  import { logWarning } from "./logger.js";
2
2
  import { getConfig } from "./config.js";
3
- import { attachRequestRecordToRpcMetadata, getConsumerHolder, withRequestHolders } from "./context.js";
3
+ import { attachRequestRecordToRpcMetadata, withRequestHolders } from "./context.js";
4
4
  import { writeRequestAttribute } from "./requestAttributes.js";
5
5
  import { coerceToException } from "./exceptions.js";
6
6
  import { addServerError } from "./serverErrors.js";
7
7
  import { getActiveSpanPipeline, isApitallySpanProcessorDeclared } from "./spanProcessor.js";
8
8
  import { addValidationErrors, isValidationResponseStatus } from "./validationErrors.js";
9
+ import { emitConsumerUpdateIfChanged } from "./consumers.js";
9
10
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
10
11
  import { RPCType, getRPCMetadata, setRPCMetadata } from "@opentelemetry/core";
11
12
  //#region src/requestObservation.ts
@@ -28,7 +29,6 @@ function startRequestObservation(options) {
28
29
  const { activeContext, extractedContext, tracerName, method, startAttributes, requestBodyCapture } = options;
29
30
  const requestRecord = { attributes: {} };
30
31
  const spanHandle = {};
31
- const consumerHolder = getConsumerHolder(activeContext) ?? {};
32
32
  Object.assign(requestRecord.attributes, startAttributes);
33
33
  const activeSpan = trace.getSpan(activeContext);
34
34
  const resolveUnavailableSpanDropReason = () => getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ?? "sampled-out";
@@ -38,13 +38,13 @@ function startRequestObservation(options) {
38
38
  requestRecord.serverSpanId = activeSpan.spanContext().spanId;
39
39
  getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);
40
40
  if (getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) requestRecord.dropReason = resolveUnavailableSpanDropReason();
41
- requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);
41
+ requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);
42
42
  } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {
43
43
  requestRecord.dropReason = resolveUnavailableSpanDropReason();
44
44
  if (requestRecord.dropReason === "sampled-out") warnAboutNonRecordingServerSpan();
45
- requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);
45
+ requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);
46
46
  } else {
47
- requestContext = withRequestHolders(extractedContext, spanHandle, requestRecord, consumerHolder);
47
+ requestContext = withRequestHolders(extractedContext, spanHandle, requestRecord);
48
48
  const ownSpan = trace.getTracer(tracerName).startSpan(method, {
49
49
  kind: SpanKind.SERVER,
50
50
  attributes: startAttributes
@@ -121,6 +121,7 @@ function finalizeRequestObservation(options) {
121
121
  if (Object.keys(stash).length > 0) getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);
122
122
  }
123
123
  ownSpan?.end(options.completedAtMillis);
124
+ emitConsumerUpdateIfChanged(requestRecord);
124
125
  getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);
125
126
  }
126
127
  function finalizeRequestObservationWithError(options) {
@@ -131,6 +132,7 @@ function finalizeRequestObservationWithError(options) {
131
132
  spanHandle.ownSpan.setStatus({ code: SpanStatusCode.ERROR });
132
133
  spanHandle.ownSpan.end();
133
134
  }
135
+ emitConsumerUpdateIfChanged(requestRecord);
134
136
  getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);
135
137
  }
136
138
  function getHeaderValue(headers, name) {
@@ -1 +1 @@
1
- {"version":3,"file":"requestObservation.js","names":[],"sources":["../src/requestObservation.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType, setRPCMetadata } from \"@opentelemetry/core\";\nimport type { BodyCapture, CapturedBody } from \"./bodyCapture.js\";\nimport { getConfig } from \"./config.js\";\nimport {\n attachRequestRecordToRpcMetadata,\n getConsumerHolder,\n type RequestRecord,\n type SpanHandle,\n withRequestHolders,\n} from \"./context.js\";\nimport { coerceToException } from \"./exceptions.js\";\nimport { logWarning } from \"./logger.js\";\nimport { writeRequestAttribute } from \"./requestAttributes.js\";\nimport { addServerError } from \"./serverErrors.js\";\nimport {\n getActiveSpanPipeline,\n isApitallySpanProcessorDeclared,\n type RequestStash,\n} from \"./spanProcessor.js\";\nimport { addValidationErrors, isValidationResponseStatus } from \"./validationErrors.js\";\n\ninterface StartRequestObservationOptions {\n activeContext: Context;\n extractedContext: Context;\n tracerName: string;\n method: string;\n startAttributes: Attributes;\n requestBodyCapture: BodyCapture;\n}\n\nexport interface StartedRequestObservation {\n requestRecord: RequestRecord;\n requestContext: Context;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n}\n\nexport interface HttpRequestStartAttributeInput {\n method: string;\n path?: string;\n query?: string;\n scheme?: string;\n serverAddress?: string;\n fullUrl?: string;\n clientAddress?: string;\n userAgent?: string;\n requestBodySize?: number;\n}\n\nexport function resolveHttpRequestStartAttributes(\n input: HttpRequestStartAttributeInput,\n): Attributes {\n const attributes: Attributes = { \"http.request.method\": input.method };\n const optionalAttributes: [string, string | number | undefined][] = [\n [\"url.path\", input.path],\n [\"url.query\", input.query],\n [\"url.scheme\", input.scheme],\n [\"url.full\", input.fullUrl],\n [\"server.address\", input.serverAddress],\n [\"client.address\", input.clientAddress],\n [\"user_agent.original\", input.userAgent],\n [\"http.request.body.size\", input.requestBodySize],\n ];\n for (const [name, value] of optionalAttributes) {\n if (value !== undefined) {\n attributes[name] = value;\n }\n }\n return attributes;\n}\n\nexport function startRequestObservation(\n options: StartRequestObservationOptions,\n): StartedRequestObservation {\n const {\n activeContext,\n extractedContext,\n tracerName,\n method,\n startAttributes,\n requestBodyCapture,\n } = options;\n const requestRecord: RequestRecord = { attributes: {} };\n const spanHandle: SpanHandle = {};\n const consumerHolder = getConsumerHolder(activeContext) ?? {};\n // Metrics and the exported span copy read from the request record, so the\n // start attributes are mirrored into it on every path, span or no span.\n Object.assign(requestRecord.attributes, startAttributes);\n const activeSpan = trace.getSpan(activeContext);\n const resolveUnavailableSpanDropReason = (): RequestRecord[\"dropReason\"] =>\n getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ??\n \"sampled-out\";\n let requestContext: Context;\n // Span kind is not part of the OTel API surface, so read the SDK-level\n // property from whichever package copy produced the span.\n if (activeSpan?.isRecording() && (activeSpan as { kind?: unknown }).kind === SpanKind.SERVER) {\n // A SERVER span produced by the user's own instrumentation is adopted:\n // no second span, and the request runs under the user's context.\n spanHandle.span = activeSpan;\n requestRecord.serverSpanId = activeSpan.spanContext().spanId;\n getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);\n if (getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);\n } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord, consumerHolder);\n } else {\n requestContext = withRequestHolders(\n extractedContext,\n spanHandle,\n requestRecord,\n consumerHolder,\n );\n const ownSpan = trace\n .getTracer(tracerName)\n .startSpan(method, { kind: SpanKind.SERVER, attributes: startAttributes }, requestContext);\n if (!ownSpan.isRecording()) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n } else {\n spanHandle.span = ownSpan;\n spanHandle.ownSpan = ownSpan;\n if (isApitallySpanProcessorDeclared() && requestRecord.serverSpanId === undefined) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutUnattachedSpanProcessor();\n }\n }\n }\n // A non-recording span still carries a valid span context, so user child\n // spans parent to it and parent-based samplers drop them consistently.\n requestContext = trace.setSpan(requestContext, ownSpan);\n }\n\n // Middleware-based span producers inspect OTel RPC metadata to demote duplicate\n // transport spans; completion adds the route.\n let rpcMetadata = getRPCMetadata(requestContext);\n if (!rpcMetadata && spanHandle.span) {\n rpcMetadata = { type: RPCType.HTTP, span: spanHandle.span };\n requestContext = setRPCMetadata(requestContext, rpcMetadata);\n }\n attachRequestRecordToRpcMetadata(rpcMetadata, requestRecord);\n if (requestRecord.dropReason !== undefined) {\n requestBodyCapture.stopBuffering();\n }\n return { requestRecord, requestContext, spanHandle, rpcMetadata };\n}\n\nexport interface RequestObservation {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n method: string;\n startTimeMillis: number;\n}\n\nexport interface FinalizeRequestObservationOptions {\n observation: RequestObservation;\n completedAtMillis: number;\n statusCode: number;\n route?: string;\n clientAddress?: string;\n requestHeaders: Headers | Record<string, string | string[] | undefined>;\n responseHeaders: Headers | Record<string, string | number | string[] | undefined>;\n capturedRequestBody?: CapturedBody;\n capturedResponseBody?: CapturedBody;\n}\n\nexport function finalizeRequestObservation(options: FinalizeRequestObservationOptions): void {\n const { observation, statusCode, route } = options;\n const { requestRecord, spanHandle, rpcMetadata, method } = observation;\n requestRecord.durationSeconds = (options.completedAtMillis - observation.startTimeMillis) / 1000;\n const { span, ownSpan } = spanHandle;\n if (options.clientAddress !== undefined) {\n writeRequestAttribute(span, requestRecord, \"client.address\", options.clientAddress);\n }\n writeRequestAttribute(span, requestRecord, \"http.response.status_code\", statusCode);\n const requestBodySize = options.capturedRequestBody?.size;\n if (requestBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.request.body.size\", requestBodySize);\n }\n const responseBodySize = options.capturedResponseBody?.size;\n if (responseBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.response.body.size\", responseBodySize);\n }\n if (route !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.route\", route);\n if (rpcMetadata) {\n rpcMetadata.route = route;\n }\n if (ownSpan?.isRecording()) {\n ownSpan.updateName(`${method} ${route}`);\n }\n } else {\n // An empty route clears one set by the producing instrumentation; request\n // metrics omit empty routes.\n requestRecord.attributes[\"http.route\"] = \"\";\n }\n if (ownSpan && statusCode >= 500) {\n ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n }\n // Validation and server errors are counted for every routed request,\n // including requests dropped from tracing.\n const consumer = requestRecord.attributes[\"apitally.consumer.identifier\"];\n const consumerIdentifier = typeof consumer === \"string\" ? consumer : undefined;\n if (requestRecord.validationErrors && isValidationResponseStatus(statusCode)) {\n addValidationErrors(consumerIdentifier, method, route ?? \"\", requestRecord.validationErrors);\n }\n if (statusCode === 500 && requestRecord.exception !== undefined) {\n const sentryEventId = requestRecord.attributes[\"apitally.exception.sentry_event_id\"];\n addServerError(\n consumerIdentifier,\n method,\n route ?? \"\",\n requestRecord.exception,\n typeof sentryEventId === \"string\" ? sentryEventId : undefined,\n );\n }\n // A dropped request's spans are never released, so a stash entry for it\n // would sit unconsumed until the cap evicts it.\n if (requestRecord.serverSpanId !== undefined && requestRecord.dropReason === undefined) {\n const config = getConfig();\n const stash: RequestStash = {};\n if (config.captureRequestHeaders) {\n stash.requestHeaders = normalizeHeaders(options.requestHeaders);\n }\n if (config.captureResponseHeaders) {\n stash.responseHeaders = normalizeHeaders(options.responseHeaders);\n }\n const requestBody = options.capturedRequestBody?.body;\n if (requestBody) {\n stash.requestBody = requestBody;\n stash.requestContentEncoding = getHeaderValue(options.requestHeaders, \"content-encoding\");\n }\n const responseBody = options.capturedResponseBody?.body;\n if (responseBody && config.captureResponseBody) {\n stash.responseBody = responseBody;\n stash.responseContentEncoding = getHeaderValue(options.responseHeaders, \"content-encoding\");\n }\n if (Object.keys(stash).length > 0) {\n getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);\n }\n }\n ownSpan?.end(options.completedAtMillis);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nexport interface FinalizeRequestObservationWithErrorOptions {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n error: unknown;\n durationSeconds: number;\n}\n\nexport function finalizeRequestObservationWithError(\n options: FinalizeRequestObservationWithErrorOptions,\n): void {\n const { requestRecord, spanHandle, error, durationSeconds } = options;\n requestRecord.durationSeconds = durationSeconds;\n if (spanHandle.span?.isRecording()) {\n spanHandle.span.recordException(coerceToException(error));\n }\n if (spanHandle.ownSpan) {\n spanHandle.ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n spanHandle.ownSpan.end();\n }\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nfunction getHeaderValue(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n name: string,\n): string | undefined {\n if (isWebHeaders(headers)) {\n return headers.get(name) ?? undefined;\n }\n for (const [headerName, value] of Object.entries(headers)) {\n if (headerName.toLowerCase() === name) {\n return Array.isArray(value) ? value.join(\",\") : value?.toString();\n }\n }\n return undefined;\n}\n\n// Values remain raw so all redaction happens at the export boundary.\nfunction normalizeHeaders(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n): Record<string, string | string[]> {\n const normalized: Record<string, string | string[]> = {};\n if (isWebHeaders(headers)) {\n // The Headers API combines repeated values except Set-Cookie.\n for (const [name, value] of headers) {\n const existing = normalized[name];\n if (existing === undefined) {\n normalized[name] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n normalized[name] = [existing, value];\n }\n }\n return normalized;\n }\n for (const [name, value] of Object.entries(headers)) {\n if (value !== undefined) {\n normalized[name.toLowerCase()] = Array.isArray(value) ? value : String(value);\n }\n }\n return normalized;\n}\n\n// Duck-typed on iterability: a plain header record has no Symbol.iterator, and\n// the Headers instance may come from another realm's implementation.\nfunction isWebHeaders(headers: object): headers is Headers {\n return typeof (headers as Headers)[Symbol.iterator] === \"function\";\n}\n\nfunction warnAboutNonRecordingServerSpan(): void {\n logWarning(\n \"The OpenTelemetry sampler did not sample the SERVER span of a request. Only sampled requests are exported to Apitally as traces and request logs. Request metrics include all requests.\",\n );\n}\n\nfunction warnAboutUnattachedSpanProcessor(): void {\n logWarning(\n \"The recording OpenTelemetry SERVER span created for a request did not reach ApitallySpanProcessor. Traces and request logs are unavailable while request metrics continue. To resolve this, add the ApitallySpanProcessor instance to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"requestObservation.js","names":[],"sources":["../src/requestObservation.ts"],"sourcesContent":["import { type Attributes, type Context, SpanKind, SpanStatusCode, trace } from \"@opentelemetry/api\";\nimport { getRPCMetadata, type RPCMetadata, RPCType, setRPCMetadata } from \"@opentelemetry/core\";\nimport type { BodyCapture, CapturedBody } from \"./bodyCapture.js\";\nimport { getConfig } from \"./config.js\";\nimport { emitConsumerUpdateIfChanged } from \"./consumers.js\";\nimport {\n attachRequestRecordToRpcMetadata,\n type RequestRecord,\n type SpanHandle,\n withRequestHolders,\n} from \"./context.js\";\nimport { coerceToException } from \"./exceptions.js\";\nimport { logWarning } from \"./logger.js\";\nimport { writeRequestAttribute } from \"./requestAttributes.js\";\nimport { addServerError } from \"./serverErrors.js\";\nimport {\n getActiveSpanPipeline,\n isApitallySpanProcessorDeclared,\n type RequestStash,\n} from \"./spanProcessor.js\";\nimport { addValidationErrors, isValidationResponseStatus } from \"./validationErrors.js\";\n\ninterface StartRequestObservationOptions {\n activeContext: Context;\n extractedContext: Context;\n tracerName: string;\n method: string;\n startAttributes: Attributes;\n requestBodyCapture: BodyCapture;\n}\n\nexport interface StartedRequestObservation {\n requestRecord: RequestRecord;\n requestContext: Context;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n}\n\nexport interface HttpRequestStartAttributeInput {\n method: string;\n path?: string;\n query?: string;\n scheme?: string;\n serverAddress?: string;\n fullUrl?: string;\n clientAddress?: string;\n userAgent?: string;\n requestBodySize?: number;\n}\n\nexport function resolveHttpRequestStartAttributes(\n input: HttpRequestStartAttributeInput,\n): Attributes {\n const attributes: Attributes = { \"http.request.method\": input.method };\n const optionalAttributes: [string, string | number | undefined][] = [\n [\"url.path\", input.path],\n [\"url.query\", input.query],\n [\"url.scheme\", input.scheme],\n [\"url.full\", input.fullUrl],\n [\"server.address\", input.serverAddress],\n [\"client.address\", input.clientAddress],\n [\"user_agent.original\", input.userAgent],\n [\"http.request.body.size\", input.requestBodySize],\n ];\n for (const [name, value] of optionalAttributes) {\n if (value !== undefined) {\n attributes[name] = value;\n }\n }\n return attributes;\n}\n\nexport function startRequestObservation(\n options: StartRequestObservationOptions,\n): StartedRequestObservation {\n const {\n activeContext,\n extractedContext,\n tracerName,\n method,\n startAttributes,\n requestBodyCapture,\n } = options;\n const requestRecord: RequestRecord = { attributes: {} };\n const spanHandle: SpanHandle = {};\n // Metrics and the exported span copy read from the request record, so the\n // start attributes are mirrored into it on every path, span or no span.\n Object.assign(requestRecord.attributes, startAttributes);\n const activeSpan = trace.getSpan(activeContext);\n const resolveUnavailableSpanDropReason = (): RequestRecord[\"dropReason\"] =>\n getActiveSpanPipeline()?.resolveRequestDropReasonBeforeSampling(startAttributes) ??\n \"sampled-out\";\n let requestContext: Context;\n // Span kind is not part of the OTel API surface, so read the SDK-level\n // property from whichever package copy produced the span.\n if (activeSpan?.isRecording() && (activeSpan as { kind?: unknown }).kind === SpanKind.SERVER) {\n // A SERVER span produced by the user's own instrumentation is adopted:\n // no second span, and the request runs under the user's context.\n spanHandle.span = activeSpan;\n requestRecord.serverSpanId = activeSpan.spanContext().spanId;\n getActiveSpanPipeline()?.attachRequestRecord(requestRecord.serverSpanId, requestRecord);\n if (getActiveSpanPipeline()?.isRequestInFlight(requestRecord.serverSpanId) !== true) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);\n } else if (activeSpan && !activeSpan.isRecording() && !activeSpan.spanContext().isRemote) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n requestContext = withRequestHolders(activeContext, spanHandle, requestRecord);\n } else {\n requestContext = withRequestHolders(extractedContext, spanHandle, requestRecord);\n const ownSpan = trace\n .getTracer(tracerName)\n .startSpan(method, { kind: SpanKind.SERVER, attributes: startAttributes }, requestContext);\n if (!ownSpan.isRecording()) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutNonRecordingServerSpan();\n }\n } else {\n spanHandle.span = ownSpan;\n spanHandle.ownSpan = ownSpan;\n if (isApitallySpanProcessorDeclared() && requestRecord.serverSpanId === undefined) {\n requestRecord.dropReason = resolveUnavailableSpanDropReason();\n if (requestRecord.dropReason === \"sampled-out\") {\n warnAboutUnattachedSpanProcessor();\n }\n }\n }\n // A non-recording span still carries a valid span context, so user child\n // spans parent to it and parent-based samplers drop them consistently.\n requestContext = trace.setSpan(requestContext, ownSpan);\n }\n\n // Middleware-based span producers inspect OTel RPC metadata to demote duplicate\n // transport spans; completion adds the route.\n let rpcMetadata = getRPCMetadata(requestContext);\n if (!rpcMetadata && spanHandle.span) {\n rpcMetadata = { type: RPCType.HTTP, span: spanHandle.span };\n requestContext = setRPCMetadata(requestContext, rpcMetadata);\n }\n attachRequestRecordToRpcMetadata(rpcMetadata, requestRecord);\n if (requestRecord.dropReason !== undefined) {\n requestBodyCapture.stopBuffering();\n }\n return { requestRecord, requestContext, spanHandle, rpcMetadata };\n}\n\nexport interface RequestObservation {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n rpcMetadata?: RPCMetadata;\n method: string;\n startTimeMillis: number;\n}\n\nexport interface FinalizeRequestObservationOptions {\n observation: RequestObservation;\n completedAtMillis: number;\n statusCode: number;\n route?: string;\n clientAddress?: string;\n requestHeaders: Headers | Record<string, string | string[] | undefined>;\n responseHeaders: Headers | Record<string, string | number | string[] | undefined>;\n capturedRequestBody?: CapturedBody;\n capturedResponseBody?: CapturedBody;\n}\n\nexport function finalizeRequestObservation(options: FinalizeRequestObservationOptions): void {\n const { observation, statusCode, route } = options;\n const { requestRecord, spanHandle, rpcMetadata, method } = observation;\n requestRecord.durationSeconds = (options.completedAtMillis - observation.startTimeMillis) / 1000;\n const { span, ownSpan } = spanHandle;\n if (options.clientAddress !== undefined) {\n writeRequestAttribute(span, requestRecord, \"client.address\", options.clientAddress);\n }\n writeRequestAttribute(span, requestRecord, \"http.response.status_code\", statusCode);\n const requestBodySize = options.capturedRequestBody?.size;\n if (requestBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.request.body.size\", requestBodySize);\n }\n const responseBodySize = options.capturedResponseBody?.size;\n if (responseBodySize !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.response.body.size\", responseBodySize);\n }\n if (route !== undefined) {\n writeRequestAttribute(span, requestRecord, \"http.route\", route);\n if (rpcMetadata) {\n rpcMetadata.route = route;\n }\n if (ownSpan?.isRecording()) {\n ownSpan.updateName(`${method} ${route}`);\n }\n } else {\n // An empty route clears one set by the producing instrumentation; request\n // metrics omit empty routes.\n requestRecord.attributes[\"http.route\"] = \"\";\n }\n if (ownSpan && statusCode >= 500) {\n ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n }\n // Validation and server errors are counted for every routed request,\n // including requests dropped from tracing.\n const consumer = requestRecord.attributes[\"apitally.consumer.identifier\"];\n const consumerIdentifier = typeof consumer === \"string\" ? consumer : undefined;\n if (requestRecord.validationErrors && isValidationResponseStatus(statusCode)) {\n addValidationErrors(consumerIdentifier, method, route ?? \"\", requestRecord.validationErrors);\n }\n if (statusCode === 500 && requestRecord.exception !== undefined) {\n const sentryEventId = requestRecord.attributes[\"apitally.exception.sentry_event_id\"];\n addServerError(\n consumerIdentifier,\n method,\n route ?? \"\",\n requestRecord.exception,\n typeof sentryEventId === \"string\" ? sentryEventId : undefined,\n );\n }\n // A dropped request's spans are never released, so a stash entry for it\n // would sit unconsumed until the cap evicts it.\n if (requestRecord.serverSpanId !== undefined && requestRecord.dropReason === undefined) {\n const config = getConfig();\n const stash: RequestStash = {};\n if (config.captureRequestHeaders) {\n stash.requestHeaders = normalizeHeaders(options.requestHeaders);\n }\n if (config.captureResponseHeaders) {\n stash.responseHeaders = normalizeHeaders(options.responseHeaders);\n }\n const requestBody = options.capturedRequestBody?.body;\n if (requestBody) {\n stash.requestBody = requestBody;\n stash.requestContentEncoding = getHeaderValue(options.requestHeaders, \"content-encoding\");\n }\n const responseBody = options.capturedResponseBody?.body;\n if (responseBody && config.captureResponseBody) {\n stash.responseBody = responseBody;\n stash.responseContentEncoding = getHeaderValue(options.responseHeaders, \"content-encoding\");\n }\n if (Object.keys(stash).length > 0) {\n getActiveSpanPipeline()?.updateStash(requestRecord.serverSpanId, stash);\n }\n }\n ownSpan?.end(options.completedAtMillis);\n emitConsumerUpdateIfChanged(requestRecord);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nexport interface FinalizeRequestObservationWithErrorOptions {\n requestRecord: RequestRecord;\n spanHandle: SpanHandle;\n error: unknown;\n durationSeconds: number;\n}\n\nexport function finalizeRequestObservationWithError(\n options: FinalizeRequestObservationWithErrorOptions,\n): void {\n const { requestRecord, spanHandle, error, durationSeconds } = options;\n requestRecord.durationSeconds = durationSeconds;\n if (spanHandle.span?.isRecording()) {\n spanHandle.span.recordException(coerceToException(error));\n }\n if (spanHandle.ownSpan) {\n spanHandle.ownSpan.setStatus({ code: SpanStatusCode.ERROR });\n spanHandle.ownSpan.end();\n }\n emitConsumerUpdateIfChanged(requestRecord);\n getActiveSpanPipeline()?.handleTransportCompletion(requestRecord);\n}\n\nfunction getHeaderValue(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n name: string,\n): string | undefined {\n if (isWebHeaders(headers)) {\n return headers.get(name) ?? undefined;\n }\n for (const [headerName, value] of Object.entries(headers)) {\n if (headerName.toLowerCase() === name) {\n return Array.isArray(value) ? value.join(\",\") : value?.toString();\n }\n }\n return undefined;\n}\n\n// Values remain raw so all redaction happens at the export boundary.\nfunction normalizeHeaders(\n headers: Headers | Record<string, string | number | string[] | undefined>,\n): Record<string, string | string[]> {\n const normalized: Record<string, string | string[]> = {};\n if (isWebHeaders(headers)) {\n // The Headers API combines repeated values except Set-Cookie.\n for (const [name, value] of headers) {\n const existing = normalized[name];\n if (existing === undefined) {\n normalized[name] = value;\n } else if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n normalized[name] = [existing, value];\n }\n }\n return normalized;\n }\n for (const [name, value] of Object.entries(headers)) {\n if (value !== undefined) {\n normalized[name.toLowerCase()] = Array.isArray(value) ? value : String(value);\n }\n }\n return normalized;\n}\n\n// Duck-typed on iterability: a plain header record has no Symbol.iterator, and\n// the Headers instance may come from another realm's implementation.\nfunction isWebHeaders(headers: object): headers is Headers {\n return typeof (headers as Headers)[Symbol.iterator] === \"function\";\n}\n\nfunction warnAboutNonRecordingServerSpan(): void {\n logWarning(\n \"The OpenTelemetry sampler did not sample the SERVER span of a request. Only sampled requests are exported to Apitally as traces and request logs. Request metrics include all requests.\",\n );\n}\n\nfunction warnAboutUnattachedSpanProcessor(): void {\n logWarning(\n \"The recording OpenTelemetry SERVER span created for a request did not reach ApitallySpanProcessor. Traces and request logs are unavailable while request metrics continue. To resolve this, add the ApitallySpanProcessor instance to your tracer provider's spanProcessors constructor option or the NodeSDK spanProcessors option.\",\n );\n}\n"],"mappings":"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"}