Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
43fd59e
docs: add example for esm-http-ts
JamieDanielson Mar 21, 2023
e5215fe
fix: use instr version not node version
JamieDanielson Mar 21, 2023
3c51c6d
deps: add import-in-the-middle
JamieDanielson Mar 21, 2023
8444af3
feat: add iitm for esm instr
JamieDanielson Mar 21, 2023
e4478d9
docs: add readme notes from valentin
JamieDanielson Mar 21, 2023
1434b27
wip: add test based on valentin's test
JamieDanielson Mar 21, 2023
a533f9f
remove changes to ritm init
JamieDanielson Mar 22, 2023
a43089b
update example - use build dir, add readme
JamieDanielson Mar 22, 2023
85a2e8f
add changelog entry
JamieDanielson Mar 22, 2023
e0b5c44
dont use ritm singleton for iitm
JamieDanielson Mar 23, 2023
63477c5
fix up call for new iitm
JamieDanielson Mar 23, 2023
1188c02
Merge branch 'main' into esm-support
JamieDanielson Mar 23, 2023
33e544c
Merge branch 'main' into esm-support
lizthegrey Mar 28, 2023
bfe51f5
wrap function refactor
pkanal Mar 31, 2023
7110c37
clean up
pkanal Apr 3, 2023
3ddb3a9
Merge pull request #1 from honeycombio/purvi/esm-tests
JamieDanielson Apr 4, 2023
3f3d9a2
Merge branch 'main' into esm-support
JamieDanielson Apr 4, 2023
2936a16
update changelog: it takes a village
JamieDanielson Apr 4, 2023
4a10129
small cleanup on test
JamieDanielson Apr 4, 2023
50363c5
version should be node version, not instr version
JamieDanielson Apr 5, 2023
c9922c3
run lint:fix for prettier error
JamieDanielson Apr 5, 2023
b722486
Merge pull request #2 from honeycombio/jamie.http-node-version
pkanal Apr 5, 2023
4ac1ee1
base unwrap functionality
pkanal Apr 5, 2023
a346531
move esm specific wrap logic to node class
pkanal Apr 5, 2023
1ca6b55
fix types
pkanal Apr 6, 2023
4401576
add masswrap & massunwrap
pkanal Apr 6, 2023
86b6837
handle edge cases for masswrap and massunwrap
pkanal Apr 6, 2023
da694c7
Merge pull request #3 from honeycombio/purvi.unwrap
JamieDanielson Apr 7, 2023
1612855
Merge branch 'main' into esm-support
JamieDanielson Apr 7, 2023
e924c99
Ship shimmer types (#5)
pkanal Apr 25, 2023
b894bcd
Merge branch 'main' into esm-support
pkanal Apr 25, 2023
68c43c9
use --experimental-meta-resolve option with hook file (#8)
pkanal Apr 26, 2023
a2a74f8
Merge branch 'main' into esm-support
pkanal May 1, 2023
a529c8c
fix markdown
pkanal May 1, 2023
ddc8dc5
Merge branch 'main' into esm-support
pkanal May 3, 2023
42eb5ba
address PR feedback (#9)
pkanal May 3, 2023
4f71df8
Update experimental/packages/opentelemetry-instrumentation/hook.js
pkanal May 5, 2023
6a052b1
Update experimental/packages/opentelemetry-instrumentation/hook.mjs
pkanal May 5, 2023
58d34c0
change back to process.versions.node (#10)
pkanal May 5, 2023
33409da
address pr feedback (#11)
pkanal May 9, 2023
1f017b0
remove `--experimental-import-meta-resolve` flag (#12)
pkanal May 10, 2023
16109d3
Merge branch 'main' into esm-support
JamieDanielson May 10, 2023
bcaef1c
Merge branch 'main' into esm-support
JamieDanielson May 11, 2023
c590efc
Merge branch 'main' into esm-support
JamieDanielson May 12, 2023
59047bd
remove import limitation from README
pkanal May 12, 2023
0167a10
add esm example to examples readme
pkanal May 12, 2023
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
Prev Previous commit
Next Next commit
use --experimental-meta-resolve option with hook file (#8)
Allows `hook.mjs` file to exported from the instrumentation package if
the user also uses the `--experimental-meta-resolve` flag. If a user
doesn't want to use this flag, they can still reference
`import-in-the-middle/hook.mjs`. I figure this option is safe because
this is such a Node targeted feature that I'm not worried about someone
trying to run this in the browser. The code path to the ESM hook is
entirely contained in the `node` folder which wouldn't be available if
someone were to run this package in the browser and they wouldn't be
using the hook anyway.

---------

Co-authored-by: Jamie Danielson <[email protected]>
  • Loading branch information
pkanal and JamieDanielson authored Apr 26, 2023
commit 68c43c91b69ec577c0506215df60c6b759c1f177
8 changes: 4 additions & 4 deletions examples/esm-http-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "esm-http-ts",
"private": true,
"version": "0.36.1",
"version": "0.38.0",
"description": "Example of HTTP integration with OpenTelemetry using ESM and TypeScript",
"main": "build/index.js",
"type": "module",
"scripts": {
"build": "tsc --build",
"start": "node --experimental-loader=import-in-the-middle/hook.mjs ./build/index.js"
"start": "node --experimental-import-meta-resolve --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./build/index.js"
},
"repository": {
"type": "git",
Expand All @@ -32,8 +32,8 @@
"dependencies": {
"@opentelemetry/api": "^1.4.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.36.0",
"@opentelemetry/instrumentation": "0.36.1",
"@opentelemetry/instrumentation-http": "0.36.1",
"@opentelemetry/instrumentation": "0.38.0",
"@opentelemetry/instrumentation-http": "0.38.0",
"@opentelemetry/resources": "^1.9.1",
"@opentelemetry/sdk-trace-base": "^1.9.1",
"@opentelemetry/sdk-trace-node": "^1.9.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ to avoid leaking information from one provider to the other because there are a

## Instrumentation for ES Modules In NodeJS (experimental)

As the module loading mechanism for ESM is different than CJS, you need to select a custom loader so instrumentation can load hook on the esm module it want to patch. To do so, you must provide the `--experimental-loader=import-in-the-middle/hook.mjs` flag to the `node` binary. Alternatively you can set the `NODE_OPTIONS` environment variable to `--experimental-loader=import-in-the-middle/hook.mjs`.
As the module loading mechanism for ESM is different than CJS, you need to select a custom loader so instrumentation can load hook on the esm module it want to patch. To do so, you must provide the `--experimental-loader=@opentelemetry/instrumentation/hook.mjs --experimental-import-meta-resolve` flag to the `node` binary. Alternatively you can set the `NODE_OPTIONS` environment variable to `NODE_OPTIONS="--experimental-loader=@opentelemetry/instrumentation/hook.mjs --experimental-import-meta-resolve"`.
To avoid using the `--experimental-import-meta-resolve` flag, the hook file can be referenced directly from the `import-in-the-middle` library as `--experimental-loader=import-in-the-middle/hook.mjs`.
As the ESM module loader from NodeJS is experimental, so is our support for it. Feel free to provide feedback or report issues about it.

## License
Expand Down
198 changes: 198 additions & 0 deletions experimental/packages/opentelemetry-instrumentation/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*!
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

const specifiers = new Map();
const isWin = process.platform === 'win32';

// FIXME: Typescript extensions are added temporarily until we find a better
// way of supporting arbitrary extensions
const EXTENSION_RE = /\.(js|mjs|cjs|ts|mts|cts)$/;
const NODE_VERSION = process.versions.node.split('.');
const NODE_MAJOR = Number(NODE_VERSION[0]);
const NODE_MINOR = Number(NODE_VERSION[1]);

let entrypoint;

function hasIitm(url) {
try {
return new URL(url).searchParams.has('iitm');
} catch {
return false;
}
}

function isIitm(url, meta) {
return url === meta.url || url === meta.url.replace('hook.mjs', 'hook.js');
}

function deleteIitm(url) {
let resultUrl;
try {
const urlObj = new URL(url);
if (urlObj.searchParams.has('iitm')) {
urlObj.searchParams.delete('iitm');
resultUrl = urlObj.href;
if (resultUrl.startsWith('file:node:')) {
resultUrl = resultUrl.replace('file:', '');
}
if (resultUrl.startsWith('file:///node:')) {
resultUrl = resultUrl.replace('file:///', '');
}
} else {
resultUrl = urlObj.href;
}
} catch {
resultUrl = url;
}
return resultUrl;
}

function isNode16AndBiggerOrEqualsThan16_17_0() {
return NODE_MAJOR === 16 && NODE_MINOR >= 17;
}

function isFileProtocol(urlObj) {
return urlObj.protocol === 'file:';
}

function isNodeProtocol(urlObj) {
return urlObj.protocol === 'node:';
}

function needsToAddFileProtocol(urlObj) {
if (NODE_MAJOR === 17) {
return !isFileProtocol(urlObj);
}
if (isNode16AndBiggerOrEqualsThan16_17_0()) {
return !isFileProtocol(urlObj) && !isNodeProtocol(urlObj);
}
return !isFileProtocol(urlObj) && NODE_MAJOR < 18;
}

function addIitm(url) {
const urlObj = new URL(url);
urlObj.searchParams.set('iitm', 'true');
return needsToAddFileProtocol(urlObj) ? 'file:' + urlObj.href : urlObj.href;
}

async function createHook(meta) {
async function resolve(specifier, context, parentResolve) {
const { parentURL = '' } = context;
const newSpecifier = deleteIitm(specifier);
if (isWin && parentURL.indexOf('file:node') === 0) {
context.parentURL = '';
}
const url = await parentResolve(newSpecifier, context, parentResolve);
if (parentURL === '' && !EXTENSION_RE.test(url.url)) {
entrypoint = url.url;
return { url: url.url, format: 'commonjs' };
}

if (isIitm(parentURL, meta) || hasIitm(parentURL)) {
return url;
}

if (context.importAssertions && context.importAssertions.type === 'json') {
return url;
}

specifiers.set(url.url, specifier);

return {
url: addIitm(url.url),
shortCircuit: true,
};
}

const iitmURL = await meta.resolve('import-in-the-middle/lib/register.js');
async function getSource(url, context, parentGetSource) {
if (hasIitm(url)) {
const realUrl = deleteIitm(url);
const realModule = await import(realUrl);
const exportNames = Object.keys(realModule);
return {
source: `
import { register } from '${iitmURL}'
import * as namespace from '${url}'
const set = {}
${exportNames
.map(
n => `
let $${n} = namespace.${n}
export { $${n} as ${n} }
set.${n} = (v) => {
$${n} = v
return true
}
`
)
.join('\n')}
register('${realUrl}', namespace, set, '${specifiers.get(realUrl)}')
`,
};
}

return parentGetSource(url, context, parentGetSource);
}

// For Node.js 16.12.0 and higher.
async function load(url, context, parentLoad) {
if (hasIitm(url)) {
const { source } = await getSource(url, context);
return {
source,
shortCircuit: true,
format: 'module',
};
}

return parentLoad(url, context, parentLoad);
}

if (NODE_MAJOR >= 20) {
process.emitWarning(
'import-in-the-middle is currently unsupported on Node.js v20 and has been disabled.'
);
return {}; // TODO: Add support for Node >=20
} else if (NODE_MAJOR >= 17 || (NODE_MAJOR === 16 && NODE_MINOR >= 12)) {
return { load, resolve };
} else {
return {
load,
resolve,
getSource,
getFormat(url, context, parentGetFormat) {
if (hasIitm(url)) {
return {
format: 'module',
};
}
if (url === entrypoint) {
return {
format: 'commonjs',
};
}

return parentGetFormat(url, context, parentGetFormat);
},
};
}
}

module.exports = { createHook };
25 changes: 25 additions & 0 deletions experimental/packages/opentelemetry-instrumentation/hook.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*!
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

import { createHook } from './hook.js';

const { load, resolve, getFormat, getSource } = await createHook(import.meta);

export { load, resolve, getFormat, getSource };
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"build/src/**/*.js",
"build/src/**/*.js.map",
"build/src/**/*.d.ts",
"hook.js",
"hook.mjs",
"doc",
"LICENSE",
"README.md"
Expand All @@ -48,7 +50,7 @@
"tdd:node": "npm run test -- --watch-extensions ts --watch",
"tdd:browser": "karma start",
"test:cjs": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'",
"test:esm": "nyc node --experimental-loader=import-in-the-middle/hook.mjs ../../../node_modules/mocha/bin/mocha 'test/node/*.test.mjs' test/node/*.test.mjs",
"test:esm": "nyc node --experimental-import-meta-resolve --experimental-loader=./hook.mjs ../../../node_modules/mocha/bin/mocha 'test/node/*.test.mjs' test/node/*.test.mjs",
"test": "npm run test:cjs && npm run test:esm",
"test:browser": "nyc karma start --single-run",
"version": "node ../../../scripts/version-update.js",
Expand Down