-
Notifications
You must be signed in to change notification settings - Fork 26
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
feat: Optionally only wrap modules hooked in --import
#146
Conversation
Can you think of any otel instrumentations where this might be problematic? My first thought was that maybe |
--import
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
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.
issue with
|
@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! |
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.
The Problem
import-in-the-middle
has been designed to wrap every module to allow hooking of them after they've been imported.However
import-in-the-middle
has a couple of fundamental issues which means it will likely remain incompatible with some libraries:drizzle-orm
exports #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
--loader
cli arg is deprecated, many APM vendors (including Sentry) now recommend that users initialise instrumentation of their code via the--import
cli arg. For Sentry at least, we register the hook and also initialise all the OpenTelemetry plugins which in turn callHook
with 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
MessagePort
that is passed like this:Or more explicitly, you can pass the message port yourself:
Now, if there are any calls to
Hook
with a specific list of modules, these will be added to theinclude
list 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!