Skip to content

Commit 422a36a

Browse files
JamieDanielsonpkanallizthegrey
authored
feat: ESM support for instrumentation (#3698)
Co-authored-by: Purvi Kanal <kanal.purvi@gmail.com> Co-authored-by: Jamie Danielson <jamieedanielson@gmail.com> Co-authored-by: Liz Fong-Jones <lizf@honeycomb.io> Co-authored-by: Purvi Kanal <purvikanal@honeycomb.io>
1 parent bba09c0 commit 422a36a

17 files changed

Lines changed: 432 additions & 12 deletions

File tree

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use the latest and greatest features, and best practices.
1717
| [grpc](grpc/) | gRPC Instrumentation to automatically collect trace data and export them to the backend of choice | Intermediate |
1818
| [otlp-exporter-node](otlp-exporter-node/) | This example shows how to use `@opentelemetry/exporter-otlp-http` to instrument a simple Node.js application | Intermediate |
1919
| [opentracing-shim](opentracing-shim/) | This is a simple example that demonstrates how existing OpenTracing instrumentation can be integrated with OpenTelemetry | Intermediate |
20+
| [esm-http-ts](esm-http-ts/) | This is a simple example that demonstrates tracing HTTP request, with an app written in TypeScript and transpiled to ES Modules. | Intermediate |
2021

2122
Examples of experimental packages can be found at [experimental/examples](../experimental/examples).
2223

examples/esm-http-ts/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Overview
2+
3+
This is a simple example that demonstrates tracing HTTP request, with an app written in TypeScript and transpiled to ES Modules.
4+
5+
## Installation
6+
7+
```sh
8+
# from this directory
9+
npm install
10+
npm run build
11+
npm start
12+
```
13+
14+
In a separate terminal, `curl localhost:3000`.
15+
16+
See two spans in the console (one manual, one for http instrumentation)
17+
18+
## Useful links
19+
20+
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>
21+
- For more information on OpenTelemetry for Node.js, visit: <https://github.com/open-telemetry/opentelemetry-js/tree/main/packages/opentelemetry-sdk-trace-node>
22+
23+
## LICENSE
24+
25+
Apache License 2.0

examples/esm-http-ts/index.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { registerInstrumentations } from '@opentelemetry/instrumentation';
2+
import { trace, DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
3+
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
4+
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
5+
import {
6+
ConsoleSpanExporter,
7+
SimpleSpanProcessor,
8+
} from '@opentelemetry/sdk-trace-base';
9+
import { Resource } from '@opentelemetry/resources';
10+
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
11+
import http from 'http';
12+
13+
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);
14+
const tracerProvider = new NodeTracerProvider({
15+
resource: new Resource({
16+
[SemanticResourceAttributes.SERVICE_NAME]: 'esm-http-ts-example',
17+
}),
18+
});
19+
const exporter = new ConsoleSpanExporter();
20+
const processor = new SimpleSpanProcessor(exporter);
21+
tracerProvider.addSpanProcessor(processor);
22+
tracerProvider.register();
23+
24+
registerInstrumentations({
25+
instrumentations: [new HttpInstrumentation()],
26+
});
27+
28+
const hostname = '0.0.0.0';
29+
const port = 3000;
30+
31+
const server = http.createServer((req, res) => {
32+
res.statusCode = 200;
33+
res.setHeader('Content-Type', 'text/plain');
34+
const tracer = trace.getTracer('esm-tracer');
35+
tracer.startActiveSpan('manual', span => {
36+
span.end();
37+
});
38+
res.end('Hello, World!\n');
39+
});
40+
41+
server.listen(port, hostname, () => {
42+
console.log(`Server running at http://${hostname}:${port}/`);
43+
});

examples/esm-http-ts/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "esm-http-ts",
3+
"private": true,
4+
"version": "0.38.0",
5+
"description": "Example of HTTP integration with OpenTelemetry using ESM and TypeScript",
6+
"main": "build/index.js",
7+
"type": "module",
8+
"scripts": {
9+
"build": "tsc --build",
10+
"start": "node --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./build/index.js"
11+
},
12+
"repository": {
13+
"type": "git",
14+
"url": "git+ssh://git@github.com/open-telemetry/opentelemetry-js.git"
15+
},
16+
"keywords": [
17+
"opentelemetry",
18+
"http",
19+
"tracing",
20+
"esm",
21+
"typescript"
22+
],
23+
"engines": {
24+
"node": ">=14"
25+
},
26+
"author": "OpenTelemetry Authors",
27+
"license": "Apache-2.0",
28+
"bugs": {
29+
"url": "https://github.com/open-telemetry/opentelemetry-js/issues"
30+
},
31+
"homepage": "https://github.com/open-telemetry/opentelemetry-js/tree/main/examples/",
32+
"dependencies": {
33+
"@opentelemetry/api": "1.4.0",
34+
"@opentelemetry/exporter-trace-otlp-proto": "0.38.0",
35+
"@opentelemetry/instrumentation": "0.38.0",
36+
"@opentelemetry/instrumentation-http": "0.38.0",
37+
"@opentelemetry/resources": "1.9.1",
38+
"@opentelemetry/sdk-trace-base": "1.9.1",
39+
"@opentelemetry/sdk-trace-node": "1.9.1",
40+
"@opentelemetry/semantic-conventions": "1.9.1"
41+
}
42+
}

examples/esm-http-ts/tsconfig.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"compilerOptions": {
3+
/* Language and Environment */
4+
"target": "ES2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
5+
6+
/* Modules */
7+
"module": "ESNext" /* Specify what module code is generated. */,
8+
"rootDir": "." /* Specify the root folder within your source files. */,
9+
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
10+
"resolveJsonModule": true /* Enable importing .json files. */,
11+
12+
/* Emit */
13+
"outDir": "build" /* Specify an output folder for all emitted files. */,
14+
15+
/* Interop Constraints */
16+
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
17+
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
18+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
19+
20+
/* Completeness */
21+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
22+
},
23+
"include": ["**/*.ts", "**/*.js", "*.config.js"],
24+
"exclude": ["node_modules"]
25+
}

experimental/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ All notable changes to experimental packages in this project will be documented
88

99
### :rocket: (Enhancement)
1010

11+
* feat(instrumentation): add ESM support for instrumentation. [#3698](https://github.com/open-telemetry/opentelemetry-js/pull/3698) @JamieDanielson, @pkanal, @vmarchaud, @lizthegrey, @bengl
12+
1113
### :bug: (Bug Fix)
1214

1315
### :books: (Refine Doc)

experimental/packages/opentelemetry-instrumentation-http/src/http.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
6666
export class HttpInstrumentation extends InstrumentationBase<Http> {
6767
/** keep track on spans not ended */
6868
private readonly _spanNotEnded: WeakSet<Span> = new WeakSet<Span>();
69-
private readonly _version = process.versions.node;
7069
private _headerCapture;
7170
private _httpServerDurationHistogram!: Histogram;
7271
private _httpClientDurationHistogram!: Histogram;
@@ -112,11 +111,12 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
112111
}
113112

114113
private _getHttpInstrumentation() {
114+
const version = process.versions.node;
115115
return new InstrumentationNodeModuleDefinition<Http>(
116116
'http',
117117
['*'],
118118
moduleExports => {
119-
this._diag.debug(`Applying patch for http@${this._version}`);
119+
this._diag.debug(`Applying patch for http@${version}`);
120120
if (isWrapped(moduleExports.request)) {
121121
this._unwrap(moduleExports, 'request');
122122
}
@@ -145,7 +145,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
145145
},
146146
moduleExports => {
147147
if (moduleExports === undefined) return;
148-
this._diag.debug(`Removing patch for http@${this._version}`);
148+
this._diag.debug(`Removing patch for http@${version}`);
149149

150150
this._unwrap(moduleExports, 'request');
151151
this._unwrap(moduleExports, 'get');
@@ -155,11 +155,12 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
155155
}
156156

157157
private _getHttpsInstrumentation() {
158+
const version = process.versions.node;
158159
return new InstrumentationNodeModuleDefinition<Https>(
159160
'https',
160161
['*'],
161162
moduleExports => {
162-
this._diag.debug(`Applying patch for https@${this._version}`);
163+
this._diag.debug(`Applying patch for https@${version}`);
163164
if (isWrapped(moduleExports.request)) {
164165
this._unwrap(moduleExports, 'request');
165166
}
@@ -188,7 +189,7 @@ export class HttpInstrumentation extends InstrumentationBase<Http> {
188189
},
189190
moduleExports => {
190191
if (moduleExports === undefined) return;
191-
this._diag.debug(`Removing patch for https@${this._version}`);
192+
this._diag.debug(`Removing patch for https@${version}`);
192193

193194
this._unwrap(moduleExports, 'request');
194195
this._unwrap(moduleExports, 'get');

experimental/packages/opentelemetry-instrumentation/README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,18 @@ If nothing is specified the global registered provider is used. Usually this is
219219
There might be usecase where someone has the need for more providers within an application. Please note that special care must be takes in such setups
220220
to avoid leaking information from one provider to the other because there are a lot places where e.g. the global `ContextManager` or `Propagator` is used.
221221

222+
## Instrumentation for ES Modules In NodeJS (experimental)
223+
224+
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` flag to the `node` binary. Alternatively you can set the `NODE_OPTIONS` environment variable to `NODE_OPTIONS="--experimental-loader=@opentelemetry/instrumentation/hook.mjs"`.
225+
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.
226+
227+
**Note**: ESM Instrumentation is not yet supported for Node 20.
228+
222229
## Limitations
223230

224-
Instrumentations for external modules (e.g. express, mongodb,...) hooks the `require` call. Therefore following conditions need to be met that this mechanism can work:
231+
Instrumentations for external modules (e.g. express, mongodb,...) hooks the `require` call or `import` statement. Therefore following conditions need to be met that this mechanism can work:
225232

226-
* `require` is used. ECMA script modules (using `import`) is not supported as of now
227-
* Instrumentations are registered **before** the module to instrument is `require`ed
233+
* Instrumentations are registered **before** the module to instrument is `require`ed (CJS only)
228234
* modules are not included in a bundle. Tools like `esbuild`, `webpack`, ... usually have some mechanism to exclude specific modules from bundling
229235

230236
## License
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*!
2+
* Copyright 2021 Datadog, Inc.
3+
* Copyright The OpenTelemetry Authors
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
19+
//
20+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
21+
22+
import {
23+
load,
24+
resolve,
25+
getFormat,
26+
getSource,
27+
} from 'import-in-the-middle/hook.mjs';
28+
export { load, resolve, getFormat, getSource };

experimental/packages/opentelemetry-instrumentation/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"build/src/**/*.js",
3333
"build/src/**/*.js.map",
3434
"build/src/**/*.d.ts",
35+
"hook.mjs",
3536
"doc",
3637
"LICENSE",
3738
"README.md"
@@ -47,7 +48,9 @@
4748
"tdd": "npm run tdd:node",
4849
"tdd:node": "npm run test -- --watch-extensions ts --watch",
4950
"tdd:browser": "karma start",
50-
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'",
51+
"test:cjs": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'",
52+
"test:esm": "nyc node --experimental-loader=./hook.mjs ../../../node_modules/mocha/bin/mocha 'test/node/*.test.mjs' test/node/*.test.mjs",
53+
"test": "npm run test:cjs && npm run test:esm",
5154
"test:browser": "nyc karma start --single-run",
5255
"version": "node ../../../scripts/version-update.js",
5356
"watch": "tsc --build --watch tsconfig.json tsconfig.esm.json tsconfig.esnext.json",
@@ -68,6 +71,8 @@
6871
"url": "https://github.com/open-telemetry/opentelemetry-js/issues"
6972
},
7073
"dependencies": {
74+
"@types/shimmer": "^1.0.2",
75+
"import-in-the-middle": "1.3.5",
7176
"require-in-the-middle": "^7.1.0",
7277
"semver": "^7.3.2",
7378
"shimmer": "^1.2.1"
@@ -82,7 +87,6 @@
8287
"@types/mocha": "10.0.0",
8388
"@types/node": "18.6.5",
8489
"@types/semver": "7.3.9",
85-
"@types/shimmer": "1.0.2",
8690
"@types/sinon": "10.0.13",
8791
"@types/webpack-env": "1.16.3",
8892
"babel-loader": "8.2.3",

0 commit comments

Comments
 (0)