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

Falcon 3 support #644

Merged
merged 32 commits into from
Sep 27, 2021
Merged

Falcon 3 support #644

merged 32 commits into from
Sep 27, 2021

Conversation

adriangb
Copy link
Contributor

@adriangb adriangb commented Sep 1, 2021

No description provided.

@adriangb adriangb requested review from a team, owais and NathanielRN and removed request for a team September 1, 2021 05:47
@adriangb
Copy link
Contributor Author

adriangb commented Sep 1, 2021

@ocelotl take a look when you have a chance. I'm seeing the following failure:

_ ERROR collecting instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py _
1956
ImportError while importing test module '/home/runner/work/opentelemetry-python-contrib/opentelemetry-python-contrib/instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py'.
1957
Hint: make sure your test modules/packages have valid Python names.
1958
Traceback:
1959
/opt/hostedtoolcache/Python/3.9.6/x64/lib/python3.9/importlib/__init__.py:127: in import_module
1960
    return _bootstrap._gcd_import(name[level:], package, level)
1961
test_falcon.py:19: in <module>
1962
    from opentelemetry.instrumentation.falcon import FalconInstrumentor
1963
E   ModuleNotFoundError: No module named 'opentelemetry.instrumentation.falcon'

This doesn't make much sense to me, I didn't change the filenames...

I'm also seeing lint and generate failures. I ran tox generate, it did a bunch of stuff and it's still complaining. It seems like running it locally doesn't agree with CI 🤷 .

Also, CONTRIBUTING.md is still broken 😢

Copy link
Contributor

@owais owais left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Looks good. Left one comment.

@@ -140,11 +140,22 @@ def instrumentation_dependencies(self) -> Collection[str]:
return _instruments

def _instrument(self, **kwargs):
self._original_falcon_api = None
self._original_falcon_app = None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use only one attribute for both 2 and 3, and conditionally read/write falcon.App or falcon.API?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, if you prefer that

@owais
Copy link
Contributor

owais commented Sep 1, 2021

@adriangb what command is that traceback from? I just ran tox -e py39-test-instrumentation-falcon on main and it worked perfectly for me locally.

@owais
Copy link
Contributor

owais commented Sep 1, 2021

@adriangb
Copy link
Contributor Author

adriangb commented Sep 1, 2021

@adriangb what command is that traceback from? I just ran tox -e py39-test-instrumentation-falcon on main and it worked perfectly for me locally.

https://github.com/open-telemetry/opentelemetry-python-contrib/pull/644/checks?check_run_id=3485738088#step:7:1963

@adriangb
Copy link
Contributor Author

adriangb commented Sep 1, 2021

I think you probably need to update these lines as well:

Makes sense, thank you, updated

tox.ini Outdated Show resolved Hide resolved
Co-authored-by: Owais Lone <[email protected]>
tox.ini Outdated Show resolved Hide resolved
tox.ini Outdated Show resolved Hide resolved
@adriangb
Copy link
Contributor Author

adriangb commented Sep 1, 2021

Thanks for the help @owais

Looks like the tests are actually broken now. The tests access a private attribute of falcon.App then double [0] index on it and then accesses __self__. There are no comments as to why all of this is going on. To be honest, I'm pretty shocked that tests are monkey patching private attributes of stuff that was already monkey patched by the instrumentation. monkey patching is bad!. Nested monkey patching is worse. Nested monkey patching of private attributes is guaranteed to break stuff.

@owais
Copy link
Contributor

owais commented Sep 1, 2021

Looks like Falcon changed how it handles middlewares internally so we need to update test code so it can correctly fetch the injected Otel middleware for both 2 and 3.

@adriangb
Copy link
Contributor Author

adriangb commented Sep 1, 2021

Yes, I agree. However I think I think it's unfair to blame this on Falcon: the test breaks because it's fragile, and it's fragile because it accesses private attributes of a 3rd party library. They could have made the change in a patch bump, we should be thankful that they seem to have done it during a major version bump...

So while it may be possible to fix this here, I think the overall approach of monkey patching stuff (in the library, in tests) and accessing private stuff in 3rd party libraries is fundamentally flawed and brittle.

It's also going to be hard to fix this test: I still can't run this locally (because as we've discussed before the local setup instructions are broken; and no one has been able to give me a series of valid steps of cloning repos into repos and installing stuff that works).

It looks like this portion of the code was authored by you (@owais ) and @lonewolf3739. I'd appreciate a walkthrough of why this is being done, what [0][0] is accessing, etc if I'm going to try to fix this test by blindly pushing to CI.

@owais
Copy link
Contributor

owais commented Sep 1, 2021

I'm not blaming anything. I was merely pointing out what needs to be fixed in order to make the test pass again.

I'd appreciate a walkthrough of why this is being done, what [0][0] is accessing, etc if I'm going to try to fix this test by blindly pushing to CI.

Looking at the test code, it need a reference to the injected middleware which can be accessed from a falcon app instance with the help of [0][0] because of how it internally stored references to middlewares. If there is a public API to fetch references to installed middleware, we should use that but I'm not aware of any.

@owais
Copy link
Contributor

owais commented Sep 1, 2021

It's also going to be hard to fix this test: I still can't run this locally (because as we've discussed before the local setup instructions are broken; and no one has been able to give me a series of valid steps of cloning repos into repos and installing stuff that works).

Is there an issue for this I can look at and answer?

@@ -193,7 +211,6 @@ def __call__(self, env, start_response):
env[_ENVIRON_ACTIVATION_KEY] = activation

def _start_response(status, response_headers, *args, **kwargs):
otel_wsgi.add_response_attributes(span, status, response_headers)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a pretty insidious bug that was hard to figure out.

If you look at the middleware, it is already setting span.status. This re sets it. otel_wsgi.add_response_attributes sets the same status code as the middleware, but does not set the description. So after this line executes span.status.description is always None. But none of the tests were checking this, so it was not caught.

But what about test_500 then? Well, in Falcon 2 (but not 3) super().__call__(...) would re-raise the error, triggering the except clause right below this that skips calling _start_response and thus otel_wsgi.add_response_attributes(span, status, response_headers) is never called and span.status.description is carried over from the middleware.

I'll add an assertion for span.status.description to other tests so that this can be avoided in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checks added in 8eda7b6

Comment on lines +181 to +188
def _handle_exception(
self, req, resp, ex, params
): # pylint: disable=C0103
# Falcon 3 does not execute middleware within the context of the exception
# so we capture the exception here and save it into the env dict
_, exc, _ = exc_info()
req.env[_ENVIRON_EXC] = exc
return super()._handle_exception(req, resp, ex, params)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out another way to do this other than accessing this private method, FYI

# Falcon 3 does not execute middleware within the context of the exception
# so we capture the exception here and save it into the env dict
_, exc, _ = exc_info()
req.env[_ENVIRON_EXC] = exc
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using the already defined but previously unused key _ENVIRON_EXC to store the status info.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this works for both 2 and 3?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the _handle_exception also exists in Falcon 2, so it seems to work 😃

Comment on lines +129 to +134
if hasattr(falcon, "App"):
# Falcon 3
_instrument_app = "App"
else:
# Falcon 2
_instrument_app = "API"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I centralized this logic up here so we don't duplicate it below



class _InstrumentedFalconAPI(falcon.API):
class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really dislike using a getattr in a class def, but it works and it avoids a deprecation warning for Falcon 3 users.

@adriangb
Copy link
Contributor Author

@owais this is now working 😄

@adriangb adriangb requested a review from owais September 16, 2021 18:00
@adriangb
Copy link
Contributor Author

I'm happy to discuss any topic but lets please create separate issues or discussions for those and here just focus on getting this PR through

I opened #683 to discuss further

@adriangb
Copy link
Contributor Author

@owais I merged in main. Could we move forward with this? Thanks.

@adriangb
Copy link
Contributor Author

thanks for updating the branch 😄

@owais
Copy link
Contributor

owais commented Sep 25, 2021

LGTM but giving others time to review as well.

@owais owais enabled auto-merge (squash) September 27, 2021 17:35
@owais
Copy link
Contributor

owais commented Sep 27, 2021

@adriangb please resolve conflcits and we'll merge this.

@lzchen
Copy link
Contributor

lzchen commented Sep 27, 2021

@adriangb

Are you interested in becoming a CODEOWNER for the falcon instrumentation package? The responsibilities are outlined here

auto-merge was automatically disabled September 27, 2021 17:43

Head branch was pushed to by a user without write access

@adriangb
Copy link
Contributor Author

@adriangb please resolve conflcits and we'll merge this.

Done 😄 , let's let the tests run and make sure everything is still ✅

Are you interested in becoming a CODEOWNER for the falcon instrumentation package?

At this point in time I have enough OSS work on my hands, but I will certainly consider it. I appreciate the offer.

@lzchen lzchen enabled auto-merge (squash) September 27, 2021 19:44
@lzchen lzchen merged commit 8e0d0e0 into open-telemetry:main Sep 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants