Repository: open-telemetry/opentelemetry-python-contrib

Issue:
Using opentelemetry-instrument breaks Sentry SDK
**Describe your environment** 
* Python 3.6.8
* Centos 7
* Supervisord
* Sentry_sdk 1.4.3
* Environment Variables Set:
** OTEL_SERVICE_NAME=testthing
** SENTRY_URL=<secure sentry url>

**Steps to reproduce**
Using the below proof of concept python script:
```
import os
import sentry_sdk


print('about to run')
if os.environ.get('SENTRY_URL', None):      # pragma: unreachable
    sentry_sdk.init(os.environ['SENTRY_URL'])
    print('sentry run')
```

When the script is executed as `python3 proof.py` the output is:
```
about to run
sentry run
```

Though when executing `opentelemetry-instrument python3 proof.py` the result is:
```
You are using Python 3.6. This version does not support timestamps with nanosecond precision and the OpenTelemetry SDK will use millisecond precision instead. Please refer to PEP 564 for more information. Please upgrade to Python 3.7 or newer to use nanosecond precision.
about to run
Traceback (most recent call last):
  File "proof.py", line 7, in <module>
    sentry_sdk.init(os.environ['SENTRY_URL'])
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/hub.py", line 105, in _init
    client = Client(*args, **kwargs)  # type: ignore
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/client.py", line 86, in __init__
    self._init_impl()
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/client.py", line 124, in _init_impl
    "auto_enabling_integrations"
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/integrations/__init__.py", line 120, in setup_integrations
    type(integration).setup_once()
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/integrations/falcon.py", line 113, in setup_once
    _patch_handle_exception()
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/integrations/falcon.py", line 139, in _patch_handle_exception
    original_handle_exception = falcon.API._handle_exception
AttributeError: 'functools.partial' object has no attribute '_handle_exception'
```

**What is the expected behavior?**
Ideally sentry should execute without errors and provide the following response from the sample script:
```
about to run
sentry run
```


**What is the actual behavior?**
Recieved the error:
```
You are using Python 3.6. This version does not support timestamps with nanosecond precision and the OpenTelemetry SDK will use millisecond precision instead. Please refer to PEP 564 for more information. Please upgrade to Python 3.7 or newer to use nanosecond precision.
about to run
Traceback (most recent call last):
  File "proof.py", line 7, in <module>
    sentry_sdk.init(os.environ['SENTRY_URL'])
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/hub.py", line 105, in _init
    client = Client(*args, **kwargs)  # type: ignore
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/client.py", line 86, in __init__
    self._init_impl()
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/client.py", line 124, in _init_impl
    "auto_enabling_integrations"
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/integrations/__init__.py", line 120, in setup_integrations
    type(integration).setup_once()
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/integrations/falcon.py", line 113, in setup_once
    _patch_handle_exception()
  File "/usr/local/lib/python3.6/site-packages/sentry_sdk/integrations/falcon.py", line 139, in _patch_handle_exception
    original_handle_exception = falcon.API._handle_exception
AttributeError: 'functools.partial' object has no attribute '_handle_exception'
```



Discussion:
Looks like an issue with Falcon. Which version of Falcon are you using? Please share output of `pip freeze`
aiocontextvars==0.2.2
apispec==5.1.1
ari==0.1.3
backoff==1.10.0
boto3==1.20.46
botocore==1.23.46
certifi==2021.10.8
charset-normalizer==2.0.11
contextvars==2.4
dataclasses==0.8
datadog==0.42.0
Deprecated==1.2.13
dnspython==2.1.0
falcon==2.0.0
googleapis-common-protos==1.54.0
grpcio==1.43.0
idna==3.3
immutables==0.16
inflection==0.5.1
Jinja2==3.0.3
jmespath==0.10.0
MarkupSafe==2.0.1
marshmallow==3.14.1
opentelemetry-api==1.9.1
opentelemetry-exporter-jaeger-thrift==1.9.1
opentelemetry-exporter-otlp-proto-grpc==1.9.1
opentelemetry-instrumentation==0.28b1
opentelemetry-instrumentation-aws-lambda==0.28b1
opentelemetry-instrumentation-dbapi==0.28b1
opentelemetry-instrumentation-falcon==0.28b1
opentelemetry-instrumentation-grpc==0.28b1
opentelemetry-instrumentation-logging==0.28b1
opentelemetry-instrumentation-redis==0.28b1
opentelemetry-instrumentation-requests==0.28b1
opentelemetry-instrumentation-sqlite3==0.28b1
opentelemetry-instrumentation-urllib==0.28b1
opentelemetry-instrumentation-urllib3==0.28b1
opentelemetry-instrumentation-wsgi==0.28b1
opentelemetry-propagator-aws-xray==1.0.1
opentelemetry-propagator-b3==1.9.1
opentelemetry-proto==1.9.1
opentelemetry-sdk==1.9.1
opentelemetry-semantic-conventions==0.28b1
opentelemetry-util-http==0.28b1
panoramisk==1.4
protobuf==3.19.4
PyJWT==2.3.0
python-dateutil==2.8.2
PyYAML==6.0
redis==3.5.3
requests==2.27.1
s3transfer==0.5.0
sentry-sdk==1.4.3
six==1.16.0
splunk-opentelemetry==1.4.1
supervisor==4.2.2
swaggerpy==0.2.1
thrift==0.15.0
typing_extensions==4.0.1
urllib3==1.26.8
uWSGI==2.0.20
websocket-client==1.2.3
wrapt==1.13.3
I should mention that the application in question doesn't use Falcon, there is a second application that runs on the same instance that does though, and that is executed using `uwsgi` which runs without any issues and pushes telemetry data.
The issue is here: https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py#L151

We wrap the Falcon API class into a partial function so when it is initialized, it receives some default params. This does not break the public Falcon API as it remains a callable but it breaks Sentry because Sentry also seems to patch Falcon and assumes certain knowledge about Falcon internals. 

I think we can change our implementation so instead of using a partial we use an actual class and pass in the kwargs some other way. 
As a side note, using a virtual environment (which is the fix we are going for in the interim) should solve this problem as if you are using falcon you would likely be running your application via uwsgi which seems to not suffer this issue

Resolving patch:
diff --git a/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py b/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py
--- a/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py
+++ b/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py
@@ -90,7 +90,6 @@ def response_hook(span, req, resp):
 ---
 """
 
-from functools import partial
 from logging import getLogger
 from sys import exc_info
 from typing import Collection
@@ -135,48 +134,32 @@ def response_hook(span, req, resp):
     _instrument_app = "API"
 
 
-class FalconInstrumentor(BaseInstrumentor):
-    # pylint: disable=protected-access,attribute-defined-outside-init
-    """An instrumentor for falcon.API
-
-    See `BaseInstrumentor`
-    """
-
-    def instrumentation_dependencies(self) -> Collection[str]:
-        return _instruments
-
-    def _instrument(self, **kwargs):
-        self._original_falcon_api = getattr(falcon, _instrument_app)
-        setattr(
-            falcon, _instrument_app, partial(_InstrumentedFalconAPI, **kwargs)
-        )
-
-    def _uninstrument(self, **kwargs):
-        setattr(falcon, _instrument_app, self._original_falcon_api)
-
-
 class _InstrumentedFalconAPI(getattr(falcon, _instrument_app)):
     def __init__(self, *args, **kwargs):
+        otel_opts = kwargs.pop("_otel_opts", {})
+
         # inject trace middleware
         middlewares = kwargs.pop("middleware", [])
-        tracer_provider = kwargs.pop("tracer_provider", None)
+        tracer_provider = otel_opts.pop("tracer_provider", None)
         if not isinstance(middlewares, (list, tuple)):
             middlewares = [middlewares]
 
-        self._tracer = trace.get_tracer(__name__, __version__, tracer_provider)
+        self._otel_tracer = trace.get_tracer(
+            __name__, __version__, tracer_provider
+        )
 
         trace_middleware = _TraceMiddleware(
-            self._tracer,
-            kwargs.pop(
+            self._otel_tracer,
+            otel_opts.pop(
                 "traced_request_attributes", get_traced_request_attrs("FALCON")
             ),
-            kwargs.pop("request_hook", None),
-            kwargs.pop("response_hook", None),
+            otel_opts.pop("request_hook", None),
+            otel_opts.pop("response_hook", None),
         )
         middlewares.insert(0, trace_middleware)
         kwargs["middleware"] = middlewares
 
-        self._excluded_urls = get_excluded_urls("FALCON")
+        self._otel_excluded_urls = get_excluded_urls("FALCON")
         super().__init__(*args, **kwargs)
 
     def _handle_exception(
@@ -190,13 +173,13 @@ def _handle_exception(
 
     def __call__(self, env, start_response):
         # pylint: disable=E1101
-        if self._excluded_urls.url_disabled(env.get("PATH_INFO", "/")):
+        if self._otel_excluded_urls.url_disabled(env.get("PATH_INFO", "/")):
             return super().__call__(env, start_response)
 
         start_time = _time_ns()
 
         span, token = _start_internal_or_server_span(
-            tracer=self._tracer,
+            tracer=self._otel_tracer,
             span_name=otel_wsgi.get_default_span_name(env),
             start_time=start_time,
             context_carrier=env,
@@ -321,3 +304,27 @@ def process_response(
 
         if self._response_hook:
             self._response_hook(span, req, resp)
+
+
+class FalconInstrumentor(BaseInstrumentor):
+    # pylint: disable=protected-access,attribute-defined-outside-init
+    """An instrumentor for falcon.API
+
+    See `BaseInstrumentor`
+    """
+
+    def instrumentation_dependencies(self) -> Collection[str]:
+        return _instruments
+
+    def _instrument(self, **opts):
+        self._original_falcon_api = getattr(falcon, _instrument_app)
+
+        class FalconAPI(_InstrumentedFalconAPI):
+            def __init__(self, *args, **kwargs):
+                kwargs["_otel_opts"] = opts
+                super().__init__(*args, **kwargs)
+
+        setattr(falcon, _instrument_app, FalconAPI)
+
+    def _uninstrument(self, **kwargs):
+        setattr(falcon, _instrument_app, self._original_falcon_api)


Test patch: