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
8 changes: 2 additions & 6 deletions .github/workflows/test-edot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,9 @@ jobs:
- '22'
- '22.0'
- '20'
- '20.0'
- '20.6.0'
- '18'
- '18.0'
- '16'
- '16.0'
- '14'
- '14.18.0'
- '18.19.0'

runs-on: ubuntu-24.04

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@
* under the License.
*/

export {
load,
resolve,
getFormat,
getSource,
} from '@opentelemetry/instrumentation/hook.mjs';
import {OpenAI} from 'openai';

let chatModel = process.env.CHAT_MODEL ?? 'gpt-4o-mini';

const client = new OpenAI();
const completion = await client.chat.completions.create({
model: chatModel,
messages: [
{
role: 'user',
content: 'Answer in up to 3 words: Which ocean contains Bouvet Island?',
},
],
});
console.log(completion.choices[0].message.content);
3 changes: 2 additions & 1 deletion examples/openai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
},
"scripts": {
"chat": "node --env-file .env --require @elastic/opentelemetry-node chat.js",
"embeddings": "node --env-file .env --require @elastic/opentelemetry-node embeddings.js"
"embeddings": "node --env-file .env --require @elastic/opentelemetry-node embeddings.js",
"chat:esm": "node --env-file .env --import @elastic/opentelemetry-node chat.mjs"
},
"dependencies": {
"@elastic/opentelemetry-node": "*",
Expand Down
27 changes: 27 additions & 0 deletions packages/opentelemetry-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,39 @@

## Unreleased

- BREAKING CHANGE: Bump min-supported node to `^18.19.0 || >=20.6.0`.
This raises the minimum-supported Node.js to the same as coming releases of OpenTelemetry JS.
This base version range ensures that `module.register()` is available for improved ES module
(ESM) auto-instrumentation.
This drops support for Node.js 14 and 16.

- feat: Improve ES module (ESM) instrumentation.

As part of this change, using `--require @elastic/opentelemetry-node` will
*no longer* setup a module hook for instrumenting ES modules; only using
`--import @elastic/opentelemetry-node` will do so. **The recommendation now
is to use `--import @elastic/opentelemetry-node` to start EDOT Node.js.**
Using `--require ...` is still fine when you know your application is only
using CommonJS modules.

Implementation details: The underlying module hook mechanism for ESM has been
changed to only hook modules that will potentially be patched by configured
instrumentations. This allows some instrumentations to work that could not
before due to some ESM files not being hookable (at least via the imperfect
mechanism for hooking ES modules). One such example is
`@elastic/instrumentation-openai`. See
<https://github.com/nodejs/import-in-the-middle/pull/146> for internal
details.

- feat: Add `@opentelemetry/instrumentation-mysql` to the default set
of instrumentations. See <https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-mysql#readme>

- feat: Add `@opentelemetry/instrumentation-mysql2` to the default set
of instrumentations. See <https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-mysql2#readme>

- feat: Add `@opentelemetry/instrumentation-cassandra-driver` to the default set
of instrumentations. See <https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/plugins/node/opentelemetry-instrumentation-cassandra#readme>

- test: Test that the native instrumentation in `@elastic/elasticsearch@8.15.0` and later works.


Expand Down
2 changes: 1 addition & 1 deletion packages/opentelemetry-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ npm install --save @elastic/opentelemetry-node
## Run

```sh
node -r @elastic/opentelemetry-node my-service.js
node --import @elastic/opentelemetry-node my-service.js
```

## Read the docs
Expand Down
6 changes: 3 additions & 3 deletions packages/opentelemetry-node/docs/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ for example, before `express` or `http` are loaded.

<!-- ✅ Step-by-step instructions -->
The recommended way to get the
distro started is by using the `-r, --require` Node.js
[CLI option](https://nodejs.org/api/cli.html#-r---require-module):
distro started is by using the `--import` Node.js
[CLI option](https://nodejs.org/api/cli.html#--importmodule):

```sh
node --require @elastic/opentelemetry-node my-service.js
node --import @elastic/opentelemetry-node my-service.js
```

EDOT Node.js will automatically instrument popular modules (listed in [Supported technologies](./supported-technologies.md))
Expand Down
4 changes: 2 additions & 2 deletions packages/opentelemetry-node/docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ variable `ELASTIC_OTEL_METRICS_DISABLED` to the string `true`.
export OTEL_EXPORTER_OTLP_ENDPOINT="${ELASTIC_APM_SERVER_URL}"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer ${ELASTIC_APM_SECRET_TOKEN}"
export ELASTIC_OTEL_METRICS_DISABLED=true
node -r @elastic/opentelemetry-node/start.js my-app.js
node --import @elastic/opentelemetry-node/start.js my-app.js
```

## Advanced configuration
Expand Down Expand Up @@ -57,4 +57,4 @@ issues when doing an overview of the instrumented service. These are:


If your service is instrumented by EDOT Node.js, or by custom instrumentation that includes the packages mentioned above, Kibana will
display them as part of the [service metrics](https://www.elastic.co/guide/en/observability/current/apm-metrics.html) in its UI.
display them as part of the [service metrics](https://www.elastic.co/guide/en/observability/current/apm-metrics.html) in its UI.
10 changes: 5 additions & 5 deletions packages/opentelemetry-node/docs/supported-technologies.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ Elastic Stack, so it is strongly recommended to be using a recent 8.x version.

## Node.js versions

EDOT Node.js supports Node.js v14.18.0 and later.
EDOT Node.js supports Node.js 18.19.0, 20.6.0, or later.
This follows from the [OpenTelemetry JS supported runtimes](https://github.com/open-telemetry/opentelemetry-js#supported-runtimes).

<!--
Dev Notes on supported Node.js versions:
- v14.18.0 or later is required for `node:`-prefixed core module loading, as
used by gaxios@6.7.0, a transitive dep of `@opentelemetry/resource-detector-gcp`.
- `^18.19.0 || >=20.6.0` is required for `module.register()` support for ESM
instrumentation.
-->

## TypeScript versions
Comment thread
trentm marked this conversation as resolved.
Expand Down Expand Up @@ -83,7 +83,7 @@ node --import @elastic/opentelemetry-node my-app.js

## ECMAScript Modules (ESM)

This Distro includes **limited and experimental** support for instrumenting [ECMAScript module imports](https://nodejs.org/api/esm.html#modules-ecmascript-modules), i.e. modules that are loaded via `import ...` statements and `import('...')` (dynamic import).
This Distro includes **limited and experimental** support for instrumenting [ECMAScript module (ESM) imports](https://nodejs.org/api/esm.html#modules-ecmascript-modules), i.e. modules that are loaded via `import ...` statements and `import('...')` (dynamic import). To enable ESM instrumentation, use `node --import @elastic/opentelemetry-node ...` to start the SDK. (Using `node --require @elastic/opentelemetry-node ...` will not enable ESM instrumentation. It is intended to signal that only CommonJS module usage should be instrumented.)

<!--
TODO: add this to the above paragraph once we have an esm.md doc:
Expand All @@ -92,5 +92,5 @@ See the [ECMAScript module support](./esm.md) document for details.

Limitations:

* For Node.js `>=20.6.0 || >=18.19.0`, support for hooking `import`s is automatically enabled. For earlier versions of Node.js, you must manually enable the `import`-hook via the `--experimental-loader=@elastic/opentelemetry-node/hook.mjs` option, e.g.: `node --experimental-loader=@elastic/opentelemetry-node/hook.mjs --require=@elastic/opentelemetry-node app.js`.
* ESM instrumentation is only support for Node.js versions `^18.19.0 || >=20.6.0`. These are the versions that include `module.register()` support. Using the older `node --experimental-loader=...` option is not supported.
* Currently only a subset of instrumentations support ESM: `express`, `ioredis`, `koa`, `pg`, `pino`. See [this OpenTelemetry JS tracking issue](https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1942) for progress.
58 changes: 17 additions & 41 deletions packages/opentelemetry-node/import.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,52 +20,28 @@
// Register ESM hook and start the SDK.
// This is called for `--import @elastic/opentelemetry-node`.

import * as module from 'node:module';
import {register} from 'node:module';
import {isMainThread} from 'node:worker_threads';

import {log} from './lib/logging.js';
// TODO: Update @opentelemetry/instrumentation exports to use it rather than IITM directly, if can.
import {createAddHookMessageChannel} from 'import-in-the-middle';

/**
* Return true iff it looks like the `@elastic/opentelemetry-node/hook.mjs`
* was loaded via node's `--experimental-loader` option.
*
* Dev Note: keep this in sync with "require.js".
*/
function haveHookFromExperimentalLoader() {
const USED_LOADER_OPT =
/--(experimental-)?loader(\s+|=)@elastic\/opentelemetry-node\/hook.mjs/;
for (let i = 0; i < process.execArgv.length; i++) {
const arg = process.execArgv[i];
const nextArg = process.execArgv[i + 1];
if (
(arg === '--loader' || arg === '--experimental-loader') &&
nextArg === '@elastic/opentelemetry-node/hook.mjs'
) {
log.trace('bootstrap-import: --loader hook args used');
return true;
} else if (USED_LOADER_OPT.test(arg)) {
log.trace('bootstrap-import: --loader hook arg used');
return true;
}
}
if (
process.env.NODE_OPTIONS &&
USED_LOADER_OPT.test(process.env.NODE_OPTIONS)
) {
log.trace('bootstrap-import: --loader arg used in NODE_OPTIONS');
return true;
}
return false;
}
import {log} from './lib/logging.js';

if (isMainThread) {
if (
typeof module.register === 'function' &&
!haveHookFromExperimentalLoader()
) {
log.trace('bootstrap-import: registering module hook');
module.register('./hook.mjs', import.meta.url);
}
// Note: If there are *multiple* installations of import-in-the-middle, then
// only those instrumentations using this same one will be hooked.
const {registerOptions, waitForAllMessagesAcknowledged} =
createAddHookMessageChannel();

log.trace('import.mjs: registering module hook');
register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions);

await import('./lib/start.js');

// Ensure that the loader has acknowledged all the modules before we allow
// execution to continue.
await waitForAllMessagesAcknowledged();
} else {
log.trace('import.mjs: skipping EDOT Node.js bootstrap on non-main thread');
}
17 changes: 9 additions & 8 deletions packages/opentelemetry-node/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions packages/opentelemetry-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
],
"author": "Elastic Observability <https://www.elastic.co/observability>",
"engines": {
"node": ">=14.18.0"
"node": "^18.19.0 || >=20.6.0"
},
"files": [
"lib",
Expand All @@ -33,7 +33,6 @@
"LICENSE",
"NOTICE.md",
"README.md",
"hook.mjs",
"import.mjs",
"package.json",
"require.js"
Expand All @@ -45,7 +44,7 @@
"lint:eslint": "eslint --ext=js,mjs,cjs . # requires node >=16.0.0",
"lint:types": "rm -rf build/lint-types && tsc --outDir build/lint-types && diff -ur types build/lint-types",
"lint:fix": "eslint --ext=js,mjs,cjs --fix . # requires node >=16.0.0",
"lint:deps": "dependency-check require.js import.mjs hook.mjs 'lib/**/*.js' 'test/**/*.js' '!test/fixtures/a-ts-proj' -e mjs:../../scripts/parse-mjs-source -i @types/tape -i dotenv -i @opentelemetry/winston-transport -i @opentelemetry/exporter-logs-* -i @opentelemetry/exporter-metrics-*",
"lint:deps": "dependency-check require.js import.mjs 'lib/**/*.js' 'test/**/*.js' '!test/fixtures/a-ts-proj' -e mjs:../../scripts/parse-mjs-source -i @types/tape -i dotenv -i @opentelemetry/winston-transport -i @opentelemetry/exporter-logs-* -i @opentelemetry/exporter-metrics-*",
"lint:license-files": "../../scripts/gen-notice.sh --lint . # requires node >=16",
"lint:changelog": "../../scripts/extract-release-notes.sh .",
"test": "NODE_OPTIONS='-r dotenv/config' DOTENV_CONFIG_PATH=./test/test-services.env tape test/**/*.test.js",
Expand All @@ -63,7 +62,6 @@
"types": "./types/sdk.d.ts",
"default": "./lib/sdk.js"
},
"./hook.mjs": "./hook.mjs",
"./package.json": "./package.json"
},
"dependencies": {
Expand All @@ -77,7 +75,6 @@
"@opentelemetry/exporter-metrics-otlp-http": "^0.57.1",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.57.1",
"@opentelemetry/host-metrics": "^0.35.0",
"@opentelemetry/instrumentation": "^0.57.1",
"@opentelemetry/instrumentation-aws-sdk": "^0.49.0",
"@opentelemetry/instrumentation-bunyan": "^0.45.0",
"@opentelemetry/instrumentation-cassandra-driver": "^0.45.0",
Expand Down Expand Up @@ -124,6 +121,7 @@
"@opentelemetry/sdk-logs": "^0.57.1",
"@opentelemetry/sdk-node": "^0.57.1",
"@opentelemetry/winston-transport": "^0.10.0",
"import-in-the-middle": "^1.12.0",
"safe-stable-stringify": "^2.4.3"
},
"devDependencies": {
Expand Down
52 changes: 9 additions & 43 deletions packages/opentelemetry-node/require.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,19 @@
* under the License.
*/

// Register ESM hook and start the SDK.
// Start the SDK for CommonJS usage.
// This is called for `--require @elastic/opentelemetry-node`.
//
// Note: This does *not* register an import hook for ESM instrumentation. Use
// `--import @elastic/opentelemetry-node` for that.

const register = require('module').register;
const {pathToFileURL} = require('url');
const {isMainThread} = require('worker_threads');

const {log} = require('./lib/logging');

/**
* Return true iff it looks like the `@elastic/opentelemetry-node/hook.mjs`
* was loaded via node's `--experimental-loader` option.
*
* Dev Note: keep this in sync with "import.mjs".
*/
function haveHookFromExperimentalLoader() {
const USED_LOADER_OPT =
/--(experimental-)?loader(\s+|=)@elastic\/opentelemetry-node\/hook.mjs/;
for (let i = 0; i < process.execArgv.length; i++) {
const arg = process.execArgv[i];
const nextArg = process.execArgv[i + 1];
if (
(arg === '--loader' || arg === '--experimental-loader') &&
nextArg === '@elastic/opentelemetry-node/hook.mjs'
) {
log.trace('bootstrap-require: --loader hook args used');
return true;
} else if (USED_LOADER_OPT.test(arg)) {
log.trace('bootstrap-require: --loader hook arg used');
return true;
}
}
if (
process.env.NODE_OPTIONS &&
USED_LOADER_OPT.test(process.env.NODE_OPTIONS)
) {
log.trace('bootstrap-require: --loader arg used in NODE_OPTIONS');
return true;
}
return false;
}

if (isMainThread) {
if (typeof register === 'function' && !haveHookFromExperimentalLoader()) {
log.trace('bootstrap-require: registering module hook');
register('./hook.mjs', pathToFileURL(__filename).toString());
}

require('./lib/start.js');
} else {
const {log} = require('./lib/logging');
log.trace(
'require.mjs: skipping EDOT Node.js bootstrap on non-main thread'
);
}
Loading