Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Express doesn't get instrumented with ESM #1849

Closed
kevinmarks opened this issue Dec 7, 2023 · 16 comments
Closed

Express doesn't get instrumented with ESM #1849

kevinmarks opened this issue Dec 7, 2023 · 16 comments
Assignees
Labels
bug Something isn't working has:reproducer This bug/feature has a minimal reproduction provided information-requested pkg:instrumentation-express priority:p2 Bugs and spec inconsistencies which cause telemetry to be incomplete or incorrect

Comments

@kevinmarks
Copy link

What version of OpenTelemetry are you using?

"@opentelemetry/api": "^1.6.0",
"@opentelemetry/auto-instrumentations-node": "^0.40.1",

What version of Node are you using?

v16.20.1

What did you do?

Instrumented a minimal node app with honeycombio

What did you expect to see?

Traces that included the express auto instrumentation

What did you see instead?

Traces that only included http and net instrumentation

Additional context

Example project here - needs a honeycomb api key in telemetry.mjs to run
https:/kevinmarks/OT-testbed

@kevinmarks kevinmarks added the bug Something isn't working label Dec 7, 2023
@Flarna
Copy link
Member

Flarna commented Dec 7, 2023

Seems you use ECMA script modules. Support is experimental and requires some extra love on how you call node. see here

@JamieDanielson JamieDanielson added pkg:instrumentation-express has:reproducer This bug/feature has a minimal reproduction provided labels Dec 7, 2023
@kevinmarks
Copy link
Author

If I run it with that prefix, I get a logline after the debug output saying:

Applying patch for [email protected]
OT-testbed Listening on 8989

but no events sent to honeycomb at all. Removing that again, http events show up.

@JamieDanielson JamieDanielson self-assigned this Dec 7, 2023
@JamieDanielson
Copy link
Member

I'm taking a look into this. The Applying patch for [email protected] log is a good sign of express instrumentation patching, but I'll report back what I find.

@JamieDanielson JamieDanielson changed the title Instrumenting a minimal node app using ESM with honeycomb doesn't instrument the express part Express doesn't get instrumented with ESM Jan 10, 2024
@JamieDanielson
Copy link
Member

I guess this is similar to some of our other ESM problems. I can do a very small reproducer using just the ExpressInstrumentation (not fully set up to instrument, just to show the problem) that I believe is a good indicator of the problem, and I think it's related to how modules are loaded in ESM vs CJS.

import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);

import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
new ExpressInstrumentation();

import express from 'express';
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000, () => console.log('Example app listening on port 3000!'));

This ESM app test.mjs shows a loading error in output, with and without the loader hook

✗ node --experimental-loader=@opentelemetry/instrumentation/hook.mjs test.mjs
(node:16336) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
@opentelemetry/api: Registered a global for diag v1.6.0.
@opentelemetry/instrumentation-express Module express has been loaded before @opentelemetry/instrumentation-express so it might not work, please initialize it before requiring express
Example app listening on port 3000!

An identical app called test.cjs that uses const and require does not have the same error

✗ node test.cjs
@opentelemetry/api: Registered a global for diag v1.6.0.
Applying patch for [email protected]
Example app listening on port 3000!

In both, the order of everything in the code is identical. This will need more time to sort out.

@OliverJAsh
Copy link

I am encountering the same issue. I created a reduced test case before discovering this issue. Sharing in case it's helpful: https:/OliverJAsh/esm-otel.

As the reduced test case demonstrates, this issue also affects the Pino instrumentation.

@JamieDanielson
Copy link
Member

@kevinmarks sorry for the delay on this, I had gotten stuck on it and picked up some other things in the meantime. I've come back around to it and believe I have a solution as I was able to use your example repro.

The key issue is that express is getting loaded before the instrumentation can monkey-patch it. So we can use top-level awaits available in ESM to solve this.

The main changes I made to make your app work were to make the tracing an async function with an awaited default export, and then await the import of express in the app. I also ran the start command with the loader hook.

// tracing.mjs
...
export const startTracing = async () => {
  const node_sdk = new NodeSDK({
  ...
  });

  node_sdk.start()
}
export default await startTracing;
// index.mjs
import startTracing from './tracing.mjs';
startTracing();

const expressModule = await import('express');
const express = expressModule.default;
...

Now with debug enabled, my log includes the patch logs I expect, and hitting the endpoint results in spans for http and express.

➜  OT-testbed git:(main) ✗ npm run start:mjs

> [email protected] start:mjs
> node --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./index.mjs
...
@opentelemetry/instrumentation-http Applying patch for [email protected]
Applying patch for net module
Applying patch for [email protected]
OT-testbed Listening on 3000
...

Would you be able to give this a try with your app and confirm it works properly?

@JamieDanielson
Copy link
Member

Actually you might even be able to keep your app and tracing module as is, and instead change the start script to ensure the tracing file is loaded first. node --experimental-loader=@opentelemetry/instrumentation/hook.mjs --import ./tracing.mjs ./index.mjs. Doing this I also got instrumentation as expected for express and http, though note I'm on Node 18.18.2.

@jeffbicca
Copy link

jeffbicca commented Jun 5, 2024

Doing some testing as @JamieDanielson said and this also seems to do the trick, just in case for those who want to use @opentelemetry/auto-instrumentations-node register script instead of developing it's own otel bootstrap file (the good part of this is to configure otel directly into the env vars):

node --loader @opentelemetry/instrumentation/hook.mjs --import @opentelemetry/auto-instrumentations-node/register index

Tested in node 20.12.1 with Express 4.18.2

@vchirikov
Copy link

--experimental-loader=@opentelemetry/instrumentation/hook.mjs doesn't work with astro for example.

@trentm
Copy link
Contributor

trentm commented Jun 12, 2024

doesn't work with astro for example.

@vchirikov If you are able to help provide more details, it would be best to open a separate issue to for getting things working with Astro. Ideally that would include a small and complete reproduction so maintainers don't have to learn how to build an astro-based app. My total guess is that astro is a web framework that is doing bundling. If so, then bundling currently breaks any auto-instrumentation because included packages are no longer loaded using require or import.

@vchirikov
Copy link

vchirikov commented Jun 12, 2024

Offtopic

Astro is based on vite, vite uses ESM. The first error was `SyntaxError: The requested module './bundle-full.mjs' does not provide an export named 'codeToHast'` from `shiki` and some of astro internals (`dist/cookies` as far as I can remember). It's an issue with `import-in-the-middle`. I found a workaround - hook ONLY the desired modules (`node:*` in my case), I have to create a git patch to do it (with `patch-package`), but at least it works.

Anyway, I think the usage of opentelemetry-js packages should be much easier & better documented.

Copy link
Contributor

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.

@github-actions github-actions bot added the stale label Aug 12, 2024
@trentm
Copy link
Contributor

trentm commented Aug 12, 2024

@vchirikov

still Offtopic

I found a workaround - hook ONLY the desired modules

I hope that OTel will get to doing that by default now that import-in-the-middle supports it via nodejs/import-in-the-middle#146

@trentm trentm removed the stale label Aug 12, 2024
@atsu85
Copy link
Contributor

atsu85 commented Oct 8, 2024

@JamieDanielson, thanks for good explanation!
Any updates related to this issue?

@JamieDanielson
Copy link
Member

@JamieDanielson, thanks for good explanation! Any updates related to this issue?

@atsu85 this should work with the suggestions listed above. I've finally added a doc with these details as well, which I'm working on getting into the main docs site.

@JamieDanielson
Copy link
Member

I will close this specific issue with the above note but if any new issues arise please feel free to open a new issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working has:reproducer This bug/feature has a minimal reproduction provided information-requested pkg:instrumentation-express priority:p2 Bugs and spec inconsistencies which cause telemetry to be incomplete or incorrect
Projects
None yet
Development

No branches or pull requests

9 participants