Skip to content

Commit f66cfc0

Browse files
committed
feat(opentelemetry-node): improved ESM instrumentation support
This updates to usage of IITM's support for only hooking modules intended to be hooked (added in IITM 1.11.0, see nodejs/import-in-the-middle#146). This helps workaround cases where IITM hooking breaks some modules. The openai-chat.mjs is one such example.
1 parent 8fddaf7 commit f66cfc0

File tree

4 files changed

+60
-47
lines changed

4 files changed

+60
-47
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ build
44
.DS_Store
55
tmp
66
/packages/mockotlpserver/db
7+
/openai.env

examples/openai-chat.mjs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
// An ES Modules (ESM) version of "openai-chat.js".
21+
//
22+
// Usage:
23+
// OPENAI_API_KEY=sk-... \
24+
// node --import @elastic/opentelemetry-node openai-chat.mjs
25+
26+
import {OpenAI} from 'openai';
27+
28+
const openai = new OpenAI();
29+
const result = await openai.chat.completions.create({
30+
model: 'gpt-4o-mini',
31+
messages: [{role: 'user', content: 'Why is the sky blue? Answer briefly.'}],
32+
});
33+
console.log(result.choices[0]?.message?.content);

packages/opentelemetry-node/import.mjs

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,52 +20,31 @@
2020
// Register ESM hook and start the SDK.
2121
// This is called for `--import @elastic/opentelemetry-node`.
2222

23-
import * as module from 'node:module';
23+
import { register } from 'node:module';
2424
import {isMainThread} from 'node:worker_threads';
2525

26+
// TODO: @opentelemetry/instrumentation should re-export this IITM method.
27+
// XXX add explicit IITM dep?
28+
// XXX can we have a guard that there is only one install of IITM in play?
29+
import { createAddHookMessageChannel } from 'import-in-the-middle';
30+
2631
import {log} from './lib/logging.js';
2732

28-
/**
29-
* Return true iff it looks like the `@elastic/opentelemetry-node/hook.mjs`
30-
* was loaded via node's `--experimental-loader` option.
31-
*
32-
* Dev Note: keep this in sync with "require.js".
33-
*/
34-
function haveHookFromExperimentalLoader() {
35-
const USED_LOADER_OPT =
36-
/--(experimental-)?loader(\s+|=)@elastic\/opentelemetry-node\/hook.mjs/;
37-
for (let i = 0; i < process.execArgv.length; i++) {
38-
const arg = process.execArgv[i];
39-
const nextArg = process.execArgv[i + 1];
40-
if (
41-
(arg === '--loader' || arg === '--experimental-loader') &&
42-
nextArg === '@elastic/opentelemetry-node/hook.mjs'
43-
) {
44-
log.trace('bootstrap-import: --loader hook args used');
45-
return true;
46-
} else if (USED_LOADER_OPT.test(arg)) {
47-
log.trace('bootstrap-import: --loader hook arg used');
48-
return true;
49-
}
50-
}
51-
if (
52-
process.env.NODE_OPTIONS &&
53-
USED_LOADER_OPT.test(process.env.NODE_OPTIONS)
54-
) {
55-
log.trace('bootstrap-import: --loader arg used in NODE_OPTIONS');
56-
return true;
57-
}
58-
return false;
59-
}
33+
// Note: If there are *multiple* installations of import-in-the-middle, then
34+
// only those instrumentations using this same one will be hooked.
35+
const { registerOptions, waitForAllMessagesAcknowledged } =
36+
createAddHookMessageChannel();
6037

6138
if (isMainThread) {
62-
if (
63-
typeof module.register === 'function' &&
64-
!haveHookFromExperimentalLoader()
65-
) {
66-
log.trace('bootstrap-import: registering module hook');
67-
module.register('./hook.mjs', import.meta.url);
68-
}
39+
log.trace('bootstrap-import: registering module hook');
40+
// XXX module.register('./hook.mjs', import.meta.url);
41+
// TODO: `@opentelemetry/instrumentation/hook.mjs` needs to re-export initialize
42+
// register('@opentelemetry/instrumentation/hook.mjs', import.meta.url, registerOptions);
43+
register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions);
6944

7045
await import('./lib/start.js');
46+
47+
// Ensure that the loader has acknowledged all the modules before we allow
48+
// execution to continue.
49+
await waitForAllMessagesAcknowledged();
7150
}

packages/opentelemetry-node/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)