44removed at any time without prior notice.
55"""
66
7- import sys
8- from importlib import import_module
9-
107from sentry_sdk .integrations import DidNotEnable , Integration
11- from sentry_sdk .integrations .opentelemetry .distro import _SentryDistro
12- from sentry_sdk .utils import logger , _get_installed_modules
13- from sentry_sdk ._types import TYPE_CHECKING
8+ from sentry_sdk .integrations .opentelemetry .propagator import SentryPropagator
9+ from sentry_sdk .integrations . opentelemetry . span_processor import SentrySpanProcessor
10+ from sentry_sdk .utils import logger
1411
1512try :
16- from opentelemetry . instrumentation . auto_instrumentation . _load import (
17- _load_instrumentors ,
18- )
13+ from opentelemetry import trace
14+ from opentelemetry . propagate import set_global_textmap
15+ from opentelemetry . sdk . trace import TracerProvider
1916except ImportError :
2017 raise DidNotEnable ("opentelemetry not installed" )
2118
22- if TYPE_CHECKING :
23- from typing import Dict
19+ try :
20+ from opentelemetry .instrumentation .django import DjangoInstrumentor # type: ignore[import-not-found]
21+ except ImportError :
22+ DjangoInstrumentor = None
2423
2524
26- CLASSES_TO_INSTRUMENT = {
27- # A mapping of packages to their entry point class that will be instrumented.
28- # This is used to post-instrument any classes that were imported before OTel
29- # instrumentation took place.
30- "fastapi" : "fastapi.FastAPI" ,
31- "flask" : "flask.Flask" ,
32- # XXX Add a mapping for all instrumentors that patch by replacing a class
25+ CONFIGURABLE_INSTRUMENTATIONS = {
26+ DjangoInstrumentor : {"is_sql_commentor_enabled" : True },
3327}
3428
3529
@@ -44,123 +38,21 @@ def setup_once():
4438 "Use at your own risk."
4539 )
4640
47- original_classes = _record_unpatched_classes ()
48-
49- try :
50- distro = _SentryDistro ()
51- distro .configure ()
52- # XXX This does some initial checks before loading instrumentations
53- # (checks OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, checks version
54- # compat). If we don't want this in the future, we can implement our
55- # own _load_instrumentors (it anyway just iterates over
56- # opentelemetry_instrumentor entry points).
57- _load_instrumentors (distro )
58- except Exception :
59- logger .exception ("[OTel] Failed to auto-initialize OpenTelemetry" )
60-
61- # XXX: Consider whether this is ok to keep and make default.
62- # The alternative is asking folks to follow specific import order for
63- # some integrations (sentry_sdk.init before you even import Flask, for
64- # instance).
65- try :
66- _patch_remaining_classes (original_classes )
67- except Exception :
68- logger .exception (
69- "[OTel] Failed to post-patch instrumented classes. "
70- "You might have to make sure sentry_sdk.init() is called before importing anything else."
71- )
41+ _setup_sentry_tracing ()
42+ # _setup_instrumentors()
7243
7344 logger .debug ("[OTel] Finished setting up OpenTelemetry integration" )
7445
7546
76- def _record_unpatched_classes ():
77- # type: () -> Dict[str, type]
78- """
79- Keep references to classes that are about to be instrumented.
80-
81- Used to search for unpatched classes after the instrumentation has run so
82- that they can be patched manually.
83- """
84- installed_packages = _get_installed_modules ()
85-
86- original_classes = {}
87-
88- for package , orig_path in CLASSES_TO_INSTRUMENT .items ():
89- if package in installed_packages :
90- try :
91- original_cls = _import_by_path (orig_path )
92- except (AttributeError , ImportError ):
93- logger .debug ("[OTel] Failed to import %s" , orig_path )
94- continue
95-
96- original_classes [package ] = original_cls
97-
98- return original_classes
99-
100-
101- def _patch_remaining_classes (original_classes ):
102- # type: (Dict[str, type]) -> None
103- """
104- Best-effort attempt to patch any uninstrumented classes in sys.modules.
105-
106- This enables us to not care about the order of imports and sentry_sdk.init()
107- in user code. If e.g. the Flask class had been imported before sentry_sdk
108- was init()ed (and therefore before the OTel instrumentation ran), it would
109- not be instrumented. This function goes over remaining uninstrumented
110- occurrences of the class in sys.modules and replaces them with the
111- instrumented class.
112-
113- Since this is looking for exact matches, it will not work in some scenarios
114- (e.g. if someone is not using the specific class explicitly, but rather
115- inheriting from it). In those cases it's still necessary to sentry_sdk.init()
116- before importing anything that's supposed to be instrumented.
117- """
118- # check which classes have actually been instrumented
119- instrumented_classes = {}
120-
121- for package in list (original_classes .keys ()):
122- original_path = CLASSES_TO_INSTRUMENT [package ]
123-
124- try :
125- cls = _import_by_path (original_path )
126- except (AttributeError , ImportError ):
127- logger .debug (
128- "[OTel] Failed to check if class has been instrumented: %s" ,
129- original_path ,
130- )
131- del original_classes [package ]
132- continue
133-
134- if not cls .__module__ .startswith ("opentelemetry." ):
135- del original_classes [package ]
136- continue
137-
138- instrumented_classes [package ] = cls
139-
140- if not instrumented_classes :
141- return
142-
143- # replace occurrences of the original unpatched class in sys.modules
144- for module_name , module in sys .modules .copy ().items ():
145- if (
146- module_name .startswith ("sentry_sdk" )
147- or module_name in sys .builtin_module_names
148- ):
149- continue
150-
151- for package , original_cls in original_classes .items ():
152- for var_name , var in vars (module ).copy ().items ():
153- if var == original_cls :
154- logger .debug (
155- "[OTel] Additionally patching %s from %s" ,
156- original_cls ,
157- module_name ,
158- )
159-
160- setattr (module , var_name , instrumented_classes [package ])
47+ def _setup_sentry_tracing ():
48+ # type: () -> None
49+ provider = TracerProvider ()
50+ provider .add_span_processor (SentrySpanProcessor ())
51+ trace .set_tracer_provider (provider )
52+ set_global_textmap (SentryPropagator ())
16153
16254
163- def _import_by_path ( path ):
164- # type: (str ) -> type
165- parts = path . rsplit ( "." , maxsplit = 1 )
166- return getattr ( import_module ( parts [ 0 ]), parts [ - 1 ] )
55+ def _setup_instrumentors ( ):
56+ # type: () -> None
57+ for instrumentor , kwargs in CONFIGURABLE_INSTRUMENTATIONS . items ():
58+ instrumentor (). instrument ( ** kwargs )
0 commit comments