Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 34 additions & 29 deletions docs/reference/opentelemetry-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -32,14 +35,13 @@ npm install --save elastic-apm-node @opentelemetry/api
```bash
export ELASTIC_APM_SERVER_URL='<url of your APM server>'
export ELASTIC_APM_SECRET_TOKEN='<secret token for your APM server>' <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:

Expand All @@ -59,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).
Expand All @@ -92,6 +94,7 @@ npm install --save elastic-apm-node @opentelemetry/api
```bash
export ELASTIC_APM_SERVER_URL='<url of your APM server>'
export ELASTIC_APM_SECRET_TOKEN='<secret token for your APM server>' # 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
```
Expand All @@ -100,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).
Expand Down
21 changes: 21 additions & 0 deletions docs/release-notes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions examples/opentelemetry-bridge/otel-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()],
Expand Down
10 changes: 5 additions & 5 deletions examples/opentelemetry-bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:../.."
}
}
4 changes: 2 additions & 2 deletions examples/opentelemetry-bridge/trace-https-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
Expand Down
4 changes: 3 additions & 1 deletion examples/opentelemetry-metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand All @@ -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
```

Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions examples/opentelemetry-metrics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 <0.300.0",
"@opentelemetry/sdk-metrics": "^2.9.0",
"elastic-apm-node": "file:../.."
}
}
25 changes: 14 additions & 11 deletions examples/opentelemetry-metrics/use-otel-metrics-sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// periodically be exported to the configured Elastic APM server:
// export ELASTIC_APM_SERVER_URL='<url of your APM server>'
// export ELASTIC_APM_SECRET_TOKEN='<secret token for your APM server>'
// export ELASTIC_APM_OPENTELEMETRY_BRIDGE_ENABLED=true
// node -r elastic-apm-node/start.js use-otel-metrics-sdk.js

'use strict';
Expand All @@ -29,26 +30,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');

Expand Down
9 changes: 3 additions & 6 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@ 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 { createOTelMeterProvider } = require('./opentelemetry-metrics');

const IncomingMessage = http.IncomingMessage;
const ServerResponse = http.ServerResponse;
Expand Down Expand Up @@ -398,10 +395,10 @@ Agent.prototype._getOrCreateOTelMeterProvider = function () {
return this._otelMeterProvider;
}

if (!this._isMetricsEnabled()) {
if (!this._conf.opentelemetryBridgeEnabled) {
return null;
}
if (!isOTelMetricsFeatSupported) {
if (!this._isMetricsEnabled()) {
return null;
}

Expand Down
21 changes: 21 additions & 0 deletions lib/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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);
}

Expand Down
13 changes: 3 additions & 10 deletions lib/instrumentation/modules/@opentelemetry/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@

const semver = require('semver');

const {
isOTelMetricsFeatSupported,
} = require('../../../opentelemetry-metrics');
const shimmer = require('../../shimmer');

/**
Expand All @@ -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;
}
Expand All @@ -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);
Expand Down
Loading
Loading