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

feat(otel): Autoinstrumentation skeleton #3143

Merged
merged 22 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7f8f892
feat(otel): Autoinstrumentation skeleton
sentrivana Jun 7, 2024
766b216
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 7, 2024
cd78db4
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 10, 2024
f45dd83
install otel for linting
sentrivana Jun 10, 2024
21d3084
type: ingores
sentrivana Jun 10, 2024
c8a3660
more mypy
sentrivana Jun 10, 2024
363b203
add otel experimental test target
sentrivana Jun 10, 2024
06f9070
change target name
sentrivana Jun 10, 2024
72bd975
rename everywhere maybe
sentrivana Jun 10, 2024
952aad2
rename everywhere maybe
sentrivana Jun 10, 2024
61d17e8
more testing
sentrivana Jun 10, 2024
7a8155b
fix tests
sentrivana Jun 10, 2024
6c76f3e
more potel tests
sentrivana Jun 13, 2024
6e3b4d2
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 13, 2024
a1c79c9
no flask, fastapi with regular otel setup
sentrivana Jun 13, 2024
e328f5b
fix py versions
sentrivana Jun 13, 2024
ea66806
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 19, 2024
fe6f2d2
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 20, 2024
10db687
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 24, 2024
c924b66
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 24, 2024
5204aac
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 25, 2024
8e1ec08
Merge branch 'master' into ivana/potel/autoinstrumentation
sentrivana Jun 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/test-integrations-miscellaneous.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ jobs:
run: |
set -x # print commands that are executed
./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry-latest"
- name: Test potel latest
run: |
set -x # print commands that are executed
./scripts/runtox.sh "py${{ matrix.python-version }}-potel-latest"
- name: Test pure_eval latest
run: |
set -x # print commands that are executed
Expand Down Expand Up @@ -81,7 +85,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.6","3.7","3.8","3.9","3.11","3.12"]
python-version: ["3.6","3.7","3.8","3.9","3.10","3.11","3.12"]
# python3.6 reached EOL and is no longer being supported on
# new versions of hosted runners on Github Actions
# ubuntu-20.04 is the last version that supported python3.6
Expand All @@ -106,6 +110,10 @@ jobs:
run: |
set -x # print commands that are executed
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-opentelemetry"
- name: Test potel pinned
run: |
set -x # print commands that are executed
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-potel"
- name: Test pure_eval pinned
run: |
set -x # print commands that are executed
Expand Down
1 change: 1 addition & 0 deletions scripts/split-tox-gh-actions/split-tox-gh-actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"Miscellaneous": [
"loguru",
"opentelemetry",
"potel",
"pure_eval",
"trytond",
],
Expand Down
10 changes: 7 additions & 3 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,13 @@ def _capture_envelope(envelope):
"[OTel] Enabling experimental OTel-powered performance monitoring."
)
self.options["instrumenter"] = INSTRUMENTER.OTEL
_DEFAULT_INTEGRATIONS.append(
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration",
)
if (
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration"
not in _DEFAULT_INTEGRATIONS
):
_DEFAULT_INTEGRATIONS.append(
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration",
)

self.integrations = setup_integrations(
self.options["integrations"],
Expand Down
66 changes: 66 additions & 0 deletions sentry_sdk/integrations/opentelemetry/distro.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
IMPORTANT: The contents of this file are part of a proof of concept and as such
are experimental and not suitable for production use. They may be changed or
removed at any time without prior notice.
"""

from sentry_sdk.integrations import DidNotEnable
from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
from sentry_sdk.utils import logger
from sentry_sdk._types import TYPE_CHECKING

try:
from opentelemetry import trace # type: ignore
from opentelemetry.instrumentation.distro import BaseDistro # type: ignore
from opentelemetry.propagate import set_global_textmap # type: ignore
from opentelemetry.sdk.trace import TracerProvider # type: ignore
except ImportError:
raise DidNotEnable("opentelemetry not installed")

try:
from opentelemetry.instrumentation.django import DjangoInstrumentor # type: ignore
except ImportError:
DjangoInstrumentor = None

try:
from opentelemetry.instrumentation.flask import FlaskInstrumentor # type: ignore
except ImportError:
FlaskInstrumentor = None

if TYPE_CHECKING:
# XXX pkg_resources is deprecated, there's a PR to switch to importlib:
# https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2181
# we should align this when the PR gets merged
from pkg_resources import EntryPoint
from typing import Any


CONFIGURABLE_INSTRUMENTATIONS = {
DjangoInstrumentor: {"is_sql_commentor_enabled": True},
FlaskInstrumentor: {"enable_commenter": True},
}


class _SentryDistro(BaseDistro): # type: ignore[misc]
def _configure(self, **kwargs):
# type: (Any) -> None
provider = TracerProvider()
provider.add_span_processor(SentrySpanProcessor())
trace.set_tracer_provider(provider)
set_global_textmap(SentryPropagator())

def load_instrumentor(self, entry_point, **kwargs):
# type: (EntryPoint, Any) -> None
instrumentor = entry_point.load()

if instrumentor in CONFIGURABLE_INSTRUMENTATIONS:
for key, value in CONFIGURABLE_INSTRUMENTATIONS[instrumentor].items():
kwargs[key] = value

instrumentor().instrument(**kwargs)
logger.debug(
"[OTel] %s instrumented (%s)",
entry_point.name,
", ".join([f"{k}: {v}" for k, v in kwargs.items()]),
)
32 changes: 12 additions & 20 deletions sentry_sdk/integrations/opentelemetry/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@
from importlib import import_module

from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
from sentry_sdk.integrations.opentelemetry.distro import _SentryDistro
from sentry_sdk.utils import logger, _get_installed_modules
from sentry_sdk._types import TYPE_CHECKING

try:
from opentelemetry import trace # type: ignore
from opentelemetry.instrumentation.auto_instrumentation._load import ( # type: ignore
_load_distro,
_load_instrumentors,
)
from opentelemetry.propagate import set_global_textmap # type: ignore
from opentelemetry.sdk.trace import TracerProvider # type: ignore
except ImportError:
raise DidNotEnable("opentelemetry not installed")

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


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

try:
distro = _load_distro()
distro = _SentryDistro()
distro.configure()
# XXX This does some initial checks before loading instrumentations
# (checks OTEL_PYTHON_DISABLED_INSTRUMENTATIONS, checks version
# compat). If we don't want this in the future, we can implement our
# own _load_instrumentors (it anyway just iterates over
# opentelemetry_instrumentor entry points).
_load_instrumentors(distro)
except Exception:
logger.exception("[OTel] Failed to auto-initialize OpenTelemetry")

# XXX: Consider whether this is ok to keep and make default.
# The alternative is asking folks to follow specific import order for
# some integrations (sentry_sdk.init before you even import Flask, for
# instance).
try:
_patch_remaining_classes(original_classes)
except Exception:
Expand All @@ -65,8 +70,6 @@ def setup_once():
"You might have to make sure sentry_sdk.init() is called before importing anything else."
)

_setup_sentry_tracing()

logger.debug("[OTel] Finished setting up OpenTelemetry integration")


Expand Down Expand Up @@ -161,14 +164,3 @@ def _import_by_path(path):
# type: (str) -> type
parts = path.rsplit(".", maxsplit=1)
return getattr(import_module(parts[0]), parts[-1])


def _setup_sentry_tracing():
# type: () -> None
provider = TracerProvider()

provider.add_span_processor(SentrySpanProcessor())

trace.set_tracer_provider(provider)

set_global_textmap(SentryPropagator())
61 changes: 53 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,59 @@ def get_file_text(file_name):
"openai": ["openai>=1.0.0", "tiktoken>=0.3.0"],
"opentelemetry": ["opentelemetry-distro>=0.35b0"],
"opentelemetry-experimental": [
"opentelemetry-distro~=0.40b0",
"opentelemetry-instrumentation-aiohttp-client~=0.40b0",
"opentelemetry-instrumentation-django~=0.40b0",
"opentelemetry-instrumentation-fastapi~=0.40b0",
"opentelemetry-instrumentation-flask~=0.40b0",
"opentelemetry-instrumentation-requests~=0.40b0",
"opentelemetry-instrumentation-sqlite3~=0.40b0",
"opentelemetry-instrumentation-urllib~=0.40b0",
# There's an umbrella package called
# opentelemetry-contrib-instrumentations that installs all
# available instrumentation packages, however it's broken in recent
# versions (after 0.41b0), see
# https://github.com/open-telemetry/opentelemetry-python-contrib/issues/2053
"opentelemetry-instrumentation-aio-pika==0.46b0",
"opentelemetry-instrumentation-aiohttp-client==0.46b0",
# "opentelemetry-instrumentation-aiohttp-server==0.46b0", # broken package
"opentelemetry-instrumentation-aiopg==0.46b0",
"opentelemetry-instrumentation-asgi==0.46b0",
"opentelemetry-instrumentation-asyncio==0.46b0",
"opentelemetry-instrumentation-asyncpg==0.46b0",
"opentelemetry-instrumentation-aws-lambda==0.46b0",
"opentelemetry-instrumentation-boto==0.46b0",
"opentelemetry-instrumentation-boto3sqs==0.46b0",
"opentelemetry-instrumentation-botocore==0.46b0",
"opentelemetry-instrumentation-cassandra==0.46b0",
"opentelemetry-instrumentation-celery==0.46b0",
"opentelemetry-instrumentation-confluent-kafka==0.46b0",
"opentelemetry-instrumentation-dbapi==0.46b0",
"opentelemetry-instrumentation-django==0.46b0",
"opentelemetry-instrumentation-elasticsearch==0.46b0",
"opentelemetry-instrumentation-falcon==0.46b0",
"opentelemetry-instrumentation-fastapi==0.46b0",
"opentelemetry-instrumentation-flask==0.46b0",
"opentelemetry-instrumentation-grpc==0.46b0",
"opentelemetry-instrumentation-httpx==0.46b0",
"opentelemetry-instrumentation-jinja2==0.46b0",
"opentelemetry-instrumentation-kafka-python==0.46b0",
"opentelemetry-instrumentation-logging==0.46b0",
"opentelemetry-instrumentation-mysql==0.46b0",
"opentelemetry-instrumentation-mysqlclient==0.46b0",
"opentelemetry-instrumentation-pika==0.46b0",
"opentelemetry-instrumentation-psycopg==0.46b0",
"opentelemetry-instrumentation-psycopg2==0.46b0",
"opentelemetry-instrumentation-pymemcache==0.46b0",
"opentelemetry-instrumentation-pymongo==0.46b0",
"opentelemetry-instrumentation-pymysql==0.46b0",
"opentelemetry-instrumentation-pyramid==0.46b0",
"opentelemetry-instrumentation-redis==0.46b0",
"opentelemetry-instrumentation-remoulade==0.46b0",
"opentelemetry-instrumentation-requests==0.46b0",
"opentelemetry-instrumentation-sklearn==0.46b0",
"opentelemetry-instrumentation-sqlalchemy==0.46b0",
"opentelemetry-instrumentation-sqlite3==0.46b0",
"opentelemetry-instrumentation-starlette==0.46b0",
"opentelemetry-instrumentation-system-metrics==0.46b0",
"opentelemetry-instrumentation-threading==0.46b0",
"opentelemetry-instrumentation-tornado==0.46b0",
"opentelemetry-instrumentation-tortoiseorm==0.46b0",
"opentelemetry-instrumentation-urllib==0.46b0",
"opentelemetry-instrumentation-urllib3==0.46b0",
"opentelemetry-instrumentation-wsgi==0.46b0",
],
"pure_eval": ["pure_eval", "executing", "asttokens"],
"pymongo": ["pymongo>=3.1"],
Expand Down
15 changes: 12 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@

import sentry_sdk
from sentry_sdk.envelope import Envelope
from sentry_sdk.integrations import _processed_integrations # noqa: F401
from sentry_sdk.profiler.transaction_profiler import teardown_profiler
from sentry_sdk.integrations import ( # noqa: F401
_DEFAULT_INTEGRATIONS,
_processed_integrations,
)
from sentry_sdk.profiler import teardown_profiler
from sentry_sdk.profiler.continuous_profiler import teardown_continuous_profiler
from sentry_sdk.transport import Transport
from sentry_sdk.utils import reraise
Expand Down Expand Up @@ -169,7 +172,13 @@ def reset_integrations():
with a clean slate to ensure monkeypatching works well,
but this also means some other stuff will be monkeypatched twice.
"""
global _processed_integrations
global _DEFAULT_INTEGRATIONS, _processed_integrations
try:
_DEFAULT_INTEGRATIONS.remove(
"sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration"
)
except ValueError:
pass
_processed_integrations.clear()


Expand Down
Loading
Loading