Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion requirements-linting.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ types-greenlet
types-redis
types-setuptools
types-webob
opentelemetry-distro
opentelemetry-distro[otlp]
pymongo # There is no separate types module.
loguru # There is no separate types module.
pre-commit # local linting
Expand Down
1 change: 1 addition & 0 deletions sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class EndpointType(Enum):
"""

ENVELOPE = "envelope"
OTLP_TRACES = "integration/otlp/v1/traces"


class CompressionAlgo(Enum):
Expand Down
82 changes: 82 additions & 0 deletions sentry_sdk/integrations/otlp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.scope import register_external_propagation_context
from sentry_sdk.utils import logger, Dsn
from sentry_sdk.consts import VERSION, EndpointType

try:
from opentelemetry import trace
from opentelemetry.propagate import set_global_textmap
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
except ImportError:
raise DidNotEnable("opentelemetry-distro[otlp] is not installed")

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Optional, Dict, Any, Tuple


def otel_propagation_context():
# type: () -> Optional[Tuple[str, str]]
"""
Get the (trace_id, span_id) from opentelemetry if exists.
"""
ctx = trace.get_current_span().get_span_context()

if ctx.trace_id == trace.INVALID_TRACE_ID or ctx.span_id == trace.INVALID_SPAN_ID:
return None

return (trace.format_trace_id(ctx.trace_id), trace.format_span_id(ctx.span_id))


def setup_otlp_exporter(dsn=None):
# type: (Optional[str]) -> None
tracer_provider = trace.get_tracer_provider()

if not isinstance(tracer_provider, TracerProvider):
logger.debug("[OTLP] No TracerProvider configured by user, creating a new one")
tracer_provider = TracerProvider()
trace.set_tracer_provider(tracer_provider)

endpoint = None
headers = None
if dsn:
auth = Dsn(dsn).to_auth(f"sentry.python/{VERSION}")
endpoint = auth.get_api_url(EndpointType.OTLP_TRACES)
headers = {"X-Sentry-Auth": auth.to_header()}
logger.debug(f"[OTLP] Sending traces to {endpoint}")

otlp_exporter = OTLPSpanExporter(endpoint=endpoint, headers=headers)
span_processor = BatchSpanProcessor(otlp_exporter)
tracer_provider.add_span_processor(span_processor)


class OTLPIntegration(Integration):
identifier = "otlp"

def __init__(self, setup_otlp_exporter=True, setup_propagator=True):
# type: (bool, bool) -> None
self.setup_otlp_exporter = setup_otlp_exporter
self.setup_propagator = setup_propagator

@staticmethod
def setup_once():
# type: () -> None
logger.debug("[OTLP] Setting up trace linking for all events")
register_external_propagation_context(otel_propagation_context)

def setup_once_with_options(self, options=None):
# type: (Optional[Dict[str, Any]]) -> None
if self.setup_otlp_exporter:
logger.debug("[OTLP] Setting up OTLP exporter")
dsn = options.get("dsn") if options else None # type: Optional[str]
setup_otlp_exporter(dsn)

if self.setup_propagator:
logger.debug("[OTLP] Setting up propagator for distributed tracing")
# TODO-neel better propagator support, chain with existing ones if possible instead of replacing
set_global_textmap(SentryPropagator())
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def get_file_text(file_name):
"openfeature": ["openfeature-sdk>=0.7.1"],
"opentelemetry": ["opentelemetry-distro>=0.35b0"],
"opentelemetry-experimental": ["opentelemetry-distro"],
"opentelemetry-otlp": ["opentelemetry-distro[otlp]>=0.35b0"],
"pure-eval": ["pure_eval", "executing", "asttokens"],
"pydantic_ai": ["pydantic-ai>=1.0.0"],
"pymongo": ["pymongo>=3.1"],
Expand Down
7 changes: 7 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ envlist =
# OpenTelemetry (OTel)
{py3.7,py3.9,py3.12,py3.13,py3.14,py3.14t}-opentelemetry

# OpenTelemetry with OTLP
{py3.7,py3.9,py3.12,py3.13,py3.14,py3.14t}-otlp

# OpenTelemetry Experimental (POTel)
{py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-potel

Expand Down Expand Up @@ -342,6 +345,9 @@ deps =
# OpenTelemetry (OTel)
opentelemetry: opentelemetry-distro

# OpenTelemetry with OTLP
otlp: opentelemetry-distro[otlp]

# OpenTelemetry Experimental (POTel)
potel: -e .[opentelemetry-experimental]

Expand Down Expand Up @@ -765,6 +771,7 @@ setenv =
cloud_resource_context: TESTPATH=tests/integrations/cloud_resource_context
gcp: TESTPATH=tests/integrations/gcp
opentelemetry: TESTPATH=tests/integrations/opentelemetry
otlp: TESTPATH=tests/integrations/otlp
potel: TESTPATH=tests/integrations/opentelemetry
socket: TESTPATH=tests/integrations/socket

Expand Down