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

Cherrypy instrumentation #1410

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

TheAnshul756
Copy link
Contributor

Description

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes #1130

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Test A

Does This PR Require a Core Repo Change?

  • No.

Checklist:

See contributing.md for styleguide, changelog guidelines, and more.

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@TheAnshul756 TheAnshul756 requested a review from a team October 27, 2022 10:34
@@ -0,0 +1,140 @@
from email.policy import default
Copy link
Member

Choose a reason for hiding this comment

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

?

)
from opentelemetry import trace, context
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.instrumentation.falcon.version import __version__
Copy link
Member

Choose a reason for hiding this comment

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

?

return _instruments

def _instrument(self, **kwargs):
self._original_cherrypy_application = cherrypy._cptree.Application
Copy link
Member

Choose a reason for hiding this comment

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

There should be a better way to do this. We can't use the private symbols because they are not guaranteed to remain backward compatible.

def _instrument(self, **kwargs):
self._original_cherrypy_application = cherrypy._cptree.Application
cherrypy._cptree.Application = _InstrumentedCherryPyApplication
cherrypy.Application = _InstrumentedCherryPyApplication
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Issue with these hooks are that they needed to be called for every paths that we want to use them for. They do not apply directly to everywhere. source

@@ -0,0 +1,3 @@
_instruments = ("cherrypy >= 1.0",)
Copy link
Member

Choose a reason for hiding this comment

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

Please also pin the upper version, there can be breaking changes b/w major version changes.

span.set_attributes(custom_attributes)

if self.response_hook:
self.response_hook(span, status, response_headers)
Copy link
Member

Choose a reason for hiding this comment

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

Please add tests. If the PR is not ready then please mark it a draft.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey, I'm really sorry. I forgot to mark this PR draft. There's still lot of work required in this PR.

@TheAnshul756 TheAnshul756 marked this pull request as draft October 31, 2022 05:14
@TheAnshul756 TheAnshul756 force-pushed the cherrypy-instrumentation branch from ea77939 to 4933b32 Compare November 28, 2022 09:04
TheAnshul756 and others added 6 commits November 28, 2022 16:32
* added new testcases related to basic rest api calls and spans check for instrumentation

* added tox changes and metrices testcases and fixed review comments

* added metrices testcases and review comments fixed

* added fixes for review comments

Co-authored-by: rahmukhe <rahmukhe@appdynamics.com>
@TheAnshul756
Copy link
Contributor Author

I've came up with three different approaches for instrumentation in cherrypy and I wanted to discuss all three of them with there pros and cons.

Instrumentation with CherryPy Hooks

Unlike other frameworks, CherryPy does not have support for custom middleware. Instead, CherryPy provides some hook points where you can add your functions. Datadog uses this approach for instrumentation. But all the endpoints for which tools need to be used must be decorated by that tool.
Eg.

class Root(object):
    @cherrypy.expose
    @cherrypy.tools.logit()
    def index(self):
        return "hello world"

Because of it, this approach can only be used for manual instrumentation, not auto-instrumentation. Although there are some workarounds that I came up with (like monkey patching the expose function) but they were also not covering all the possible cases (what if the function is not exposed with expose decorator index.exposed = true).

Instrumentation with CherryPy Publisher Subscriber

CherryPy’s backbone consists of a bus system implementing a simple publish/subscribe messaging pattern. Simply put, in CherryPy everything is controlled via that bus.

Various channels are available in CherryPy, but only two were helpful to us, i.e. before_request and after_request. Scout Python APM Agent use this approach of instrumentation. But the challenge with this approach is the before_request channel triggers at a very early stage of the request handling cycle, even before the request object parses the environ dictionary from the HTTP message, because of which we can not do much in before_request (including span creation). Thus this approach leaves us with a significant limitation.

Instrumentation by Patching CherryPy Application class

This is how we have instrumented various frameworks, and this approach can also be done here. New Relics and Appdynamics agent also uses this approach. The issue with this approach is Application class is implemented in _cptree module in CherryPy and imported in __init__ module. We can patch the class imported in __init__, but this will not enable instrumentation as CherryPy creates the object of the Application class in _cptree and uses the Application from that module directly, which forces us to instrument Application class both in __init__ and _cptree. I have used this approach for instrumenting in my PR but @srikanthccv believes

We can't use the private symbols because they are not guaranteed to remain backwards compatible.

I've implemented all three approaches and tested them to understand their pros and cons. The first two approaches will also come with other challenges, such as handling exceptions. And the third approach, although being backward incompatible, is tried and tested and guarantees to work.
I need help knowing what to do next and which approach to proceed with. Also, if anyone has other ideas, I'm more than interested to know.

@srikanthccv
Copy link
Member

This is how we have instrumented various frameworks, and this approach can also be done here.

I am not against the idea of patching the Application class but I don't think it is a good idea to patch the private/internals of the framework.

And the third approach, although being backward incompatible, is tried and tested and guarantees to work.

What is guaranteed here?

Overall, I don't think it's a good idea to patch the private/internals of the library. We have seen this fail with a few instrumentations, so they either had to be pinned to some minor version which works or rewritten to not patch private symbols and get it to work in general. I would like to hear others thoughts on this @open-telemetry/opentelemetry-python-contrib-approvers .

rahulmukherjee68 and others added 3 commits December 6, 2022 14:20
* added new testcases related to basic rest api calls and spans check for instrumentation

* added tox changes and metrices testcases and fixed review comments

* added metrices testcases and review comments fixed

* added fixes for review comments

* added custom header testcases for instrumentation

* removed print statements

* added return statement to cherrypy app

Co-authored-by: rahmukhe <rahmukhe@appdynamics.com>
@TheAnshul756
Copy link
Contributor Author

What is guaranteed here?

Sorry for the misunderstanding. What I mean by 'guarantee' here is that with other approaches, some loopholes could be there with instrumentation, like some exceptions could be missed or the way the endpoints are exposed could affect the instrumentation. And this approach covers all the possible cases.

I am not against the idea of patching the Application class but I don't think it is a good idea to patch the private/internals of the framework.

I understand your concern, which is why I tried instrumenting with other approaches, but nothing seems to work completely unless someone has a better idea.

@@ -0,0 +1,40 @@
OpenTelemetry CherryPy Tracing
Copy link
Member

Choose a reason for hiding this comment

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

Please add an example how to use this instrumentation

from typing import Collection

from opentelemetry.util.http import parse_excluded_urls, get_excluded_urls, get_traced_request_attrs
import cherrypy
Copy link
Member

Choose a reason for hiding this comment

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

organize the import statement properly



class CherryPyInstrumentor(BaseInstrumentor):
"""An instrumentor for FastAPI
Copy link
Member

Choose a reason for hiding this comment

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

Change the docstring from FastAPI to CherryPy. Also please add an example how to use it

activation.__exit__(None, None, None)
else:
activation.__exit__(
type(exc),
Copy link
Member

Choose a reason for hiding this comment

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

you should use exception instead of exc just to make it more readable

for key, _ in not_expected.items():
self.assertNotIn(key, server_span.attributes)

class TestNonRecordingSpanWithCustomHeaders(TestBase, helper.CPWebCase):
Copy link
Member

Choose a reason for hiding this comment

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

why dont you inherit this class from the TestCherryPyBase and override all functions? This will remove some redundant code

@sanketmehta28
Copy link
Member

Also resolve the build checks

@TheAnshul756
Copy link
Contributor Author

Overall, I don't think it's a good idea to patch the private/internals of the library. We have seen this fail with a few instrumentations, so they either had to be pinned to some minor version which works or rewritten to not patch private symbols and get it to work in general. I would like to hear others thoughts on this.

@open-telemetry/opentelemetry-python-contrib-approvers Please share your thoughts on this.

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.

Auto Instrumentation support for CherryPy Python Framework
4 participants