-
Notifications
You must be signed in to change notification settings - Fork 549
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 instrumentation results in routes with no stack, crashing Ghost #2271
Comments
Debugging it further, this happens because the express instrumentation wraps the opentelemetry-js-contrib/plugins/node/opentelemetry-instrumentation-express/src/instrumentation.ts Line 176 in 3e863cf
Express adds properties to the By returning a new wrapped function, these properties are lost. I verified by placing a breakpoint in the otel code above around the original.stack // [Layer]
wrapped.stack // undefined
wrapped.__original.stack // [Layer] Right now just to solve this internally, I've decided to hack around it by having the wrapped function inherit the original one, here's a diff --git a/node_modules/@opentelemetry/instrumentation-express/build/src/instrumentation.js b/node_modules/@opentelemetry/instrumentation-express/build/src/instrumentation.js
index 3e850bd..8b6eaf1 100644
--- a/node_modules/@opentelemetry/instrumentation-express/build/src/instrumentation.js
+++ b/node_modules/@opentelemetry/instrumentation-express/build/src/instrumentation.js
@@ -123,7 +123,7 @@ class ExpressInstrumentation extends instrumentation_1.InstrumentationBase {
this._wrap(layer, 'handle', (original) => {
if (original.length === 4)
return original;
- return function (req, res) {
+ const wrapped = function (req, res) {
(0, utils_1.storeLayerPath)(req, layerPath);
const route = req[internal_types_1._LAYERS_STORE_PROPERTY]
.filter(path => path !== '/' && path !== '/*')
@@ -209,6 +209,22 @@ class ExpressInstrumentation extends instrumentation_1.InstrumentationBase {
}
return result;
};
+
+ // express sets multiple properties on the handler *function*. By
+ // returning a new function, we eliminate possible access to the
+ // previously set properties. As a workaround, set the original as
+ // the wrapped's prototype. This allows `get`s to go through. It's
+ // not foolprood, since `set`s will still only apply to the wrapped
+ // function.
+ //
+ // For a concrete example, try accessing:
+ //
+ // original.stack
+ // vs.
+ // wrapped.stack
+ Object.setPrototypeOf(wrapped, original);
+
+ return wrapped;
});
}
_getSpanName(info, defaultName) { Right now it's working, but it's a little iffy. I'm more than open to PR this, lmk what you think. Thanks |
Hi @Zirak thanks for the detailed report. Since this is related to the express instrumentation, I'll transfer this issue to the contrib repo. |
@JamieDanielson would you mind taking a look at this? 🙂 |
At a glance, this seems like it will be resolved with the changes in #2137 , which was created to fix #1950 . The short description includes this note: "The patched function now exposes same properties proxying the ones from the original handle function." This update will be available in the next release. |
Hmm... actually I'm testing out the changes and it doesn't seem to resolve this issue. When running this repro app in debug mode, I can see the stack on @Zirak thank you again for the detailed report and reproducer. If you are open to it, a PR would be appreciated on this one! |
In some local testing I was able to add Object.keys(original).forEach(key => {
Object.defineProperty(patched, key, {
get() {
return original[key];
},
set(value) {
original[key] = value;
},
});
});
Object.setPrototypeOf(patched, original);
return patched; |
Thanks for the follow-up, and talk about timing with the ongoing express patch! Since it was released I able to install @opentelemetry/[email protected] and reproduce that the bug is still happening. After some debugging I think I understand why: These properties are not > original.stack
[Layer]
> Object.keys(original)
[]
> original.hasOwnProperty('stack')
false
> Object.getPrototypeOf(original).hasOwnProperty('stack')
true
> Object.keys(Object.getPrototypeOf(original))
['params', '_params', 'caseSensitive', 'mergeParams', 'strict', 'stack'] Express internals confuse me all the way to the 7th circle, so there might be flows in which these keys do exist on The reason my (naive) Seeing the above, I have three suggestions in mind:
3 is I think most "pure" solution, in that it emulates existing behaviour, but comes with two tradeoffs:
|
OTEL's handler patching occurs when So here's my 2cts on the options:
This is a good question. When unwrapping the handlers are still the patched ones. I wonder if we should keep track of them ti patch/unpatch accordingly 🤔 |
Note: To get a callable function with the tracing logic we need to create the proxy using the patched function as target const patched = funtcion (req, res) { ... };
return new Proxy(patched, {
get (target, prop) { return original[prop]; },
set (target, prop, val) { original[prop] = val; },
}); otherwise the returned function would have no tracing logic at all. |
What happened?
Steps to Reproduce
Given the following
index.js
, which imitates Ghost's routing:Run twice:
Expected Result
Both runs print a router stack like:
Actual Result
When running with otel, the router is
undefined
, causing shenanigans and a server 500:Additional Details
It's also possible to replicate this with Ghost itself. Running Ghost with opentelemetry and visiting http://localhost:2368/about/ results in a 500.
I've yet to successfully debug exactly where this originates.
OpenTelemetry Setup Code
No response
package.json
No response
Relevant log output
No response
The text was updated successfully, but these errors were encountered: