From 1c71e3b706fad411ce3f4770e7cc5c80ce9dae0a Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Mon, 13 Apr 2026 10:02:18 -0500 Subject: [PATCH 1/2] feat!: upgrade OpenTelemetry to 2.x, bump to v2.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sdk-trace-node ^1.25.0 → ^2.6.1 - resources ^1.25.0 → ^2.6.1 - exporter-trace-otlp-http ^0.52.0 → ^0.214.0 - semantic-conventions ^1.25.0 → ^1.40.0 OTEL 2.x API changes applied: - new Resource({}) → resourceFromAttributes() factory function - provider.addSpanProcessor() → spanProcessors: [] in constructor config - SEMRESATTRS_DEPLOYMENT_ENVIRONMENT → ATTR_DEPLOYMENT_ENVIRONMENT_NAME from @opentelemetry/semantic-conventions/incubating (changes resource attribute key from deployment.environment to deployment.environment.name) Fixes the sdk-trace-base version mismatch crash seen in downstream consumers that pin OTEL packages to 2.x globally. --- package-lock.json | 577 +++++++--------------------------------------- package.json | 10 +- src/provider.ts | 25 +- 3 files changed, 100 insertions(+), 512 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a6b958..3e6f217 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "@scope3data/observability-js", - "version": "1.0.0", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scope3data/observability-js", - "version": "1.0.0", + "version": "2.0.0", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.52.0", - "@opentelemetry/resources": "^1.25.0", - "@opentelemetry/sdk-trace-node": "^1.25.0", - "@opentelemetry/semantic-conventions": "^1.25.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", + "@opentelemetry/resources": "^2.6.1", + "@opentelemetry/sdk-trace-node": "^2.6.1", + "@opentelemetry/semantic-conventions": "^1.40.0", "@pyroscope/nodejs": "^0.4.8", "@sentry/node": "10.34.0", "@sentry/opentelemetry": "10.34.0", @@ -31,7 +31,7 @@ "vitest": "^4.0.18" }, "engines": { - "node": ">= 24" + "node": ">= 20" } }, "node_modules/@apm-js-collab/code-transformer": { @@ -750,21 +750,21 @@ } }, "node_modules/@opentelemetry/api-logs": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz", - "integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", + "integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api": "^1.0.0" + "@opentelemetry/api": "^1.3.0" }, "engines": { - "node": ">=14" + "node": ">=8.0.0" } }, "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz", - "integrity": "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.1.tgz", + "integrity": "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ==", "license": "Apache-2.0", "peer": true, "engines": { @@ -775,9 +775,9 @@ } }, "node_modules/@opentelemetry/core": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", - "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", + "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", "license": "Apache-2.0", "peer": true, "dependencies": { @@ -791,79 +791,22 @@ } }, "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.52.1.tgz", - "integrity": "sha512-05HcNizx0BxcFKKnS5rwOV+2GevLTVIRA0tRgWYyw4yCgR53Ic/xk83toYKts7kbzcI+dswInUg/4s8oyA+tqg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/otlp-exporter-base": "0.52.1", - "@opentelemetry/otlp-transformer": "0.52.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz", - "integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.214.0.tgz", + "integrity": "sha512-kIN8nTBMgV2hXzV/a20BCFilPZdAIMYYJGSgfMMRm/Xa+07y5hRDS2Vm12A/z8Cdu3Sq++ZvJfElokX2rkgGgw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-exporter-base": "0.214.0", + "@opentelemetry/otlp-transformer": "0.214.0", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" + "@opentelemetry/api": "^1.3.0" } }, "node_modules/@opentelemetry/instrumentation": { @@ -1272,199 +1215,40 @@ } }, "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.52.1.tgz", - "integrity": "sha512-z175NXOtX5ihdlshtYBe5RpGeBoTXVCKPPLiQlD6FHvpM4Ch+p2B0yWKYSrBfLH24H9zjJiBdTrtD+hLlfnXEQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/otlp-transformer": "0.52.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.214.0.tgz", + "integrity": "sha512-u1Gdv0/E9wP+apqWf7Wv2npXmgJtxsW2XL0TEv9FZloTZRuMBKmu8cYVXwS4Hm3q/f/3FuCnPTgiwYvIqRSpRg==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "2.6.1", + "@opentelemetry/otlp-transformer": "0.214.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" + "@opentelemetry/api": "^1.3.0" } }, "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.52.1.tgz", - "integrity": "sha512-I88uCZSZZtVa0XniRqQWKbjAUm73I8tpEy/uJYPPYw5d7BRdVk0RfTBQw8kSUl01oVWEuqxLDa802222MYyWHg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-logs": "0.52.1", - "@opentelemetry/sdk-metrics": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1", - "protobufjs": "^7.3.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.214.0.tgz", + "integrity": "sha512-DSaYcuBRh6uozfsWN3R8HsN0yDhCuWP7tOFdkUOVaWD1KVJg8m4qiLUsg/tNhTLS9HUYUcwNpwL2eroLtsZZ/w==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/sdk-logs": "0.214.0", + "@opentelemetry/sdk-metrics": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1", + "protobufjs": "^7.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz", - "integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/propagator-b3": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.30.1.tgz", - "integrity": "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.30.1.tgz", - "integrity": "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" + "@opentelemetry/api": "^1.3.0" } }, "node_modules/@opentelemetry/redis-common": { @@ -1477,184 +1261,65 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", + "integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", "license": "Apache-2.0", + "peer": true, "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/core": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-logs": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.52.1.tgz", - "integrity": "sha512-MBYh+WcPPsN8YpRHRmK1Hsca9pVlyyKd4BxOC4SsgHACnl/bPp4Cri9hWhVm5+2tiQ9Zf4qSc1Jshw9tOLGWQA==", + "version": "0.214.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.214.0.tgz", + "integrity": "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1" + "@opentelemetry/api-logs": "0.214.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz", - "integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "lodash.merge": "^4.6.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.1.tgz", + "integrity": "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz", - "integrity": "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz", + "integrity": "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==", "license": "Apache-2.0", "peer": true, "dependencies": { - "@opentelemetry/core": "2.6.0", - "@opentelemetry/resources": "2.6.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", - "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.6.0", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -1665,78 +1330,22 @@ } }, "node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.30.1.tgz", - "integrity": "sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.6.1.tgz", + "integrity": "sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/context-async-hooks": "1.30.1", - "@opentelemetry/core": "1.30.1", - "@opentelemetry/propagator-b3": "1.30.1", - "@opentelemetry/propagator-jaeger": "1.30.1", - "@opentelemetry/sdk-trace-base": "1.30.1", - "semver": "^7.5.2" + "@opentelemetry/context-async-hooks": "2.6.1", + "@opentelemetry/core": "2.6.1", + "@opentelemetry/sdk-trace-base": "2.6.1" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/context-async-hooks": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", - "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, "node_modules/@opentelemetry/semantic-conventions": { "version": "1.40.0", "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", @@ -2309,22 +1918,6 @@ "@opentelemetry/semantic-conventions": "^1.37.0" } }, - "node_modules/@sentry/node/node_modules/@opentelemetry/resources": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", - "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.6.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, "node_modules/@sentry/opentelemetry": { "version": "10.34.0", "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.34.0.tgz", @@ -3171,12 +2764,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "license": "MIT" - }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", diff --git a/package.json b/package.json index ede8219..02a020c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scope3data/observability-js", - "version": "1.1.0", + "version": "2.0.0", "description": "Unified observability (Sentry, OpenTelemetry, Pyroscope) for Node.js services", "keywords": [ "observability", @@ -49,10 +49,10 @@ }, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.52.0", - "@opentelemetry/resources": "^1.25.0", - "@opentelemetry/sdk-trace-node": "^1.25.0", - "@opentelemetry/semantic-conventions": "^1.25.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.214.0", + "@opentelemetry/resources": "^2.6.1", + "@opentelemetry/sdk-trace-node": "^2.6.1", + "@opentelemetry/semantic-conventions": "^1.40.0", "@pyroscope/nodejs": "^0.4.8", "@sentry/node": "10.34.0", "@sentry/opentelemetry": "10.34.0", diff --git a/src/provider.ts b/src/provider.ts index bf0faf3..da68149 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -1,5 +1,5 @@ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http' -import { Resource } from '@opentelemetry/resources' +import { resourceFromAttributes } from '@opentelemetry/resources' import { BatchSpanProcessor, NodeTracerProvider, @@ -8,8 +8,8 @@ import { import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, - SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, } from '@opentelemetry/semantic-conventions' +import { ATTR_DEPLOYMENT_ENVIRONMENT_NAME } from '@opentelemetry/semantic-conventions/incubating' import Pyroscope from '@pyroscope/nodejs' import * as Sentry from '@sentry/node' import { @@ -162,21 +162,16 @@ function initializeSentry(config: ResolvedConfig): void { } function initializeOtelProvider(config: ResolvedConfig): void { - const resource = new Resource({ + const resource = resourceFromAttributes({ [ATTR_SERVICE_NAME]: config.serviceName, - [SEMRESATTRS_DEPLOYMENT_ENVIRONMENT]: config.environment, + [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: config.environment, [ATTR_SERVICE_VERSION]: config.release, }) const sentryClient = Sentry.getClient() - const provider = new NodeTracerProvider({ - resource, - sampler: sentryClient ? new SentrySampler(sentryClient) : undefined, - }) - - provider.addSpanProcessor( + const spanProcessors: SpanProcessor[] = [ new SentrySpanProcessor() as unknown as SpanProcessor, - ) + ] if (config.otlp.enabled && config.otlp.endpoint) { const otlpExporter = new OTLPTraceExporter({ @@ -184,7 +179,7 @@ function initializeOtelProvider(config: ResolvedConfig): void { headers: config.otlp.headers, }) - provider.addSpanProcessor( + spanProcessors.push( new BatchSpanProcessor(otlpExporter, { maxExportBatchSize: 512, scheduledDelayMillis: 5000, @@ -193,6 +188,12 @@ function initializeOtelProvider(config: ResolvedConfig): void { ) } + const provider = new NodeTracerProvider({ + resource, + sampler: sentryClient ? new SentrySampler(sentryClient) : undefined, + spanProcessors, + }) + provider.register({ propagator: new SentryPropagator(), }) From 61c567beca02aa8633f42dc0f2eb9287e12f02d3 Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Mon, 13 Apr 2026 10:10:55 -0500 Subject: [PATCH 2/2] fix: address code review findings from PR #5 - types.ts: derive SentryIntegration via ReturnType instead of fragile Extract[number] - config.ts: clamp tracesSampler return value through clampSampleRate(), consistent with how sampleRate is handled - provider.ts: extract tracesSampler composition into exported buildTracesSampler() so the logic can be unit-tested directly - filtering.test.ts: replace duplicated makeSampler local copy with buildTracesSampler from the real implementation, so tests actually cover provider.ts logic --- src/__tests__/filtering.test.ts | 68 ++++++++++++++++----------------- src/config.ts | 7 +++- src/provider.ts | 57 +++++++++++++++++---------- src/types.ts | 10 ++--- 4 files changed, 80 insertions(+), 62 deletions(-) diff --git a/src/__tests__/filtering.test.ts b/src/__tests__/filtering.test.ts index e239c51..b0cf0fa 100644 --- a/src/__tests__/filtering.test.ts +++ b/src/__tests__/filtering.test.ts @@ -1,4 +1,6 @@ -import { describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, vi } from 'vitest' +import { resetConfig, resolveConfig } from '../config' +import { buildTracesSampler } from '../provider' const DEFAULT_FILTER_CONFIG = { ignoredRoutes: ['/health', '/health/liveness', '/metrics'], @@ -11,6 +13,9 @@ const DEFAULT_FILTER_CONFIG = { } describe('Observability Filtering', () => { + afterEach(() => { + resetConfig() + }) describe('tracesSampler logic', () => { const shouldDropTrace = (route: string): boolean => { const normalized = route.replace(/\/+$/, '') @@ -232,73 +237,66 @@ describe('Observability Filtering', () => { }) describe('custom tracesSampler hook', () => { - type SamplerContext = { + // Minimal stub satisfying TracesSamplerSamplingContext's required fields. + const ctx = (partial: { name?: string attributes?: Record - } + }) => + ({ + name: '', + inheritOrSampleWith: (r: number) => r, + ...partial, + }) as Parameters>[0] - // Replicates the composition logic from provider.ts initializeSentry const makeSampler = ( ignoredRoutes: string[], - customSampler?: (ctx: SamplerContext) => number | undefined, + customSampler?: (c: ReturnType) => number | undefined, defaultRate = 1.0, ) => { - return (ctx: SamplerContext): number => { - const httpTarget = ctx.attributes?.['http.target'] as string | undefined - const rawRoute = httpTarget || ctx.name || '' - const route = rawRoute.replace(/\/+$/, '') - - for (const ignored of ignoredRoutes) { - if (route === ignored || route.startsWith(`${ignored}?`)) { - return 0 - } - } - - if (customSampler) { - const result = customSampler(ctx) - if (result !== undefined) return result - } - - return defaultRate - } + const config = resolveConfig({ + serviceName: 'test', + filters: { ignoredRoutes }, + sentry: { sampleRate: defaultRate, tracesSampler: customSampler }, + }) + return buildTracesSampler(config) } it('falls back to sampleRate when no custom sampler provided', () => { const sampler = makeSampler(['/health'], undefined, 0.5) - expect(sampler({ name: '/api/data' })).toBe(0.5) + expect(sampler(ctx({ name: '/api/data' }))).toBe(0.5) }) it('custom sampler return value overrides sampleRate', () => { - const custom = (ctx: SamplerContext) => { - if (ctx.attributes?.['customer.id'] === '84') return 0 + const custom = (c: ReturnType) => { + if (c.attributes?.['customer.id'] === '84') return 0 return undefined } const sampler = makeSampler([], custom, 0.5) - expect(sampler({ attributes: { 'customer.id': '84' } })).toBe(0) - expect(sampler({ attributes: { 'customer.id': '1' } })).toBe(0.5) + expect(sampler(ctx({ attributes: { 'customer.id': '84' } }))).toBe(0) + expect(sampler(ctx({ attributes: { 'customer.id': '1' } }))).toBe(0.5) }) it('custom sampler returning undefined falls back to sampleRate', () => { - const custom = (_ctx: SamplerContext) => undefined + const custom = () => undefined const sampler = makeSampler([], custom, 0.75) - expect(sampler({ name: '/api/data' })).toBe(0.75) + expect(sampler(ctx({ name: '/api/data' }))).toBe(0.75) }) it('ignoredRoutes check short-circuits before custom sampler is called', () => { const custom = vi.fn(() => 1.0 as number | undefined) const sampler = makeSampler(['/health'], custom, 0.5) - expect(sampler({ name: '/health' })).toBe(0) + expect(sampler(ctx({ name: '/health' }))).toBe(0) expect(custom).not.toHaveBeenCalled() }) it('custom sampler can return fractional sample rates', () => { - const custom = (ctx: SamplerContext) => { - if (ctx.attributes?.['tier'] === 'premium') return 1.0 + const custom = (c: ReturnType) => { + if (c.attributes?.['tier'] === 'premium') return 1.0 return 0.1 } const sampler = makeSampler([], custom, 0.5) - expect(sampler({ attributes: { tier: 'premium' } })).toBe(1.0) - expect(sampler({ attributes: { tier: 'free' } })).toBe(0.1) + expect(sampler(ctx({ attributes: { tier: 'premium' } }))).toBe(1.0) + expect(sampler(ctx({ attributes: { tier: 'free' } }))).toBe(0.1) }) }) }) diff --git a/src/config.ts b/src/config.ts index e7d06f5..f1b5cf8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,7 +47,12 @@ export function resolveConfig(config: ObservabilityConfig): ResolvedConfig { enabled: sentryEnabled, sampleRate: sentrySampleRate, profileSampleRate: config.sentry?.profileSampleRate ?? sentrySampleRate, - tracesSampler: config.sentry?.tracesSampler, + tracesSampler: config.sentry?.tracesSampler + ? (ctx) => { + const rate = config.sentry!.tracesSampler!(ctx) + return rate !== undefined ? clampSampleRate(rate) : undefined + } + : undefined, integrations: config.sentry?.integrations ?? [], }, diff --git a/src/provider.ts b/src/provider.ts index da68149..36033f8 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -101,27 +101,7 @@ function initializeSentry(config: ResolvedConfig): void { ...config.sentry.integrations, ], enabled: config.sentry.enabled, - tracesSampler: (samplingContext) => { - const { name, attributes } = samplingContext - const httpTarget = attributes?.['http.target'] as string | undefined - const rawRoute = httpTarget || name || '' - const route = rawRoute.replace(/\/+$/, '') - - for (const ignoredRoute of config.filters.ignoredRoutes) { - if (route === ignoredRoute || route.startsWith(`${ignoredRoute}?`)) { - return 0 - } - } - - if (config.sentry.tracesSampler) { - const result = config.sentry.tracesSampler(samplingContext) - if (result !== undefined) { - return result - } - } - - return config.sentry.sampleRate - }, + tracesSampler: buildTracesSampler(config), beforeSend(event, hint) { const error = hint?.originalException @@ -161,6 +141,41 @@ function initializeSentry(config: ResolvedConfig): void { }) } +/** + * Builds the Sentry `tracesSampler` function from a resolved config. + * + * Exported for unit testing. Not intended for direct use by consumers. + */ +export function buildTracesSampler( + config: Pick, +) { + return ( + samplingContext: Parameters< + NonNullable + >[0], + ): number => { + const { name, attributes } = samplingContext + const httpTarget = attributes?.['http.target'] as string | undefined + const rawRoute = httpTarget || name || '' + const route = rawRoute.replace(/\/+$/, '') + + for (const ignoredRoute of config.filters.ignoredRoutes) { + if (route === ignoredRoute || route.startsWith(`${ignoredRoute}?`)) { + return 0 + } + } + + if (config.sentry.tracesSampler) { + const result = config.sentry.tracesSampler(samplingContext) + if (result !== undefined) { + return result + } + } + + return config.sentry.sampleRate + } +} + function initializeOtelProvider(config: ResolvedConfig): void { const resource = resourceFromAttributes({ [ATTR_SERVICE_NAME]: config.serviceName, diff --git a/src/types.ts b/src/types.ts index db50261..9ba1cc9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,10 @@ import type { Span as OtelSpan } from '@opentelemetry/api' -import type { NodeOptions } from '@sentry/node' +import type * as Sentry from '@sentry/node' -type SamplingContext = Parameters>[0] -type SentryIntegration = NonNullable< - Extract ->[number] +type SamplingContext = Parameters< + NonNullable +>[0] +type SentryIntegration = ReturnType /** Sentry error monitoring and profiling configuration. */ export interface SentryConfig {