-
Notifications
You must be signed in to change notification settings - Fork 44
feat: Optionally only wrap modules hooked in --import
#146
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
Conversation
|
Can you think of any otel instrumentations where this might be problematic? My first thought was that maybe |
--import
AbhiPrasad
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should update the README to add details about createAddHookMessageChannel
Qard
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a bit of a race condition here in that there's no way to be sure the messages have been received by the loader thread before the --import file completes and the entrypoint file begins.
Perhaps there could be some sort of acknowledgement message for each and there could be a promise you can await at the end of that --import file to wait until all acknowledgements have been received before proceeding?
And even if it's not possible to trigger the race condition now, it remains a possibility in the future! I've added acknowledgment messages and updated the example in the top post. |
|
I'm getting exit code 13 from the new test on some versions in CI but it passes locally:
You can't have the end of execution blocked simply waiting on a promise because it assumes it's never going to resolve? |
| const timer = setInterval(() => { }, 1000) | ||
| const promise = new Promise((resolve) => { | ||
| resolveFn = resolve | ||
| }).then(() => { clearInterval(timer) }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This timer stops the process exiting with error code 13 which is triggered by blocking exit via top-level await if there are no more active handles. This feels... sub-optimal but I don't know how else we should be working around this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent little bit trying to avoid this but couldn't come up with a better way. I'm gonna take another look tomorrow morning, but if I can't figure anything else out I'll approve.
|
@trentm thanks for the feedback!
Yes, this is a bug in the previously added feature. I'll submit an additional PR to fix and test for this (#149).
For On the other hand, I'll take another look at the code and how |
e3c735c to
2d3fc58
Compare
|
Ok, so I've marked this new API as experimental and this and the previous feature addition as incompatible with the
We may be able to fix this in the future! |
trentm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this!
| } | ||
|
|
||
| /** | ||
| * EXPERIMENTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too bad TypeScript jsdoc tag support doesn't support @experimental. Hopefully at some point (microsoft/TypeScript#56808)
|
Sorry, I had to make some changes after merging in the |
| for (const each of modules) { | ||
| if (!each.startsWith('node:') && builtinModules.includes(each)) { | ||
| includeModules.push(`node:${each}`) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the addition I need to make!
Likely closes many issues but I don't want to auto-close anything specific here. We should probably confirm the issues are closed individually. `import-in-the-middle` by default wraps every ES module with a wrapping module that later allows it exports to be modified. This has issues though because the wrapping output of `import-in-the-middle` is not compatible with all modules. To help work around this I [added a feature](nodejs/import-in-the-middle#146) to `import-in-the-middle` that allows us to only wrap modules that we specifically need to instrument. ```ts import * as Sentry from '@sentry/node'; Sentry.init({ dsn: '__DSN__', registerEsmLoaderHooks: { onlyHookedModules: true }, }); ``` So far I've only changed the express integration test to use this new mode.
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.
The Problem
import-in-the-middlehas been designed to wrap every module to allow hooking of them after they've been imported.However
import-in-the-middlehas a couple of fundamental issues which means it will likely remain incompatible with some libraries:drizzle-ormexports #141 (comment)These issues almost certainly cannot be solved with our current wrapping module strategy and are unlikely to get fixed even with the new loader proposals without changes to the ESM spec.
A Solution?
The obvious workaround is to only wrap modules that we later intend to hook!
Since the
--loadercli arg is deprecated, many APM vendors (including Sentry) now recommend that users initialise instrumentation of their code via the--importcli arg. For Sentry at least, we register the hook and also initialise all the OpenTelemetry plugins which in turn callHookwith the required libraries.This PR adds the ability to only wrap modules that are hooked before they are imported. This is almost always the case when hooking via
--import.When registering the iitm hook, you can pass a
MessagePortthat is passed like this:Or more explicitly, you can pass the message port yourself:
Now, if there are any calls to
Hookwith a specific list of modules, these will be added to theincludelist and only those modules will be wrapped.This would mean that if you initialise OpenTelemetry via
--import, the OpenTelemetry plugins define which modules to wrap!