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

fix(instr-express): fix handler patching for already patched router #2294

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,11 @@ export class ExpressInstrumentation extends InstrumentationBase {
// some properties holding metadata and state so we need to proxy them
// through through patched function
// ref: https://github.com/open-telemetry/opentelemetry-js-contrib/issues/1950
Object.keys(original).forEach(key => {
// Also some apps/libs do their own patching before OTEL and have these properties
// in the proptotype. So we use a `for...in` loop to get own properties and also
// any enumerable prop in the prototype chain
// ref: https://github.com/open-telemetry/opentelemetry-js-contrib/issues/2271
for (const key in original) {
Object.defineProperty(patched, key, {
get() {
return original[key];
Expand All @@ -324,8 +328,7 @@ export class ExpressInstrumentation extends InstrumentationBase {
original[key] = value;
},
});
});

}
return patched;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,47 @@ describe('ExpressInstrumentation', () => {
}
);
});

it('should keep the handle properties even if router is patched before instrumentation does it', async () => {
const rootSpan = tracer.startSpan('rootSpan');
let routerLayer: { name: string; handle: { stack: any[] } };

const expressApp = express();
const router = express.Router();
const CustomRouter: (...p: Parameters<typeof router>) => void = (
req,
res,
next
) => router(req, res, next);
router.use('/:slug', (req, res, next) => {
const stack = req.app._router.stack as any[];
routerLayer = stack.find(router => router.name === 'CustomRouter');
return res.status(200).end('bar');
});
// The patched router now has express router's own properties in its prototype so
// they are not accessible through `Object.keys(...)`
// https://github.com/TryGhost/Ghost/blob/fefb9ec395df8695d06442b6ecd3130dae374d94/ghost/core/core/frontend/web/site.js#L192
Object.setPrototypeOf(CustomRouter, router);
expressApp.use(CustomRouter);

const httpServer = await createServer(expressApp);
server = httpServer.server;
port = httpServer.port;
await context.with(
trace.setSpan(context.active(), rootSpan),
async () => {
const response = await httpRequest.get(
`http://localhost:${port}/foo`
);
assert.strictEqual(response, 'bar');
rootSpan.end();
assert.ok(
routerLayer.handle.stack.length === 1,
'router layer stack is accessible'
);
}
);
});
});

describe('Disabling plugin', () => {
Expand Down