From 98cf439f64061b14a54e3053450e23d41d20128b Mon Sep 17 00:00:00 2001 From: Gerard Soldevila Date: Mon, 29 Jun 2026 17:59:41 +0200 Subject: [PATCH 01/11] chore(deps): upgrade @opentelemetry/core and @opentelemetry/sdk-metrics to 2.x Bumps `@opentelemetry/core` and `@opentelemetry/sdk-metrics` from 1.x to 2.8.0. This picks up the upstream fix for CVE-2026-54285 (unbounded `baggage` header processing) in `@opentelemetry/core`. The OTel Metrics SDK 2.x has breaking API changes that required updating the agent's metrics integration: - The aggregation classes (`ExplicitBucketHistogramAggregation`, `SumAggregation`, `LastValueAggregation`, `DropAggregation`) were removed in favour of `AggregationOption` config objects keyed by `AggregationType`. - `MeterProvider.addMetricReader()` was removed; metric readers must now be passed via the constructor `readers` option. Updated `lib/opentelemetry-metrics`, the `@opentelemetry/sdk-metrics` instrumentation, the OTel metrics test fixtures, and the example to the 2.x API. The minimum supported `@opentelemetry/sdk-metrics` version for instrumentation is now 2.0.0. Co-authored-by: Cursor --- docs/release-notes/index.md | 2 + examples/opentelemetry-metrics/package.json | 4 +- .../use-otel-metrics-sdk.js | 24 ++-- .../modules/@opentelemetry/sdk-metrics.js | 19 +-- .../ElasticApmMetricExporter.js | 22 +-- lib/opentelemetry-metrics/index.js | 5 +- package-lock.json | 125 +++++++----------- package.json | 4 +- .../fixtures/package-lock.json | 114 ++++++++-------- .../fixtures/package.json | 4 +- .../fixtures/use-just-otel-sdk.js | 20 +-- ...otel-api-with-registered-meter-provider.js | 5 +- 12 files changed, 165 insertions(+), 183 deletions(-) diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index e55672341a..b6a436194d 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -42,6 +42,8 @@ To check for security updates, go to [Security announcements for the Elastic sta * Remove the "Get started with Restify" document and stop testing instrumentation against Restify. Restify is no longer getting maintenance. The instrumentation support for restify is unchanged, so this is not a breaking change. ([#4892](https://github.com/elastic/apm-agent-nodejs/pull/4892)) +* Upgrade `@opentelemetry/core` and `@opentelemetry/sdk-metrics` to 2.x. This includes the upstream fix for [CVE-2026-54285](https://github.com/open-telemetry/opentelemetry-js/security/advisories/GHSA-8988-4f7v-96qf) in `@opentelemetry/core`. The OTel Metrics SDK integration was updated for the 2.x API changes (aggregation configuration objects and `MeterProvider` reader registration via the constructor). ([#5126](https://github.com/elastic/apm-agent-nodejs/pull/5126)) + ## 4.15.0 [4-15-0] **Release date:** Oct 16, 2025 diff --git a/examples/opentelemetry-metrics/package.json b/examples/opentelemetry-metrics/package.json index 78e7913c7d..d2fb12ba84 100644 --- a/examples/opentelemetry-metrics/package.json +++ b/examples/opentelemetry-metrics/package.json @@ -4,8 +4,8 @@ "private": true, "dependencies": { "@opentelemetry/api": "^1.4.1", - "@opentelemetry/exporter-prometheus": ">=0.41.0 <2", - "@opentelemetry/sdk-metrics": "^1.12.0", + "@opentelemetry/exporter-prometheus": ">=0.200.0 <1", + "@opentelemetry/sdk-metrics": "^2.8.0", "elastic-apm-node": "file:../.." } } diff --git a/examples/opentelemetry-metrics/use-otel-metrics-sdk.js b/examples/opentelemetry-metrics/use-otel-metrics-sdk.js index c5f5cfada5..e3bbcb94a6 100644 --- a/examples/opentelemetry-metrics/use-otel-metrics-sdk.js +++ b/examples/opentelemetry-metrics/use-otel-metrics-sdk.js @@ -29,26 +29,28 @@ const { performance } = require('perf_hooks'); const otel = require('@opentelemetry/api'); const { MeterProvider, - View, - ExplicitBucketHistogramAggregation, + AggregationType, } = require('@opentelemetry/sdk-metrics'); const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); const exporter = new PrometheusExporter({ host: '127.0.0.1', port: 3001 }); const meterProvider = new MeterProvider({ + readers: [exporter], views: [ - new View({ + { instrumentName: 'latency', - aggregation: new ExplicitBucketHistogramAggregation( - // Use the same default buckets as in `prom-client` for comparison. - // This is to demonstrate using a View. The default buckets used by the - // APM agent would suffice as well. - [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], - ), - }), + aggregation: { + type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM, + options: { + // Use the same default buckets as in `prom-client` for comparison. + // This is to demonstrate using a View. The default buckets used by + // the APM agent would suffice as well. + boundaries: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + }, + }, + }, ], }); -meterProvider.addMetricReader(exporter); otel.metrics.setGlobalMeterProvider(meterProvider); console.log('Prometheus metrics at http://127.0.0.1:3001/metrics'); diff --git a/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js b/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js index e266852f3e..cdbdb4c5f1 100644 --- a/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +++ b/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js @@ -37,10 +37,10 @@ module.exports = function (mod, agent, { version, enabled }) { ); return mod; } - // Minimum supported version is 1.11.0 because that version included the fix - // for side-effects from having two MetricReaders. - // https://github.com/open-telemetry/opentelemetry-js/issues/3664 - if (!semver.satisfies(version, '>=1.11.0 <2', { includePrerelease: true })) { + // Minimum supported version is 2.0.0. In OTel SDK 2.x the `MeterProvider` + // requires metric readers to be passed via the constructor `readers` option; + // the `addMetricReader()` method was removed. + if (!semver.satisfies(version, '>=2.0.0 <3', { includePrerelease: true })) { log.debug( '@opentelemetry/sdk-metrics@%s is not supported, skipping @opentelemetry/sdk-metrics instrumentation', version, @@ -56,15 +56,18 @@ module.exports = function (mod, agent, { version, enabled }) { } class ApmMeterProvider extends mod.MeterProvider { - constructor(...args) { - super(...args); + constructor(options = {}) { // We create a new metric reader for each new MeterProvider instance, // because they shutdown independently -- they cannot be shared between - // multiple MeterProviders. + // multiple MeterProviders. In OTel SDK 2.x readers can only be provided + // via the constructor, so the agent's reader is appended to any the user + // already configured. log.trace( '@opentelemetry/sdk-metrics ins: create Elastic APM MetricReader', ); - this.addMetricReader(createOTelMetricReader(agent)); + const readers = options.readers ? [...options.readers] : []; + readers.push(createOTelMetricReader(agent)); + super({ ...options, readers }); } } Object.defineProperty(mod, 'MeterProvider', { diff --git a/lib/opentelemetry-metrics/ElasticApmMetricExporter.js b/lib/opentelemetry-metrics/ElasticApmMetricExporter.js index 0aad0813ca..20fce54cd3 100644 --- a/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +++ b/lib/opentelemetry-metrics/ElasticApmMetricExporter.js @@ -8,10 +8,7 @@ const { ExportResultCode } = require('@opentelemetry/core'); const { AggregationTemporality, InstrumentType, - ExplicitBucketHistogramAggregation, - SumAggregation, - LastValueAggregation, - DropAggregation, + AggregationType, DataPointType, } = require('@opentelemetry/sdk-metrics'); const { LRUCache } = require('lru-cache'); @@ -96,12 +93,15 @@ function fillIntakeHistogramSample(sample, otelDataPoint) { class ElasticApmMetricExporter { constructor(agent) { this._agent = agent; - this._histogramAggregation = new ExplicitBucketHistogramAggregation( - this._agent._conf.customMetricsHistogramBoundaries, - ); - this._sumAggregation = new SumAggregation(); - this._lastValueAggregation = new LastValueAggregation(); - this._dropAggregation = new DropAggregation(); + this._histogramAggregation = { + type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM, + options: { + boundaries: this._agent._conf.customMetricsHistogramBoundaries, + }, + }; + this._sumAggregation = { type: AggregationType.SUM }; + this._lastValueAggregation = { type: AggregationType.LAST_VALUE }; + this._dropAggregation = { type: AggregationType.DROP }; this._attrDropWarnCache = new LRUCache({ max: 1000 }); this._dataPointTypeDropWarnCache = new LRUCache({ max: 1000 }); } @@ -110,7 +110,7 @@ class ElasticApmMetricExporter { * Spec: https://github.com/elastic/apm/blob/main/specs/agents/metrics-otel.md#aggregation * * @param {import('@opentelemetry/sdk-metrics').InstrumentType} instrumentType - * @returns {import('@opentelemetry/sdk-metrics').Aggregation} + * @returns {import('@opentelemetry/sdk-metrics').AggregationOption} */ selectAggregation(instrumentType) { // The same behaviour as OTel's `DefaultAggregation`, except for changes diff --git a/lib/opentelemetry-metrics/index.js b/lib/opentelemetry-metrics/index.js index 441ea26189..b9f06b9eca 100644 --- a/lib/opentelemetry-metrics/index.js +++ b/lib/opentelemetry-metrics/index.js @@ -38,8 +38,9 @@ function createOTelMetricReader(agent) { } function createOTelMeterProvider(agent) { - const meterProvider = new MeterProvider(); - meterProvider.addMetricReader(createOTelMetricReader(agent)); + const meterProvider = new MeterProvider({ + readers: [createOTelMetricReader(agent)], + }); return meterProvider; } diff --git a/package-lock.json b/package-lock.json index ffc5cb7983..7727dbeece 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "dependencies": { "@elastic/ecs-pino-format": "^1.5.0", "@opentelemetry/api": "^1.4.1", - "@opentelemetry/core": "^1.11.0", - "@opentelemetry/sdk-metrics": "^1.12.0", + "@opentelemetry/core": "^2.8.0", + "@opentelemetry/sdk-metrics": "^2.8.0", "after-all-results": "^2.0.0", "agentkeepalive": "^4.2.1", "async-value-promise": "^1.1.1", @@ -4310,30 +4310,6 @@ "node": ">=20" } }, - "node_modules/@elastic/transport/node_modules/@opentelemetry/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", - "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", - "dev": true, - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@elastic/transport/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", - "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@elastic/transport/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -5355,53 +5331,57 @@ } }, "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.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" + "@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": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/core": "2.8.0", + "@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" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", + "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "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==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", "engines": { "node": ">=14" } @@ -20066,21 +20046,6 @@ "undici": "^7.16.0" }, "dependencies": { - "@opentelemetry/core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", - "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", - "dev": true, - "requires": { - "@opentelemetry/semantic-conventions": "^1.29.0" - } - }, - "@opentelemetry/semantic-conventions": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", - "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", - "dev": true - }, "debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -20919,35 +20884,35 @@ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" }, "@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.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", "requires": { - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/semantic-conventions": "^1.29.0" } }, "@opentelemetry/resources": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", "requires": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" } }, "@opentelemetry/sdk-metrics": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", + "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", "requires": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0" } }, "@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==" + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==" }, "@pinojs/redact": { "version": "0.4.0", diff --git a/package.json b/package.json index 5eb79b0fe9..c948956dbd 100644 --- a/package.json +++ b/package.json @@ -92,8 +92,8 @@ "dependencies": { "@elastic/ecs-pino-format": "^1.5.0", "@opentelemetry/api": "^1.4.1", - "@opentelemetry/core": "^1.11.0", - "@opentelemetry/sdk-metrics": "^1.12.0", + "@opentelemetry/core": "^2.8.0", + "@opentelemetry/sdk-metrics": "^2.8.0", "after-all-results": "^2.0.0", "agentkeepalive": "^4.2.1", "async-value-promise": "^1.1.1", diff --git a/test/opentelemetry-metrics/fixtures/package-lock.json b/test/opentelemetry-metrics/fixtures/package-lock.json index cef242197a..73010ae93e 100644 --- a/test/opentelemetry-metrics/fixtures/package-lock.json +++ b/test/opentelemetry-metrics/fixtures/package-lock.json @@ -9,8 +9,8 @@ "version": "1.0.0", "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-prometheus": ">=0.41.2 <2", - "@opentelemetry/sdk-metrics": "^1.30.0" + "@opentelemetry/exporter-prometheus": ">=0.200.0 <1", + "@opentelemetry/sdk-metrics": "^2.8.0" } }, "node_modules/@opentelemetry/api": { @@ -22,70 +22,75 @@ } }, "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.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" + "@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/exporter-prometheus": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.57.2.tgz", - "integrity": "sha512-VqIqXnuxWMWE/1NatAGtB1PvsQipwxDcdG4RwA/umdBcW3/iOHp0uejvFHTRN2O78ZPged87ErJajyUBPUhlDQ==", + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.219.0.tgz", + "integrity": "sha512-TxOnJ85eWJY5JyOJsNMXiRTYlkDcOv0u3KbXEzWCc+tUS9sjL/BC6BcdxZ0B9r2OFVqsrZFXUzSD2sZUy42Ucw==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1", - "@opentelemetry/sdk-metrics": "1.30.1" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-metrics": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "node_modules/@opentelemetry/resources": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/core": "2.8.0", + "@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" + "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", + "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", + "license": "Apache-2.0", "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0" }, "engines": { - "node": ">=14" + "node": "^18.19.0 || >=20.6.0" }, "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" + "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "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==", + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", "engines": { "node": ">=14" } @@ -98,45 +103,46 @@ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" }, "@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.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz", + "integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==", "requires": { - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/semantic-conventions": "^1.29.0" } }, "@opentelemetry/exporter-prometheus": { - "version": "0.57.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.57.2.tgz", - "integrity": "sha512-VqIqXnuxWMWE/1NatAGtB1PvsQipwxDcdG4RwA/umdBcW3/iOHp0uejvFHTRN2O78ZPged87ErJajyUBPUhlDQ==", + "version": "0.219.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.219.0.tgz", + "integrity": "sha512-TxOnJ85eWJY5JyOJsNMXiRTYlkDcOv0u3KbXEzWCc+tUS9sjL/BC6BcdxZ0B9r2OFVqsrZFXUzSD2sZUy42Ucw==", "requires": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1", - "@opentelemetry/sdk-metrics": "1.30.1" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0", + "@opentelemetry/sdk-metrics": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" } }, "@opentelemetry/resources": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz", + "integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==", "requires": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/semantic-conventions": "^1.29.0" } }, "@opentelemetry/sdk-metrics": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.30.1.tgz", - "integrity": "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.8.0.tgz", + "integrity": "sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==", "requires": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1" + "@opentelemetry/core": "2.8.0", + "@opentelemetry/resources": "2.8.0" } }, "@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==" + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==" } } } diff --git a/test/opentelemetry-metrics/fixtures/package.json b/test/opentelemetry-metrics/fixtures/package.json index 8c10129770..e1dafd6634 100644 --- a/test/opentelemetry-metrics/fixtures/package.json +++ b/test/opentelemetry-metrics/fixtures/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-prometheus": ">=0.41.2 <2", - "@opentelemetry/sdk-metrics": "^1.30.0" + "@opentelemetry/exporter-prometheus": ">=0.200.0 <1", + "@opentelemetry/sdk-metrics": "^2.8.0" } } diff --git a/test/opentelemetry-metrics/fixtures/use-just-otel-sdk.js b/test/opentelemetry-metrics/fixtures/use-just-otel-sdk.js index 078c32f56e..9c5f083259 100644 --- a/test/opentelemetry-metrics/fixtures/use-just-otel-sdk.js +++ b/test/opentelemetry-metrics/fixtures/use-just-otel-sdk.js @@ -16,24 +16,26 @@ const { MeterProvider, - View, - ExplicitBucketHistogramAggregation, + AggregationType, } = require('@opentelemetry/sdk-metrics'); const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); const exporter = new PrometheusExporter({ host: '127.0.0.1' }); const meterProvider = new MeterProvider({ + readers: [exporter], views: [ - new View({ + { instrumentName: 'test_histogram_viewbuckets', - aggregation: new ExplicitBucketHistogramAggregation( - // Use the same default buckets as in `prom-client`. - [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], - ), - }), + aggregation: { + type: AggregationType.EXPLICIT_BUCKET_HISTOGRAM, + options: { + // Use the same default buckets as in `prom-client`. + boundaries: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], + }, + }, + }, ], }); -meterProvider.addMetricReader(exporter); const meter = meterProvider.getMeter('test-meter'); diff --git a/test/opentelemetry-metrics/fixtures/use-otel-api-with-registered-meter-provider.js b/test/opentelemetry-metrics/fixtures/use-otel-api-with-registered-meter-provider.js index b0231d3e70..32b7af45a2 100644 --- a/test/opentelemetry-metrics/fixtures/use-otel-api-with-registered-meter-provider.js +++ b/test/opentelemetry-metrics/fixtures/use-otel-api-with-registered-meter-provider.js @@ -23,8 +23,9 @@ const { MeterProvider } = require('@opentelemetry/sdk-metrics'); const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus'); const exporter = new PrometheusExporter({ host: '127.0.0.1' }); -const meterProvider = new MeterProvider(); -meterProvider.addMetricReader(exporter); +const meterProvider = new MeterProvider({ + readers: [exporter], +}); otel.metrics.setGlobalMeterProvider(meterProvider); const meter = otel.metrics.getMeter('test-meter'); From 50577ce70444f373b02fbc460ca2272ad3dd862e Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 14:13:44 -0700 Subject: [PATCH 02/11] fix(opentelemetry-bridge): only load opentelemetry sdk packages if opentelemetryBridgeEnabled - Extend the `opentelemetryBridgeEnabled` config setting to *also* cover the integration with `@opentelemetry/sdk-metrics`. This was an oversight with OTel Metrics support was added after the initial support for OTel Tracing. This handles https://github.com/elastic/apm-agent-nodejs/issues/4956 - Ensure that `@opentelemetry/*` libs are **only require'd if `opentelemetryBridgeEnabled` is true** and if the current running Node.js version is in the supported range for the included Node.js SDK. If a user explicitly tries to enable the OTel Bridge, but is using too old of a Node.js version (e.g. 14 or 16), then the APM agent will log a warning and set `opentelemetryBridgeEnabled = false`. These changes allow the OpenTelemetry integration support to update the internally included `@opentelemetry/*` libs without hard breaking usage of the APM agent with older Node.js versions. It *does* mean that the OpenTelemetry Bridge no longer works with Node.js 14 and 18 (requires "^18.19.0 || >=20.6.0"). This is breaking for those users, but the Bridge is experimental, so that is allowed in minor releases. Closes: https://github.com/elastic/apm-agent-nodejs/issues/4956 --- lib/agent.js | 10 ++++----- lib/config/config.js | 21 ++++++++++++++++++ .../modules/@opentelemetry/api.js | 13 +++-------- .../modules/@opentelemetry/sdk-metrics.js | 22 ++++++------------- lib/opentelemetry-metrics/index.js | 12 ---------- .../OTelBridgeNonRecordingSpan.test.js | 11 ++++++++++ .../OTelBridgeRunContext.test.js | 11 ++++++++++ .../active-span-and-context-interop.test.js | 12 +++++++++- test/opentelemetry-bridge/fixtures.test.js | 14 +++++++++++- .../interface-ContextManager.test.js | 12 ++++++++++ .../otel-bridge-feature.test.js | 11 ++++++++++ test/opentelemetry-metrics/fixtures.test.js | 9 +++++--- 12 files changed, 110 insertions(+), 48 deletions(-) diff --git a/lib/agent.js b/lib/agent.js index 090ad6e45f..2006e04449 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -31,10 +31,6 @@ const symbols = require('./symbols'); const { frameCacheStats, initStackTraceCollection } = require('./stacktraces'); const Span = require('./instrumentation/span'); const Transaction = require('./instrumentation/transaction'); -const { - isOTelMetricsFeatSupported, - createOTelMeterProvider, -} = require('./opentelemetry-metrics'); const { createApmClient } = require('./apm-client/apm-client'); const IncomingMessage = http.IncomingMessage; @@ -398,14 +394,16 @@ Agent.prototype._getOrCreateOTelMeterProvider = function () { return this._otelMeterProvider; } - if (!this._isMetricsEnabled()) { + if (!this._conf.opentelemetryBridgeEnabled) { return null; } - if (!isOTelMetricsFeatSupported) { + if (!this._isMetricsEnabled()) { return null; } this.logger.trace('create Elastic APM MeterProvider for @opentelemetry/api'); + // Only import anything `@opentelemetry/*` if `opentelemetryBridgeEnabled`. + const { createOTelMeterProvider } = require('./opentelemetry-metrics'); this._otelMeterProvider = createOTelMeterProvider(this); return this._otelMeterProvider; }; diff --git a/lib/config/config.js b/lib/config/config.js index 58c1e8459d..a6ad3f7662 100644 --- a/lib/config/config.js +++ b/lib/config/config.js @@ -9,6 +9,7 @@ const fs = require('fs'); const path = require('path'); +const semver = require('semver'); const truncate = require('unicode-byte-truncate'); const { INTAKE_STRING_MAX_SIZE, REDACTED } = require('../constants'); @@ -222,6 +223,26 @@ class Config { this.breakdownMetrics = false; } + // Override `opentelemetryBridgeEnabled` if the current Node.js version + // is not supported by the included OTel SDK deps. + if (this.opentelemetryBridgeEnabled) { + // The supported Node.js version range from "package.json#engines.node" + // in `@opentelemetry/{core,sdk-metrics}`. + const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; + const canSupportOpenTelemetryBridge = semver.satisfies( + process.version, + OTEL_SDK_ENGINES_NODE, + ); + if (!canSupportOpenTelemetryBridge) { + this.opentelemetryBridgeEnabled = false; + this.logger.warn( + 'Cannot support OpenTelemetry Bridge feature with Node.js "%s", requires "%s". OpenTelemetry integration will be disabled.', + process.version, + OTEL_SDK_ENGINES_NODE, + ); + } + } + this.loggingPreambleData = getPreambleData(this); } diff --git a/lib/instrumentation/modules/@opentelemetry/api.js b/lib/instrumentation/modules/@opentelemetry/api.js index 7bc8f3ff69..d90f6fee51 100644 --- a/lib/instrumentation/modules/@opentelemetry/api.js +++ b/lib/instrumentation/modules/@opentelemetry/api.js @@ -14,9 +14,6 @@ const semver = require('semver'); -const { - isOTelMetricsFeatSupported, -} = require('../../../opentelemetry-metrics'); const shimmer = require('../../shimmer'); /** @@ -43,6 +40,9 @@ function isNoopMeterProvider(provider) { module.exports = function (mod, agent, { version, enabled }) { const log = agent.logger; + if (!agent._conf.opentelemetryBridgeEnabled) { + return mod; + } if (!enabled) { return mod; } @@ -60,13 +60,6 @@ module.exports = function (mod, agent, { version, enabled }) { ); return mod; } - if (!isOTelMetricsFeatSupported) { - log.debug( - 'elastic-apm-node OTel Metrics support does not support node %s, skipping @opentelemetry/api instrumentation', - process.version, - ); - return mod; - } log.debug('shimming @opentelemetry/api .metrics.getMeterProvider()'); shimmer.wrap(mod.metrics, 'getMeterProvider', wrapGetMeterProvider); diff --git a/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js b/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js index cdbdb4c5f1..a5ea2f3906 100644 --- a/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +++ b/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js @@ -19,14 +19,12 @@ const semver = require('semver'); -const { - isOTelMetricsFeatSupported, - createOTelMetricReader, -} = require('../../../opentelemetry-metrics'); - module.exports = function (mod, agent, { version, enabled }) { const log = agent.logger; + if (!agent._conf.opentelemetryBridgeEnabled) { + return mod; + } if (!enabled) { return mod; } @@ -37,9 +35,7 @@ module.exports = function (mod, agent, { version, enabled }) { ); return mod; } - // Minimum supported version is 2.0.0. In OTel SDK 2.x the `MeterProvider` - // requires metric readers to be passed via the constructor `readers` option; - // the `addMetricReader()` method was removed. + // Minimum supported version is 2.0.0. if (!semver.satisfies(version, '>=2.0.0 <3', { includePrerelease: true })) { log.debug( '@opentelemetry/sdk-metrics@%s is not supported, skipping @opentelemetry/sdk-metrics instrumentation', @@ -47,13 +43,9 @@ module.exports = function (mod, agent, { version, enabled }) { ); return mod; } - if (!isOTelMetricsFeatSupported) { - log.debug( - 'elastic-apm-node OTel Metrics feature does not support node %s, skipping @opentelemetry/sdk-metrics instrumentation', - process.version, - ); - return mod; - } + + // Only import anything `@opentelemetry/*` if `opentelemetryBridgeEnabled`. + const { createOTelMetricReader } = require('../../../opentelemetry-metrics'); class ApmMeterProvider extends mod.MeterProvider { constructor(options = {}) { diff --git a/lib/opentelemetry-metrics/index.js b/lib/opentelemetry-metrics/index.js index b9f06b9eca..7c3ec24681 100644 --- a/lib/opentelemetry-metrics/index.js +++ b/lib/opentelemetry-metrics/index.js @@ -10,20 +10,9 @@ const { MeterProvider, PeriodicExportingMetricReader, } = require('@opentelemetry/sdk-metrics'); -const semver = require('semver'); const ElasticApmMetricExporter = require('./ElasticApmMetricExporter'); -// `isOTelMetricsFeatSupported` is true if the agent's included OTel Metrics -// feature is supported. Currently this depends on the Node.js version supported -// by the Metrics SDK package. -const _supportRange = require('@opentelemetry/sdk-metrics/package.json').engines - .node; -const isOTelMetricsFeatSupported = semver.satisfies( - process.version, - _supportRange, -); - function createOTelMetricReader(agent) { const metricsInterval = agent._conf.metricsInterval; assert( @@ -45,7 +34,6 @@ function createOTelMeterProvider(agent) { } module.exports = { - isOTelMetricsFeatSupported, createOTelMetricReader, createOTelMeterProvider, }; diff --git a/test/opentelemetry-bridge/OTelBridgeNonRecordingSpan.test.js b/test/opentelemetry-bridge/OTelBridgeNonRecordingSpan.test.js index e8edd46597..2d7918ed16 100644 --- a/test/opentelemetry-bridge/OTelBridgeNonRecordingSpan.test.js +++ b/test/opentelemetry-bridge/OTelBridgeNonRecordingSpan.test.js @@ -6,6 +6,17 @@ 'use strict'; +const semver = require('semver'); +const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; +if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { + console.log( + '# SKIP: skipping %s, OTel SDK does not support node %s', + __filename, + process.version, + ); + process.exit(0); +} + const apm = require('../..').start({ opentelemetryBridgeEnabled: true, // Make the agent quiet. diff --git a/test/opentelemetry-bridge/OTelBridgeRunContext.test.js b/test/opentelemetry-bridge/OTelBridgeRunContext.test.js index f45aa81c55..f912ddf6d9 100644 --- a/test/opentelemetry-bridge/OTelBridgeRunContext.test.js +++ b/test/opentelemetry-bridge/OTelBridgeRunContext.test.js @@ -6,6 +6,17 @@ 'use strict'; +const semver = require('semver'); +const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; +if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { + console.log( + '# SKIP: skipping %s, OTel SDK does not support node %s', + __filename, + process.version, + ); + process.exit(0); +} + require('../..').start({ opentelemetryBridgeEnabled: true, // Make the agent quiet. diff --git a/test/opentelemetry-bridge/active-span-and-context-interop.test.js b/test/opentelemetry-bridge/active-span-and-context-interop.test.js index f5c9da9f58..2b7a8766ae 100644 --- a/test/opentelemetry-bridge/active-span-and-context-interop.test.js +++ b/test/opentelemetry-bridge/active-span-and-context-interop.test.js @@ -10,6 +10,17 @@ // the OTel API. Cases are described here: // https://github.com/elastic/apm/blob/main/specs/agents/tracing-api-otel.md#active-spans-and-context +const semver = require('semver'); +const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; +if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { + console.log( + '# SKIP: skipping %s, OTel SDK does not support node %s', + __filename, + process.version, + ); + process.exit(0); +} + const apm = require('../..').start({ opentelemetryBridgeEnabled: true, // Make the agent quiet. @@ -22,7 +33,6 @@ const apm = require('../..').start({ }); const otel = require('@opentelemetry/api'); -const semver = require('semver'); const tape = require('tape'); const { safeGetPackageVersion } = require('../_utils'); diff --git a/test/opentelemetry-bridge/fixtures.test.js b/test/opentelemetry-bridge/fixtures.test.js index e70941062c..c7f21998d7 100644 --- a/test/opentelemetry-bridge/fixtures.test.js +++ b/test/opentelemetry-bridge/fixtures.test.js @@ -33,6 +33,16 @@ const { const { MockAPMServer } = require('../_mock_apm_server'); const { findObjInArray } = require('../_utils'); +const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; +if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { + console.log( + '# SKIP: skipping %s, OTel SDK does not support node %s', + __filename, + process.version, + ); + process.exit(0); +} + const haveUsablePerformanceNow = semver.satisfies(process.version, '>=8.12.0'); const cases = [ @@ -734,7 +744,9 @@ const cases = [ }, ]; -cases.forEach((c) => { +// XXX +// cases.forEach((c) => { +[cases[0]].forEach((c) => { tape.test( `test/opentelemetry-bridge/fixtures/${c.script}`, c.testOpts || {}, diff --git a/test/opentelemetry-bridge/interface-ContextManager.test.js b/test/opentelemetry-bridge/interface-ContextManager.test.js index 5710894e41..79328be44c 100644 --- a/test/opentelemetry-bridge/interface-ContextManager.test.js +++ b/test/opentelemetry-bridge/interface-ContextManager.test.js @@ -8,6 +8,18 @@ // Exercise the full `interface ContextManager`. +const semver = require('semver'); +const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; +if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { + console.log( + '# SKIP: skipping %s, OTel SDK does not support node %s', + __filename, + process.version, + ); + process.exit(0); +} + + require('../..').start({ opentelemetryBridgeEnabled: true, // Make the agent quiet. diff --git a/test/opentelemetry-bridge/otel-bridge-feature.test.js b/test/opentelemetry-bridge/otel-bridge-feature.test.js index b7f52aea2a..7dc87e4e85 100644 --- a/test/opentelemetry-bridge/otel-bridge-feature.test.js +++ b/test/opentelemetry-bridge/otel-bridge-feature.test.js @@ -20,6 +20,17 @@ // - Currently at commit 4412a55 of https://github.com/elastic/apm/commits/main/tests/agents/gherkin-specs/otel_bridge.feature // - Each "Scenario: ..." from "otel_bridge.feature" is a separate test case below. +const semver = require('semver'); +const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; +if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { + console.log( + '# SKIP: skipping %s, OTel SDK does not support node %s', + __filename, + process.version, + ); + process.exit(0); +} + const otel = require('@opentelemetry/api'); const tape = require('tape'); diff --git a/test/opentelemetry-metrics/fixtures.test.js b/test/opentelemetry-metrics/fixtures.test.js index 852c287812..e23d1dcd52 100644 --- a/test/opentelemetry-metrics/fixtures.test.js +++ b/test/opentelemetry-metrics/fixtures.test.js @@ -32,11 +32,14 @@ const { formatForTComment, } = require('../_utils'); -if (!semver.satisfies(process.version, '>=14')) { +const OTEL_SDK_ENGINES_NODE = '^18.19.0 || >=20.6.0'; +if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { console.log( - `# SKIP @opentelemetry/sdk-metrics only supports node >=14 (node ${process.version})`, + '# SKIP: skipping %s, OTel SDK does not support node %s', + __filename, + process.version, ); - process.exit(); + process.exit(0); } const isUndiciIncompat = require('../_is_undici_incompat')(); From 8e9790e4b05b0bd2896885cbe3e8aa92ed27120b Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 14:33:47 -0700 Subject: [PATCH 03/11] update changelog entry --- docs/release-notes/index.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index c18b8cfcb0..0ab70767c3 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -26,6 +26,27 @@ To check for security updates, go to [Security announcements for the Elastic sta % % ### Fixes [next-fixes] +## Next [next] +**Release date:** Month day, year + +### Features and enhancements [next-features-enhancements] + + +### Fixes [next-fixes] + +* Update [*OpenTelemetry bridge*](/reference/opentelemetry-bridge.md) to use OTel SDK 2.x. [#5126](https://github.com/elastic/apm-agent-nodejs/pull/5126)) + * **Possibly breaking for users of the OpenTelemetry bridge.** + This upgrades internal OpenTelemetry SDK dependencies from 1.x to 2.x, + which implies a [minimum supported Node.js version range of `^18.19.0 || >=20.6.0`](https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/upgrade-to-2.x.md#-nodejs-supported-versions). + The (experimental) Elastic APM OpenTelemetry bridge will no longer work + for Node.js 14 and 16 users. + * The [`opentelemetryBridgeEnabled`](/reference/configuration.md#opentelemetry-bridge-enabled) + config setting **must now be set to true for OpenTelemetry metrics + integration**. Before this change, the metrics integration would accidentally + be enabled regardless of the opentelemetryBridgeEnabled setting. + * The `@opentelemetry/*` dependency updates include an upstream fix for [CVE-2026-54285](https://github.com/open-telemetry/opentelemetry-js/security/advisories/GHSA-8988-4f7v-96qf), though that vulnerability did not impact this package's usage. + * Thanks to [JoshElias](https://github.com/JoshElias) for an [earlier PR on which this PR was based](https://github.com/elastic/apm-agent-nodejs/pull/4933). + ## 4.17.0 [4-17-0] **Release date:** Jun 30, 2026 @@ -48,8 +69,6 @@ To check for security updates, go to [Security announcements for the Elastic sta * Remove the "Get started with Restify" document and stop testing instrumentation against Restify. Restify is no longer getting maintenance. The instrumentation support for restify is unchanged, so this is not a breaking change. ([#4892](https://github.com/elastic/apm-agent-nodejs/pull/4892)) -* Upgrade `@opentelemetry/core` and `@opentelemetry/sdk-metrics` to 2.x. This includes the upstream fix for [CVE-2026-54285](https://github.com/open-telemetry/opentelemetry-js/security/advisories/GHSA-8988-4f7v-96qf) in `@opentelemetry/core`. The OTel Metrics SDK integration was updated for the 2.x API changes (aggregation configuration objects and `MeterProvider` reader registration via the constructor). ([#5126](https://github.com/elastic/apm-agent-nodejs/pull/5126)) - ## 4.15.0 [4-15-0] **Release date:** Oct 16, 2025 From c92e8ec97c9bf633fa463d0049e4d4cc357d38fa Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 14:34:31 -0700 Subject: [PATCH 04/11] lint:fix --- test/opentelemetry-bridge/interface-ContextManager.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/opentelemetry-bridge/interface-ContextManager.test.js b/test/opentelemetry-bridge/interface-ContextManager.test.js index 79328be44c..1261fa3fc4 100644 --- a/test/opentelemetry-bridge/interface-ContextManager.test.js +++ b/test/opentelemetry-bridge/interface-ContextManager.test.js @@ -19,7 +19,6 @@ if (!semver.satisfies(process.version, OTEL_SDK_ENGINES_NODE)) { process.exit(0); } - require('../..').start({ opentelemetryBridgeEnabled: true, // Make the agent quiet. From 35f3d3e4559e877fddebf52f0e1c3f3790d51594 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 15:13:11 -0700 Subject: [PATCH 05/11] ensure ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED set for otel metrics tests; refactor to avoid circular import and interaction with hooking of sdk-metrics package --- lib/agent.js | 3 +-- lib/opentelemetry-metrics/index.js | 20 +++++++++++++------- test/opentelemetry-metrics/fixtures.test.js | 3 ++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/agent.js b/lib/agent.js index 2006e04449..95d2b180a4 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -32,6 +32,7 @@ const { frameCacheStats, initStackTraceCollection } = require('./stacktraces'); const Span = require('./instrumentation/span'); const Transaction = require('./instrumentation/transaction'); const { createApmClient } = require('./apm-client/apm-client'); +const { createOTelMeterProvider } = require('./opentelemetry-metrics'); const IncomingMessage = http.IncomingMessage; const ServerResponse = http.ServerResponse; @@ -402,8 +403,6 @@ Agent.prototype._getOrCreateOTelMeterProvider = function () { } this.logger.trace('create Elastic APM MeterProvider for @opentelemetry/api'); - // Only import anything `@opentelemetry/*` if `opentelemetryBridgeEnabled`. - const { createOTelMeterProvider } = require('./opentelemetry-metrics'); this._otelMeterProvider = createOTelMeterProvider(this); return this._otelMeterProvider; }; diff --git a/lib/opentelemetry-metrics/index.js b/lib/opentelemetry-metrics/index.js index 7c3ec24681..985960cd95 100644 --- a/lib/opentelemetry-metrics/index.js +++ b/lib/opentelemetry-metrics/index.js @@ -6,14 +6,15 @@ const assert = require('assert'); -const { - MeterProvider, - PeriodicExportingMetricReader, -} = require('@opentelemetry/sdk-metrics'); - -const ElasticApmMetricExporter = require('./ElasticApmMetricExporter'); - function createOTelMetricReader(agent) { + // Lazy-import `@opentelemetry/sdk-metrics` to avoid interaction with + // require-in-the-middle hooking of it. Also to avoid any + // import of `@opentelemetry/*` unless `opentelemetryBridgeEnabled`. + const { + PeriodicExportingMetricReader, + } = require('@opentelemetry/sdk-metrics'); + const ElasticApmMetricExporter = require('./ElasticApmMetricExporter'); + const metricsInterval = agent._conf.metricsInterval; assert( metricsInterval > 0, @@ -27,6 +28,11 @@ function createOTelMetricReader(agent) { } function createOTelMeterProvider(agent) { + // Lazy-import `@opentelemetry/sdk-metrics` to avoid interaction with + // require-in-the-middle hooking of it. Also to avoid any + // import of `@opentelemetry/*` unless `opentelemetryBridgeEnabled`. + const { MeterProvider } = require('@opentelemetry/sdk-metrics'); + const meterProvider = new MeterProvider({ readers: [createOTelMetricReader(agent)], }); diff --git a/test/opentelemetry-metrics/fixtures.test.js b/test/opentelemetry-metrics/fixtures.test.js index e23d1dcd52..e867954907 100644 --- a/test/opentelemetry-metrics/fixtures.test.js +++ b/test/opentelemetry-metrics/fixtures.test.js @@ -398,12 +398,13 @@ cases.forEach((c) => { server.start(function (serverUrl) { const proc = execFile( process.execPath, - ['-r', '../../start.js', scriptPath], + ['-r', '../../start.js', '--trace-warnings', scriptPath], { cwd: __dirname, timeout: 10000, // guard on hang, 3s is sometimes too short for CI env: Object.assign({}, process.env, c.env, { ELASTIC_APM_SERVER_URL: serverUrl, + ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED: 'true', ELASTIC_APM_METRICS_INTERVAL: '500ms', ELASTIC_APM_API_REQUEST_TIME: '500ms', ELASTIC_APM_CENTRAL_CONFIG: 'false', From d3713c3121e56c4420910ce952648de30f97ff4b Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 15:25:23 -0700 Subject: [PATCH 06/11] fix-up (httpstat.us is not responding) and modernize the bridge example --- examples/opentelemetry-bridge/otel-sdk.js | 5 +++-- examples/opentelemetry-bridge/package.json | 10 +++++----- examples/opentelemetry-bridge/trace-https-request.js | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/opentelemetry-bridge/otel-sdk.js b/examples/opentelemetry-bridge/otel-sdk.js index 252895d938..2f86c8325c 100644 --- a/examples/opentelemetry-bridge/otel-sdk.js +++ b/examples/opentelemetry-bridge/otel-sdk.js @@ -33,8 +33,9 @@ const { const { HttpInstrumentation } = require('@opentelemetry/instrumentation-http'); module.exports = (() => { - const provider = new NodeTracerProvider(); - provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + const provider = new NodeTracerProvider({ + spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())], + }); provider.register(); registerInstrumentations({ instrumentations: [new HttpInstrumentation()], diff --git a/examples/opentelemetry-bridge/package.json b/examples/opentelemetry-bridge/package.json index bb575283be..e10f76a791 100644 --- a/examples/opentelemetry-bridge/package.json +++ b/examples/opentelemetry-bridge/package.json @@ -8,11 +8,11 @@ }, "dependencies": { "@opentelemetry/api": "^1.4.0", - "@opentelemetry/core": "^1.11.0", - "@opentelemetry/instrumentation": ">=0.41.0 <2", - "@opentelemetry/instrumentation-http": ">=0.41.0 <2", - "@opentelemetry/sdk-trace-base": "^1.11.0", - "@opentelemetry/sdk-trace-node": "^1.11.0", + "@opentelemetry/core": "^2.9.0", + "@opentelemetry/instrumentation": ">=0.220.0 <0.300.0", + "@opentelemetry/instrumentation-http": ">=0.220.0 <0.300.0", + "@opentelemetry/sdk-trace-base": "^2.9.0", + "@opentelemetry/sdk-trace-node": "^2.9.0", "elastic-apm-node": "file:../.." } } diff --git a/examples/opentelemetry-bridge/trace-https-request.js b/examples/opentelemetry-bridge/trace-https-request.js index f0b42ddafc..0cb53be522 100644 --- a/examples/opentelemetry-bridge/trace-https-request.js +++ b/examples/opentelemetry-bridge/trace-https-request.js @@ -19,12 +19,12 @@ const otel = require('@opentelemetry/api'); const tracer = otel.trace.getTracer('trace-https-request'); tracer.startActiveSpan('makeRequest', (span) => { - https.get('https://httpstat.us/200', (response) => { + https.get('https://www.google.com/', (response) => { console.log('STATUS:', response.statusCode); const body = []; response.on('data', (chunk) => body.push(chunk)); response.on('end', () => { - console.log('BODY:', body.toString()); + console.log('BODY: "%s..."', body.toString().slice(0, 80)); span.end(); }); }); From f3d2f703dc57fe74aceb32bf658b6f38b116967f Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 15:36:16 -0700 Subject: [PATCH 07/11] avoid a spurious sometimes-race in this test case --- test/opentelemetry-metrics/fixtures.test.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/opentelemetry-metrics/fixtures.test.js b/test/opentelemetry-metrics/fixtures.test.js index e867954907..6c3cb15063 100644 --- a/test/opentelemetry-metrics/fixtures.test.js +++ b/test/opentelemetry-metrics/fixtures.test.js @@ -269,10 +269,9 @@ const cases = [ const warnLines = stdout .split('\n') .filter((ln) => ~ln.indexOf('dropping array-valued metric attribute')); - t.equal( - warnLines.length, - 1, - 'exactly one log.warn about dropping the array-valued metric attribute', + t.ok( + warnLines.length >= 1, + 'at least one log.warn about dropping the array-valued metric attribute', ); t.ok( warnLines[0].indexOf('test_counter_attrs'), @@ -398,7 +397,7 @@ cases.forEach((c) => { server.start(function (serverUrl) { const proc = execFile( process.execPath, - ['-r', '../../start.js', '--trace-warnings', scriptPath], + ['-r', '../../start.js', scriptPath], { cwd: __dirname, timeout: 10000, // guard on hang, 3s is sometimes too short for CI From daa636fda7663f43dc95f5f2a1771d6f01d10a45 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 15:40:46 -0700 Subject: [PATCH 08/11] update the otel metrics examples --- examples/opentelemetry-metrics/README.md | 4 +++- examples/opentelemetry-metrics/package.json | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/opentelemetry-metrics/README.md b/examples/opentelemetry-metrics/README.md index 787ef4bb24..af47384ff8 100644 --- a/examples/opentelemetry-metrics/README.md +++ b/examples/opentelemetry-metrics/README.md @@ -19,6 +19,7 @@ This directory shows how you can use the OpenTelemetry Metrics API with the Elas ``` export ELASTIC_APM_SERVER_URL=https://... # your Elastic APM Server URL export ELASTIC_APM_SECRET_TOKEN=... + export ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true node -r elastic-apm-node/start.js otel-metrics-hello-world.js ``` @@ -39,6 +40,7 @@ This directory shows how you can use the OpenTelemetry Metrics API with the Elas ``` export ELASTIC_APM_SERVER_URL=https://... # your Elastic APM Server URL export ELASTIC_APM_SECRET_TOKEN=... + export ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true node -r elastic-apm-node/start.js use-otel-metrics-sdk.js ``` @@ -48,7 +50,7 @@ This directory shows how you can use the OpenTelemetry Metrics API with the Elas while true; do curl http://127.0.0.1:3000/; sleep 1; done ``` - **The a percentile of the `latency` custom histogram metric can now be visualized in Kibana.** + **Then a percentile of the `latency` custom histogram metric can now be visualized in Kibana.** See https://www.elastic.co/guide/en/apm/agent/nodejs/current/opentelemetry-bridge.html diff --git a/examples/opentelemetry-metrics/package.json b/examples/opentelemetry-metrics/package.json index d2fb12ba84..7f8ca21563 100644 --- a/examples/opentelemetry-metrics/package.json +++ b/examples/opentelemetry-metrics/package.json @@ -4,8 +4,8 @@ "private": true, "dependencies": { "@opentelemetry/api": "^1.4.1", - "@opentelemetry/exporter-prometheus": ">=0.200.0 <1", - "@opentelemetry/sdk-metrics": "^2.8.0", + "@opentelemetry/exporter-prometheus": ">=0.200.0 <0.300.0", + "@opentelemetry/sdk-metrics": "^2.9.0", "elastic-apm-node": "file:../.." } } From 6e27e896de168dc7aaad04a224f4527d184b3d6c Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 15:48:54 -0700 Subject: [PATCH 09/11] add a brief note about the OTel-native SDK option; this could be a separate PR, but I'm piggy-backing --- docs/reference/opentelemetry-bridge.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/reference/opentelemetry-bridge.md b/docs/reference/opentelemetry-bridge.md index d41138b868..1cce75ac62 100644 --- a/docs/reference/opentelemetry-bridge.md +++ b/docs/reference/opentelemetry-bridge.md @@ -15,6 +15,9 @@ applies_to: Integration with the OpenTelemetry Tracing API was added as experimental in v3.34.0. Integration with the OpenTelemetry Metrics API was added as experimental in v3.45.0. :::: +::::{note} +For OpenTelemetry-based observability, consider using [Elastic's OTel-native Node.js SDK (`@elastic/opentelemetry-node`)](https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/node). Using an OTel-native SDK provides the full functionality of the OpenTelemetry ecosystem, without the limitations in this experimental bridge. +:::: The Elastic APM OpenTelemetry bridge allows one to use the vendor-neutral [OpenTelemetry API](https://opentelemetry.io/docs/instrumentation/js/) ([`@opentelemetry/api`](https://www.npmjs.com/package/@opentelemetry/api)) in your code, and have the Elastic Node.js APM agent handle those API calls. This allows one to use the Elastic APM agent for tracing and metrics without any vendor lock-in to the APM agent’s own [public API](/reference/api.md) when adding manual tracing or custom metrics. From fa9c45d235451b61a5b89dd326176f2ca9d59ecf Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Mon, 6 Jul 2026 15:55:41 -0700 Subject: [PATCH 10/11] update bridge docs to ensure ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true is used for metrics integration --- docs/reference/opentelemetry-bridge.md | 60 ++++++++++--------- .../use-otel-metrics-sdk.js | 1 + 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/docs/reference/opentelemetry-bridge.md b/docs/reference/opentelemetry-bridge.md index 1cce75ac62..fedaab3c85 100644 --- a/docs/reference/opentelemetry-bridge.md +++ b/docs/reference/opentelemetry-bridge.md @@ -35,14 +35,13 @@ npm install --save elastic-apm-node @opentelemetry/api ```bash export ELASTIC_APM_SERVER_URL='' export ELASTIC_APM_SECRET_TOKEN='' <1> -export ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true <2> -export NODE_OPTIONS='-r elastic-apm-node/start.js' <3> +export ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true +export NODE_OPTIONS='-r elastic-apm-node/start.js' <2> node my-app.js ``` 1. Or use ELASTIC_APM_API_KEY= -2. Future versions may drop this config var and enable usage of the tracing API by default. -3. Tell node to preload and start the APM agent. +2. Tell node to preload and start the APM agent. Or, alternatively, you can configure and start the APM agent at the top of your application code: @@ -62,21 +61,21 @@ See [the full APM agent configuration reference](/reference/configuration.md) fo ③ Finally, you can use the [OpenTelemetry API](https://open-telemetry.github.io/opentelemetry-js/modules/_opentelemetry_api.html) for any manual tracing in your code. For example, the following script uses [Tracer#startActiveSpan()](https://open-telemetry.github.io/opentelemetry-js/interfaces/_opentelemetry_api._opentelemetry_api.Tracer.html#startactivespan) to trace an outgoing HTTPS request: ```js -const https = require('https') -const otel = require('@opentelemetry/api') -const tracer = otel.trace.getTracer('trace-https-request') - -tracer.startActiveSpan('makeRequest', span => { - https.get('https://httpstat.us/200', (response) => { - console.log('STATUS:', response.statusCode) - const body = [] - response.on('data', (chunk) => body.push(chunk)) +const https = require('https'); +const otel = require('@opentelemetry/api'); +const tracer = otel.trace.getTracer('trace-https-request'); + +tracer.startActiveSpan('makeRequest', (span) => { + https.get('https://www.google.com/', (response) => { + console.log('STATUS:', response.statusCode); + const body = []; + response.on('data', (chunk) => body.push(chunk)); response.on('end', () => { - console.log('BODY:', body.toString()) - span.end() - }) - }) -}) + console.log('BODY: "%s..."', body.toString().slice(0, 80)); + span.end(); + }); + }); +}); ``` The APM agent source code repository includes [some examples using the OpenTelemetry tracing bridge](https://github.com/elastic/apm-agent-nodejs/tree/main/examples/opentelemetry-bridge). @@ -95,6 +94,7 @@ npm install --save elastic-apm-node @opentelemetry/api ```bash export ELASTIC_APM_SERVER_URL='' export ELASTIC_APM_SECRET_TOKEN='' # or ELASTIC_APM_API_KEY=... +export ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true export NODE_OPTIONS='-r elastic-apm-node/start.js' # Tell node to preload and start the APM agent node my-app.js ``` @@ -103,22 +103,24 @@ node my-app.js ```js // otel-metrics-hello-world.js <1> -const { createServer } = require('http') -const otel = require('@opentelemetry/api') +const { createServer } = require('http'); +const otel = require('@opentelemetry/api'); -const meter = otel.metrics.getMeter('my-meter') -const numReqs = meter.createCounter('num_requests', { description: 'number of HTTP requests' }) +const meter = otel.metrics.getMeter('my-meter'); +const numReqs = meter.createCounter('num_requests', { + description: 'number of HTTP requests', +}); const server = createServer((req, res) => { - numReqs.add(1) - req.resume() + numReqs.add(1); + req.resume(); req.on('end', () => { - res.end('pong\n') - }) -}) + res.end('pong\n'); + }); +}); server.listen(3000, () => { - console.log('listening at http://127.0.0.1:3000/') -}) + console.log('listening at http://127.0.0.1:3000/'); +}); ``` 1. The full example is [here](https://github.com/elastic/apm-agent-nodejs/blob/main/examples/opentelemetry-metrics/otel-metrics-hello-world.js). diff --git a/examples/opentelemetry-metrics/use-otel-metrics-sdk.js b/examples/opentelemetry-metrics/use-otel-metrics-sdk.js index e3bbcb94a6..bb1ded0102 100644 --- a/examples/opentelemetry-metrics/use-otel-metrics-sdk.js +++ b/examples/opentelemetry-metrics/use-otel-metrics-sdk.js @@ -19,6 +19,7 @@ // periodically be exported to the configured Elastic APM server: // export ELASTIC_APM_SERVER_URL='' // export ELASTIC_APM_SECRET_TOKEN='' +// export ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true // node -r elastic-apm-node/start.js use-otel-metrics-sdk.js 'use strict'; From 6931c6105155de2d425cf9f640160595b9553cdd Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Tue, 7 Jul 2026 08:11:36 -0700 Subject: [PATCH 11/11] remove debugging tweak in tests --- test/opentelemetry-bridge/fixtures.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/opentelemetry-bridge/fixtures.test.js b/test/opentelemetry-bridge/fixtures.test.js index c7f21998d7..0639d0f8ab 100644 --- a/test/opentelemetry-bridge/fixtures.test.js +++ b/test/opentelemetry-bridge/fixtures.test.js @@ -744,9 +744,7 @@ const cases = [ }, ]; -// XXX -// cases.forEach((c) => { -[cases[0]].forEach((c) => { +cases.forEach((c) => { tape.test( `test/opentelemetry-bridge/fixtures/${c.script}`, c.testOpts || {},