8000 feat(potel): Make tracing APIs use OTel in the background by sentrivana · Pull Request #3242 · getsentry/sentry-python · GitHub
[go: up one dir, main page]

Skip to content

feat(potel): Make tracing APIs use OTel in the background #3242

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

Merged
merged 45 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
28effd6
Skeletons for new components
sl0thentr0py Jun 10, 2024
e7cbb59
Add simple scope management whenever a context is attached
sl0thentr0py Jun 12, 2024
618d6ca
Don't parse DSN twice
sl0thentr0py Jun 12, 2024
7dba029
wip
sl0thentr0py Jun 12, 2024
951477f
Skeletons for new components
sl0thentr0py Jun 10, 2024
1a35dae
Skeletons for new components
sl0thentr0py Jun 10, 2024
c523182
Add simple scope management whenever a context is attached
sl0thentr0py Jun 12, 2024
aee3463
Merge branch 'master' into potel-base
sentrivana Jun 26, 2024
27e9e82
mypy fixes
sentrivana Jun 26, 2024
4f9257d
Merge remote-tracking branch 'origin/master' into potel-base
sl0thentr0py Jun 27, 2024
9bfab81
Merge branch 'potel-base' into neel/potel/initial-scope-management
sl0thentr0py Jun 27, 2024
e7a20f2
Merge branch 'neel/potel/initial-scope-management' into neel/potel/sp…
sl0thentr0py Jun 27, 2024
436626b
Merge branch 'potel-base' into neel/potel/initial-scope-management
sl0thentr0py Jun 27, 2024
5d04d3d
Merge branch 'neel/potel/initial-scope-management' into neel/potel/sp…
sl0thentr0py Jun 27, 2024
048acc9
working span processor
sl0thentr0py Jun 28, 2024
8a08fb3
lint
sl0thentr0py Jun 28, 2024
2e2e5b9
Port over op/description/status extraction
sl0thentr0py Jul 2, 2024
2c29711
defaultdict
sl0thentr0py Jul 2, 2024
7afe4bb
naive impl
sentrivana Jul 3, 2024
21b5921
wip
sentrivana Jul 4, 2024
2e1b785
fix args
sentrivana Jul 4, 2024
74f1e4a
wip
sentrivana Jul 5, 2024
8993799
remove extra docs
sentrivana Jul 8, 2024
cd3e140
Add simple scope management whenever a context is attached (#3159)
sl0thentr0py Jul 9, 2024
f0c1a84
Implement new POTel span processor (#3223)
sl0thentr0py Jul 9, 2024
de1b0e3
Merge remote-tracking branch 'origin/master' into potel-base
sl0thentr0py Jul 9, 2024
f534a22
Merge branch 'potel-base' into ivana/potel/start-span
sl0thentr0py Jul 9, 2024
0fe78f6
Basic test cases for potel (#3286)
sl0thentr0py Jul 16, 2024
d80af4c
Proxy POTelSpan.set_data to underlying otel span attributes (#3297)
sl0thentr0py Jul 16, 2024
6742739
Merge branch 'potel-base' into ivana/potel/start-span
sentrivana Jul 31, 2024
25914a5
ref(tracing): Simplify backwards-compat code (#3379)
szokeasaurusrex Aug 2, 2024
cb6b686
New Scope implementation based on OTel Context (#3389)
sl0thentr0py Aug 6, 2024
01835ed
Merge branch 'potel-base' into ivana/potel/start-span
sentrivana Aug 12, 2024
0e4755b
Fix circular imports (#3431)
sentrivana Aug 12, 2024
45e281f
Random tweaks (#3437)
sentrivana Aug 12, 2024
59e11b4
Merge branch 'potel-base' into ivana/potel/start-span
sentrivana Aug 12, 2024
76ccff5
Merge branch 'potel-base' into ivana/potel/start-span
sentrivana Aug 12, 2024
52fca29
Origin improvements (#3432)
sentrivana Aug 13, 2024
67a5823
Tweak OTel timestamp utils (#3436)
sentrivana Aug 13, 2024
74d62f9
Create spans on scope (#3442)
sentrivana Aug 13, 2024
5ccfb34
Fill out more property/method stubs (#3441)
sentrivana Aug 13, 2024
c764ebe
Cleanup origin handling and defaults (#3445)
sl0thentr0py Aug 13, 2024
b3d7226
Merge branch 'potel-base' into ivana/potel/start-span
sentrivana Aug 14, 2024
29b7fa8
add note to migration guide
sentrivana Aug 14, 2024
f9cdd44
Attribute namespace for tags, measurements (#3448)
sentrivana Aug 14, 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
6 changes: 5 additions & 1 deletion MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh

### Changed

- The SDK now supports Python 3.7 and higher.
- `sentry_sdk.start_span` now only takes keyword arguments.
- The `Span()` constructor does not accept a `hub` parameter anymore.
- `Span.finish()` does not accept a `hub` parameter anymore.
- The `Profile()` constructor does not accept a `hub` parameter anymore.
- A `Profile` object does not have a `.hub` property anymore.

### Removed

- When setting span status, the HTTP status code is no longer automatically added as a tag.
- Class `Hub` has been removed.
- Class `_ScopeManager` has been removed.
- The context manager `auto_session_tracking()` has been removed. Use `track_session()` instead.
- The context manager `auto_session_tracking_scope()` has been removed. Use `track_session()` instead.
- Utility function `is_auto_session_tracking_enabled()` has been removed. There is no public replacement. There is a private `_is_auto_session_tracking_enabled()` (if you absolutely need this function) It accepts a `scope` parameter instead of the previously used `hub` parameter.
- Utility function `is_auto_session_tracking_enabled()` has been removed. There is no public replacement. There is a private `_is_auto_session_tracking_enabled()` (if you absolutely need this function) It accepts a `scope` parameter instead of the previously used `hub` parameter.
- Utility function `is_auto_session_tracking_enabled_scope()` has been removed. There is no public replacement. There is a private `_is_auto_session_tracking_enabled()` (if you absolutely need this function)

### Deprecated

- `sentry_sdk.start_transaction` is deprecated. Use `sentry_sdk.start_span` instead.

## Upgrading to 2.0

Expand Down
49 changes: 36 additions & 13 deletions sentry_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

from sentry_sdk import tracing_utils, Client
from sentry_sdk._init_implementation import init
from sentry_sdk.scope import Scope, new_scope, isolation_scope
from sentry_sdk.tracing import NoOpSpan, Transaction, trace
from sentry_sdk.tracing import POTelSpan, Transaction, trace
from sentry_sdk.crons import monitor

# TODO-neel-potel make 2 scope strategies/impls and switch
from sentry_sdk.integrations.opentelemetry.scope import (
PotelScope as Scope,
new_scope,
isolation_scope,
)

from sentry_sdk._types import TYPE_CHECKING

Expand Down Expand Up @@ -227,22 +232,40 @@ def flush(
return get_client().flush(timeout=timeout, callback=callback)


@scopemethod
def start_span(
*,
span=None,
custom_sampling_context=None,
**kwargs, # type: Any
):
# type: (...) -> Span
return get_current_s 8000 cope().start_span(**kwargs)
# type: (...) -> POTelSpan
"""
Start and return a span.

This is the entry point to manual tracing instrumentation.

A tree structure can be built by adding child spans to the span.
To start a new child span within the span, call the `start_child()` method.

When used as a context manager, spans are automatically finished at the end
of the `with` block. If not using context managers, call the `finish()`
method.
"""
# TODO: Consider adding type hints to the method signature.
return get_current_scope().start_span(span, custom_sampling_context, **kwargs)


@scopemethod
def start_transaction(
transaction=None, # type: Optional[Transaction]
custom_sampling_context=None, # type: Optional[SamplingContext]
**kwargs, # type: Unpack[TransactionKwargs]
):
# type: (...) -> Union[Transaction, NoOpSpan]
# type: (...) -> POTelSpan
"""
.. deprecated:: 3.0.0
This function is deprecated and will be removed in a future release.
Use :py:meth:`sentry_sdk.start_span` instead.

Start and return a transaction on the current scope.

Start an existing transaction if given, otherwise create and start a new
Expand Down Expand Up @@ -271,8 +294,10 @@ def start_transaction(
constructor. See :py:class:`sentry_sdk.tracing.Transaction` for
available arguments.
"""
return get_current_scope().start_transaction(
transaction, custom_sampling_context, **kwargs
return start_span(
span=transaction,
custom_sampling_context=custom_sampling_context,
**kwargs,
)


Expand Down Expand Up @@ -311,10 +336,8 @@ def get_baggage():
return None


def continue_trace(
environ_or_headers, op=None, name=None, source=None, origin="manual"
):
# type: (Dict[str, Any], Optional[str], Optional[str], Optional[str], str) -> Transaction
def continue_trace(environ_or_headers, op=None, name=None, source=None, origin=None):
# type: (Dict[str, Any], Optional[str], Optional[str], Optional[str], Optional[str]) -> Transaction
"""
Sets the propagation context from environment or headers and returns a transaction.
"""
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ def __init__(
unsafe_context_data=False,
transaction_style="endpoint",
mechanism_type="asgi",
span_origin="manual",
span_origin=None,
):
# type: (Any, bool, str, str, str) -> None
# type: (Any, bool, str, str, Optional[str]) -> None
"""
Instrument an ASGI application with Sentry. Provides HTTP/websocket
data to sent events and basic handling for exceptions bubbling up
Expand Down
3 changes: 2 additions & 1 deletion sentry_sdk/integrations/boto3.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.tracing import Span

from sentry_sdk._types import TYPE_CHECKING
from sentry_sdk.utils import (
Expand All @@ -19,6 +18,8 @@
from typing import Optional
from typing import Type

from sentry_sdk.tracing import Span

try:
from botocore import __version__ as BOTOCORE_VERSION # type: ignore
from botocore.client import BaseClient # type: ignore
Expand Down
16 changes: 16 additions & 0 deletions sentry_sdk/integrations/opentelemetry/consts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
from opentelemetry.context import create_key


# propagation keys
SENTRY_TRACE_KEY = create_key("sentry-trace")
SENTRY_BAGGAGE_KEY = create_key("sentry-baggage")

# scope management keys
SENTRY_SCOPES_KEY = create_key("sentry_scopes")
SENTRY_FORK_ISOLATION_SCOPE_KEY = create_key("sentry_fork_isolation_scope")

OTEL_SENTRY_CONTEXT = "otel"
SPAN_ORIGIN = "auto.otel"


class SentrySpanAttribute:
# XXX not all of these need separate attributes, we might just use
# existing otel attrs for some
DESCRIPTION = "sentry.description"
OP = "sentry.op"
ORIGIN = "sentry.origin"
MEASUREMENT = "sentry.measurement"
TAG = "sentry.tag"
30 changes: 18 additions & 12 deletions sentry_sdk/integrations/opentelemetry/contextvars_context.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
from opentelemetry.context import Context, create_key, get_value, set_value
from opentelemetry.context import Context, get_value, set_value
from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext

from sentry_sdk.scope import Scope


_SCOPES_KEY = create_key("sentry_scopes")
import sentry_sdk
from sentry_sdk.integrations.opentelemetry.consts import (
SENTRY_SCOPES_KEY,
SENTRY_FORK_ISOLATION_SCOPE_KEY,
)


class SentryContextVarsRuntimeContext(ContextVarsRuntimeContext):
def attach(self, context):
# type: (Context) -> object
scopes = get_value(_SCOPES_KEY, context)
scopes = get_value(SENTRY_SCOPES_KEY, context)
should_fork_isolation_scope = context.pop(
SENTRY_FORK_ISOLATION_SCOPE_KEY, False
)

if scopes and isinstance(scopes, tuple):
(current_scope, isolation_scope) = scopes
else:
current_scope = Scope.get_current_scope()
isolation_scope = Scope.get_isolation_scope()
current_scope = sentry_sdk.get_current_scope()
isolation_scope = sentry_sdk.get_isolation_scope()

# TODO-neel-potel fork isolation_scope too like JS
# once we setup our own apis to pass through to otel
new_scopes = (current_scope.fork(), isolation_scope)
new_context = set_value(_SCOPES_KEY, new_scopes, context)
new_scope = current_scope.fork()
new_isolation_scope = (
isolation_scope.fork() if should_fork_isolation_scope else isolation_scope
)
new_scopes = (new_scope, new_isolation_scope)

new_context = set_value(SENTRY_SCOPES_KEY, new_scopes, context)
return super().attach(new_context)
14 changes: 12 additions & 2 deletions sentry_sdk/integrations/opentelemetry/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
from sentry_sdk.integrations.opentelemetry.potel_span_processor import (
PotelSentrySpanProcessor,
)
from sentry_sdk.integrations.opentelemetry.contextvars_context import (
SentryContextVarsRuntimeContext,
)
from sentry_sdk.utils import logger

try:
Expand Down Expand Up @@ -46,9 +51,14 @@ def setup_once():

def _setup_sentry_tracing():
# type: () -> None
import opentelemetry.context

opentelemetry.context._RUNTIME_CONTEXT = SentryContextVarsRuntimeContext()

provider = TracerProvider()
provider.add_span_processor(SentrySpanProcessor())
provider.add_span_processor(PotelSentrySpanProcessor())
trace.set_tracer_provider(provider)

set_global_textmap(SentryPropagator())


Expand Down
83 changes: 53 additions & 30 deletions sentry_sdk/integrations/opentelemetry/potel_span_processor.py
1241
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
from opentelemetry.sdk.trace import Span, ReadableSpan, SpanProcessor

from sentry_sdk import capture_event
from sentry_sdk.tracing import DEFAULT_SPAN_ORIGIN
from sentry_sdk.integrations.opentelemetry.utils import (
is_sentry_span,
convert_otel_timestamp,
convert_from_otel_timestamp,
extract_span_attributes,
extract_span_data,
)
from sentry_sdk.integrations.opentelemetry.consts import (
OTEL_SENTRY_CONTEXT,
SPAN_ORIGIN,
SentrySpanAttribute,
)
from sentry_sdk._types import TYPE_CHECKING

Expand Down Expand Up @@ -107,21 +109,21 @@ def _root_span_to_transaction_event(self, span):
# type: (ReadableSpan) -> Optional[Event]
if not span.context:
return None
if not span.start_time:
return None
if not span.end_time:

event = self._common_span_transaction_attributes_as_json(span)
if event is None:
return None

trace_id = format_trace_id(span.context.trace_id)
span_id = format_span_id(span.context.span_id)
parent_span_id = format_span_id(span.parent.span_id) if span.parent else None

(op, description, status, _) = extract_span_data(span)
(op, description, status, _, origin) = extract_span_data(span)

trace_context = {
"trace_id": trace_id,
"span_id": span_id,
"origin": SPAN_ORIGIN,
"origin": origin or DEFAULT_SPAN_ORIGIN,
"op": op,
"status": status,
} # type: dict[str, Any]
Expand All @@ -135,47 +137,68 @@ def _root_span_to_transaction_event(self, span):
if span.resource.attributes:
contexts[OTEL_SENTRY_CONTEXT] = {"resource": dict(span.resource.attributes)}

event = {
"type": "transaction",
"transaction": description,
# TODO-neel-potel tx source based on integration
"transaction_info": {"source": "custom"},
"contexts": contexts,
"start_timestamp": convert_otel_timestamp(span.start_time),
"timestamp": convert_otel_timestamp(span.end_time),
} # type: Event
event.update(
{
"type": "transaction",
"transaction": description,
# TODO-neel-potel tx source based on integration
"transaction_info": {"source": "custom"},
"contexts": contexts,
}
) # type: Event

return event

def _span_to_json(self, span):
# type: (ReadableSpan) -> Optional[dict[str, Any]]
if not span.context:
return None
if not span.start_time:
return None
if not span.end_time:

span_json = self._common_span_transaction_attributes_as_json(span)
if span_json is None:
return None

trace_id = format_trace_id(span.context.trace_id)
span_id = format_span_id(span.context.span_id)
parent_span_id = format_span_id(span.parent.span_id) if span.parent else None

(op, description, status, _) = extract_span_data(span)
(op, description, status, _, origin) = extract_span_data(span)

span_json = {
"trace_id": trace_id,
"span_id": span_id,
"origin": SPAN_ORIGIN,
"op": op,
"description": description,
"status": status,
"start_timestamp": convert_otel_timestamp(span.start_time),
"timestamp": convert_otel_timestamp(span.end_time),
} # type: dict[str, Any]
span_json.update(
{
"trace_id": trace_id,
"span_id": span_id,
"op": op,
"description": description,
"status": status,
"origin": origin or DEFAULT_SPAN_ORIGIN,
}
)

if parent_span_id:
span_json["parent_span_id"] = parent_span_id

if span.attributes:
span_json["data"] = dict(span.attributes)

return span_json

def _common_span_transaction_attributes_as_json(self, span):
# type: (ReadableSpan) -> Optional[dict[str, Any]]
if not span.start_time or not span.end_time:
return None

common_json = {
"start_timestamp": convert_from_otel_timestamp(span.start_time),
"timestamp": convert_from_otel_timestamp(span.end_time),
} # type: dict[str, Any]

measurements = extract_span_attributes(span, SentrySpanAttribute.MEASUREMENT)
if measurements:
common_json["measurements"] = measurements

tags = extract_span_attributes(span, SentrySpanAttribute.TAG)
if tags:
common_json["tags"] = tags

return common_json
Loading
Loading
0