Skip to content

Commit ce911ed

Browse files
sentrivanaarjenzorgdoc
authored andcommitted
feat(otel): Autoinstrumentation skeleton (getsentry#3143)
Expand the POTel PoC's autoinstrumentation capabilities. This change allows us to: - install and enable all available instrumentations by default - further configure instrumentations that accept optional arguments
1 parent 2b56452 commit ce911ed

File tree

9 files changed

+279
-55
lines changed

9 files changed

+279
-55
lines changed

.github/workflows/test-integrations-miscellaneous.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ jobs:
5050
run: |
5151
set -x # print commands that are executed
5252
./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry-latest"
53+
- name: Test potel latest
54+
run: |
55+
set -x # print commands that are executed
56+
./scripts/runtox.sh "py${{ matrix.python-version }}-potel-latest"
5357
- name: Test pure_eval latest
5458
run: |
5559
set -x # print commands that are executed
@@ -81,7 +85,7 @@ jobs:
8185
strategy:
8286
fail-fast: false
8387
matrix:
84-
python-version: ["3.6","3.7","3.8","3.9","3.11","3.12"]
88+
python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12"]
8589
# python3.6 reached EOL and is no longer being supported on
8690
# new versions of hosted runners on Github Actions
8791
# ubuntu-20.04 is the last version that supported python3.6
@@ -106,6 +110,10 @@ jobs:
106110
run: |
107111
set -x # print commands that are executed
108112
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-opentelemetry"
113+
- name: Test potel pinned
114+
run: |
115+
set -x # print commands that are executed
116+
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-potel"
109117
- name: Test pure_eval pinned
110118
run: |
111119
set -x # print commands that are executed

scripts/split-tox-gh-actions/split-tox-gh-actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
"Miscellaneous": [
125125
"loguru",
126126
"opentelemetry",
127+
"potel",
127128
"pure_eval",
128129
"trytond",
129130
],

sentry_sdk/client.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,13 @@ def _capture_envelope(envelope):
358358
"[OTel] Enabling experimental OTel-powered performance monitoring."
359359
)
360360
self.options["instrumenter"] = INSTRUMENTER.OTEL
361-
_DEFAULT_INTEGRATIONS.append(
362-
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration",
363-
)
361+
if (
362+
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration"
363+
not in _DEFAULT_INTEGRATIONS
364+
):
365+
_DEFAULT_INTEGRATIONS.append(
366+
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration",
367+
)
364368

365369
self.integrations = setup_integrations(
366370
self.options["integrations"],
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
IMPORTANT: The contents of this file are part of a proof of concept and as such
3+
are experimental and not suitable for production use. They may be changed or
4+
removed at any time without prior notice.
5+
"""
6+
7+
from sentry_sdk.integrations import DidNotEnable
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
11+
from sentry_sdk._types import TYPE_CHECKING
12+
13+
try:
14+
from opentelemetry import trace # type: ignore
15+
from opentelemetry.instrumentation.distro import BaseDistro # type: ignore
16+
from opentelemetry.propagate import set_global_textmap # type: ignore
17+
from opentelemetry.sdk.trace import TracerProvider # type: ignore
18+
except ImportError:
19+
raise DidNotEnable("opentelemetry not installed")
20+
21+
try:
22+
from opentelemetry.instrumentation.django import DjangoInstrumentor # type: ignore
23+
except ImportError:
24+
DjangoInstrumentor = None
25+
26+
try:
27+
from opentelemetry.instrumentation.flask import FlaskInstrumentor # type: ignore
28+
except ImportError:
29+
FlaskInstrumentor = None
30+
31+
if TYPE_CHECKING:
32+
# XXX pkg_resources is deprecated, there's a PR to switch to importlib:
33+
# https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2181
34+
# we should align this when the PR gets merged
35+
from pkg_resources import EntryPoint
36+
from typing import Any
37+
38+
39+
CONFIGURABLE_INSTRUMENTATIONS = {
40+
DjangoInstrumentor: {"is_sql_commentor_enabled": True},
41+
FlaskInstrumentor: {"enable_commenter": True},
42+
}
43+
44+
45+
class _SentryDistro(BaseDistro): # type: ignore[misc]
46+
def _configure(self, **kwargs):
47+
# type: (Any) -> None
48+
provider = TracerProvider()
49+
provider.add_span_processor(SentrySpanProcessor())
50+
trace.set_tracer_provider(provider)
51+
set_global_textmap(SentryPropagator())
52+
53+
def load_instrumentor(self, entry_point, **kwargs):
54+
# type: (EntryPoint, Any) -> None
55+
instrumentor = entry_point.load()
56+
57+
if instrumentor in CONFIGURABLE_INSTRUMENTATIONS:
58+
for key, value in CONFIGURABLE_INSTRUMENTATIONS[instrumentor].items():
59+
kwargs[key] = value
60+
61+
instrumentor().instrument(**kwargs)
62+
logger.debug(
63+
"[OTel] %s instrumented (%s)",
64+
entry_point.name,
65+
", ".join([f"{k}: {v}" for k, v in kwargs.items()]),
66+
)

sentry_sdk/integrations/opentelemetry/integration.py

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,14 @@
88
from importlib import import_module
99

1010
from sentry_sdk.integrations import DidNotEnable, Integration
11-
from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
12-
from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
11+
from sentry_sdk.integrations.opentelemetry.distro import _SentryDistro
1312
from sentry_sdk.utils import logger, _get_installed_modules
1413
from sentry_sdk._types import TYPE_CHECKING
1514

1615
try:
17-
from opentelemetry import trace # type: ignore
1816
from opentelemetry.instrumentation.auto_instrumentation._load import ( # type: ignore
19-
_load_distro,
2017
_load_instrumentors,
2118
)
22-
from opentelemetry.propagate import set_global_textmap # type: ignore
23-
from opentelemetry.sdk.trace import TracerProvider # type: ignore
2419
except ImportError:
2520
raise DidNotEnable("opentelemetry not installed")
2621

@@ -34,6 +29,7 @@
3429
# instrumentation took place.
3530
"fastapi": "fastapi.FastAPI",
3631
"flask": "flask.Flask",
32+
# XXX Add a mapping for all instrumentors that patch by replacing a class
3733
}
3834

3935

@@ -51,12 +47,21 @@ def setup_once():
5147
original_classes = _record_unpatched_classes()
5248

5349
try:
54-
distro = _load_distro()
50+
distro = _SentryDistro()
5551
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).
5657
_load_instrumentors(distro)
5758
except Exception:
5859
logger.exception("[OTel] Failed to auto-initialize OpenTelemetry")
5960

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).
6065
try:
6166
_patch_remaining_classes(original_classes)
6267
except Exception:
@@ -65,8 +70,6 @@ def setup_once():
6570
"You might have to make sure sentry_sdk.init() is called before importing anything else."
6671
)
6772

68-
_setup_sentry_tracing()
69-
7073
logger.debug("[OTel] Finished setting up OpenTelemetry integration")
7174

7275

@@ -161,14 +164,3 @@ def _import_by_path(path):
161164
# type: (str) -> type
162165
parts = path.rsplit(".", maxsplit=1)
163166
return getattr(import_module(parts[0]), parts[-1])
164-
165-
166-
def _setup_sentry_tracing():
167-
# type: () -> None
168-
provider = TracerProvider()
169-
170-
provider.add_span_processor(SentrySpanProcessor())
171-
172-
trace.set_tracer_provider(provider)
173-
174-
set_global_textmap(SentryPropagator())

setup.py

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,59 @@ def get_file_text(file_name):
6666
"openai": ["openai>=1.0.0", "tiktoken>=0.3.0"],
6767
"opentelemetry": ["opentelemetry-distro>=0.35b0"],
6868
"opentelemetry-experimental": [
69-
"opentelemetry-distro~=0.40b0",
70-
"opentelemetry-instrumentation-aiohttp-client~=0.40b0",
71-
"opentelemetry-instrumentation-django~=0.40b0",
72-
"opentelemetry-instrumentation-fastapi~=0.40b0",
73-
"opentelemetry-instrumentation-flask~=0.40b0",
74-
"opentelemetry-instrumentation-requests~=0.40b0",
75-
"opentelemetry-instrumentation-sqlite3~=0.40b0",
76-
"opentelemetry-instrumentation-urllib~=0.40b0",
69+
# There's an umbrella package called
70+
# opentelemetry-contrib-instrumentations that installs all
71+
# available instrumentation packages, however it's broken in recent
72+
# versions (after 0.41b0), see
73+
# https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2053
74+
"opentelemetry-instrumentation-aio-pika==0.46b0",
75+
"opentelemetry-instrumentation-aiohttp-client==0.46b0",
76+
# "opentelemetry-instrumentation-aiohttp-server==0.46b0", # broken package
77+
"opentelemetry-instrumentation-aiopg==0.46b0",
78+
"opentelemetry-instrumentation-asgi==0.46b0",
79+
"opentelemetry-instrumentation-asyncio==0.46b0",
80+
"opentelemetry-instrumentation-asyncpg==0.46b0",
81+
"opentelemetry-instrumentation-aws-lambda==0.46b0",
82+
"opentelemetry-instrumentation-boto==0.46b0",
83+
"opentelemetry-instrumentation-boto3sqs==0.46b0",
84+
"opentelemetry-instrumentation-botocore==0.46b0",
85+
"opentelemetry-instrumentation-cassandra==0.46b0",
86+
"opentelemetry-instrumentation-celery==0.46b0",
87+
"opentelemetry-instrumentation-confluent-kafka==0.46b0",
88+
"opentelemetry-instrumentation-dbapi==0.46b0",
89+
"opentelemetry-instrumentation-django==0.46b0",
90+
"opentelemetry-instrumentation-elasticsearch==0.46b0",
91+
"opentelemetry-instrumentation-falcon==0.46b0",
92+
"opentelemetry-instrumentation-fastapi==0.46b0",
93+
"opentelemetry-instrumentation-flask==0.46b0",
94+
"opentelemetry-instrumentation-grpc==0.46b0",
95+
"opentelemetry-instrumentation-httpx==0.46b0",
96+
"opentelemetry-instrumentation-jinja2==0.46b0",
97+
"opentelemetry-instrumentation-kafka-python==0.46b0",
98+
"opentelemetry-instrumentation-logging==0.46b0",
99+
"opentelemetry-instrumentation-mysql==0.46b0",
100+
"opentelemetry-instrumentation-mysqlclient==0.46b0",
101+
"opentelemetry-instrumentation-pika==0.46b0",
102+
"opentelemetry-instrumentation-psycopg==0.46b0",
103+
"opentelemetry-instrumentation-psycopg2==0.46b0",
104+
"opentelemetry-instrumentation-pymemcache==0.46b0",
105+
"opentelemetry-instrumentation-pymongo==0.46b0",
106+
"opentelemetry-instrumentation-pymysql==0.46b0",
107+
"opentelemetry-instrumentation-pyramid==0.46b0",
108+
"opentelemetry-instrumentation-redis==0.46b0",
109+
"opentelemetry-instrumentation-remoulade==0.46b0",
110+
"opentelemetry-instrumentation-requests==0.46b0",
111+
"opentelemetry-instrumentation-sklearn==0.46b0",
112+
"opentelemetry-instrumentation-sqlalchemy==0.46b0",
113+
"opentelemetry-instrumentation-sqlite3==0.46b0",
114+
"opentelemetry-instrumentation-starlette==0.46b0",
115+
"opentelemetry-instrumentation-system-metrics==0.46b0",
116+
"opentelemetry-instrumentation-threading==0.46b0",
117+
"opentelemetry-instrumentation-tornado==0.46b0",
118+
"opentelemetry-instrumentation-tortoiseorm==0.46b0",
119+
"opentelemetry-instrumentation-urllib==0.46b0",
120+
"opentelemetry-instrumentation-urllib3==0.46b0",
121+
"opentelemetry-instrumentation-wsgi==0.46b0",
77122
],
78123
"pure_eval": ["pure_eval", "executing", "asttokens"],
79124
"pymongo": ["pymongo>=3.1"],

tests/conftest.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121

2222
import sentry_sdk
2323
from sentry_sdk.envelope import Envelope
24-
from sentry_sdk.integrations import _processed_integrations # noqa: F401
25-
from sentry_sdk.profiler.transaction_profiler import teardown_profiler
24+
from sentry_sdk.integrations import ( # noqa: F401
25+
_DEFAULT_INTEGRATIONS,
26+
_processed_integrations,
27+
)
28+
from sentry_sdk.profiler import teardown_profiler
2629
from sentry_sdk.profiler.continuous_profiler import teardown_continuous_profiler
2730
from sentry_sdk.transport import Transport
2831
from sentry_sdk.utils import reraise
@@ -169,7 +172,13 @@ def reset_integrations():
169172
with a clean slate to ensure monkeypatching works well,
170173
but this also means some other stuff will be monkeypatched twice.
171174
"""
172-
global _processed_integrations
175+
global _DEFAULT_INTEGRATIONS, _processed_integrations
176+
try:
177+
_DEFAULT_INTEGRATIONS.remove(
178+
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration"
179+
)
180+
except ValueError:
181+
pass
173182
_processed_integrations.clear()
174183

175184

0 commit comments

Comments
 (0)