Skip to content

Commit d5e4bf2

Browse files
committed
feat(instrumentation): implement require-in-the-middle singleton
1 parent 038df3f commit d5e4bf2

File tree

1 file changed

+52
-16
lines changed

1 file changed

+52
-16
lines changed

experimental/packages/opentelemetry-instrumentation/src/platform/node/instrumentation.ts

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export abstract class InstrumentationBase<T = any>
2929
extends InstrumentationAbstract
3030
implements types.Instrumentation {
3131
private _modules: InstrumentationModuleDefinition<T>[];
32-
private _hooks: RequireInTheMiddle.Hooked[] = [];
32+
private _hooked = false;
3333
private _enabled = false;
3434

3535
constructor(
@@ -142,7 +142,7 @@ export abstract class InstrumentationBase<T = any>
142142
this._enabled = true;
143143

144144
// already hooked, just call patch again
145-
if (this._hooks.length > 0) {
145+
if (this._hooked) {
146146
for (const module of this._modules) {
147147
if (typeof module.patch === 'function' && module.moduleExports) {
148148
module.patch(module.moduleExports, module.moduleVersion);
@@ -158,22 +158,20 @@ export abstract class InstrumentationBase<T = any>
158158

159159
this._warnOnPreloadedModules();
160160
for (const module of this._modules) {
161-
this._hooks.push(
162-
RequireInTheMiddle(
163-
[module.name],
164-
{ internals: true },
165-
(exports, name, baseDir) => {
166-
return this._onRequire<typeof exports>(
167-
(module as unknown) as InstrumentationModuleDefinition<
168-
typeof exports
161+
requireInTheMiddleSingleton.register(
162+
module.name,
163+
(exports, name, baseDir) => {
164+
return this._onRequire<typeof exports>(
165+
(module as unknown) as InstrumentationModuleDefinition<
166+
typeof exports
169167
>,
170-
exports,
171-
name,
172-
baseDir
173-
);
174-
}
175-
)
168+
exports,
169+
name,
170+
baseDir
171+
);
172+
}
176173
);
174+
this._hooked = true;
177175
}
178176
}
179177

@@ -210,3 +208,41 @@ function isSupported(supportedVersions: string[], version?: string, includePrere
210208
return satisfies(version, supportedVersion, { includePrerelease });
211209
});
212210
}
211+
212+
/**
213+
* Singleton class for `require-in-the-middle`
214+
* Allows instrumentation plugins to patch modules with only a single `require` patch
215+
* TODO: Move this to a higher level to ensure it is a singleton across the Node.js process
216+
*/
217+
class RequireInTheMiddleSingleton {
218+
private _modulesToHook: Array<{ moduleName: string, onRequire: RequireInTheMiddle.OnRequireFn }> = [];
219+
220+
constructor() {
221+
this.initialize();
222+
}
223+
224+
initialize() {
225+
RequireInTheMiddle(
226+
// Intercept all `require` calls; we will filter the matching ones below
227+
null,
228+
{ internals: true },
229+
(exports, name, baseDir) => {
230+
const matches = this._modulesToHook.filter(({ moduleName: hookedName }) => {
231+
return name === hookedName || name.startsWith(hookedName + path.sep);
232+
});
233+
234+
for (const { onRequire } of matches) {
235+
exports = onRequire(exports, name, baseDir);
236+
}
237+
238+
return exports;
239+
}
240+
);
241+
}
242+
243+
register(moduleName: string, onRequire: RequireInTheMiddle.OnRequireFn) {
244+
this._modulesToHook.push({ moduleName, onRequire });
245+
}
246+
}
247+
248+
const requireInTheMiddleSingleton = new RequireInTheMiddleSingleton();

0 commit comments

Comments
 (0)