Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions sentry_sdk/_span_batcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ def add(self, span: "StreamedSpan") -> None:
def _to_transport_format(item: "StreamedSpan") -> "Any":
# TODO[span-first]
res: "dict[str, Any]" = {}

if item._attributes:
res["attributes"] = {
k: serialize_attribute(v) for (k, v) in item.attributes.items()
}

return res

def _flush(self) -> None:
Expand Down
11 changes: 10 additions & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
from sentry_sdk.scope import Scope
from sentry_sdk.session import Session
from sentry_sdk.spotlight import SpotlightClient
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.transport import Transport, Item
from sentry_sdk._log_batcher import LogBatcher
from sentry_sdk._metrics_batcher import MetricsBatcher
Expand Down Expand Up @@ -227,6 +228,9 @@ def _capture_log(self, log: "Log", scope: "Scope") -> None:
def _capture_metric(self, metric: "Metric", scope: "Scope") -> None:
pass

def _capture_span(self, span: "StreamedSpan", scope: "Scope") -> None:
pass

def capture_session(self, *args: "Any", **kwargs: "Any") -> None:
return None

Expand Down Expand Up @@ -920,7 +924,7 @@ def capture_event(

def _capture_telemetry(
self,
telemetry: "Optional[Union[Log, Metric]]",
telemetry: "Optional[Union[Log, Metric, StreamedSpan]]",
ty: str,
scope: "Scope",
) -> None:
Expand All @@ -947,6 +951,8 @@ def _capture_telemetry(
batcher = self.log_batcher
elif ty == "metric":
batcher = self.metrics_batcher # type: ignore
elif ty == "span":
batcher = self.span_batcher # type: ignore

if batcher is not None:
batcher.add(telemetry) # type: ignore
Expand All @@ -957,6 +963,9 @@ def _capture_log(self, log: "Optional[Log]", scope: "Scope") -> None:
def _capture_metric(self, metric: "Optional[Metric]", scope: "Scope") -> None:
self._capture_telemetry(metric, "metric", scope)

def _capture_span(self, span: "Optional[StreamedSpan]", scope: "Scope") -> None:
self._capture_telemetry(span, "span", scope)

def capture_session(
self,
session: "Session",
Expand Down
51 changes: 38 additions & 13 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
from sentry_sdk.tracing_utils import (
Baggage,
has_tracing_enabled,
has_span_streaming_enabled,
normalize_incoming_data,
PropagationContext,
)
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.tracing import (
BAGGAGE_HEADER_NAME,
SENTRY_TRACE_HEADER_NAME,
Expand Down Expand Up @@ -1278,6 +1280,17 @@ def _capture_metric(self, metric: "Optional[Metric]") -> None:

client._capture_metric(metric, scope=merged_scope)

def _capture_span(self, span: "Optional[StreamedSpan]") -> None:
if span is None:
return

client = self.get_client()
if not has_span_streaming_enabled(client.options):
return

merged_scope = self._merge_scopes()
client._capture_span(span, scope=merged_scope)

def capture_message(
self,
message: str,
Expand Down Expand Up @@ -1522,16 +1535,25 @@ def _apply_flags_to_event(
)

def _apply_scope_attributes_to_telemetry(
self, telemetry: "Union[Log, Metric]"
self, telemetry: "Union[Log, Metric, StreamedSpan]"
) -> None:
# TODO: turn Logs, Metrics into actual classes
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want to do this eventually, but it's a breaking change so it has to wait.

if isinstance(telemetry, dict):
attributes = telemetry["attributes"]
else:
attributes = telemetry._attributes

for attribute, value in self._attributes.items():
if attribute not in telemetry["attributes"]:
telemetry["attributes"][attribute] = value
if attribute not in attributes:
attributes[attribute] = value

def _apply_user_attributes_to_telemetry(
self, telemetry: "Union[Log, Metric]"
self, telemetry: "Union[Log, Metric, StreamedSpan]"
) -> None:
attributes = telemetry["attributes"]
if isinstance(telemetry, dict):
attributes = telemetry["attributes"]
else:
attributes = telemetry._attributes

if not should_send_default_pii() or self._user is None:
return
Expand Down Expand Up @@ -1651,16 +1673,19 @@ def apply_to_event(
return event

@_disable_capture
def apply_to_telemetry(self, telemetry: "Union[Log, Metric]") -> None:
def apply_to_telemetry(self, telemetry: "Union[Log, Metric, StreamedSpan]") -> None:
# Attributes-based events and telemetry go through here (logs, metrics,
# spansV2)
trace_context = self.get_trace_context()
trace_id = trace_context.get("trace_id")
if telemetry.get("trace_id") is None:
telemetry["trace_id"] = trace_id or "00000000-0000-0000-0000-000000000000"
span_id = trace_context.get("span_id")
if telemetry.get("span_id") is None and span_id:
telemetry["span_id"] = span_id
if not isinstance(telemetry, StreamedSpan):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stuff for metrics and logs in this if should be moved elsewhere, but out of scope of this PR.

trace_context = self.get_trace_context()
trace_id = trace_context.get("trace_id")
if telemetry.get("trace_id") is None:
telemetry["trace_id"] = (
trace_id or "00000000-0000-0000-0000-000000000000"
)
span_id = trace_context.get("span_id")
if telemetry.get("span_id") is None and span_id:
telemetry["span_id"] = span_id

self._apply_scope_attributes_to_telemetry(telemetry)
self._apply_user_attributes_to_telemetry(telemetry)
Expand Down
30 changes: 29 additions & 1 deletion sentry_sdk/traces.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import uuid
from typing import TYPE_CHECKING

from sentry_sdk.utils import format_attribute

if TYPE_CHECKING:
from typing import Optional
from sentry_sdk._types import Attributes, AttributeValue


class StreamedSpan:
Expand All @@ -22,15 +25,40 @@ class StreamedSpan:
span implementation lives in tracing.Span.
"""

__slots__ = ("_trace_id",)
__slots__ = (
"name",
"_attributes",
"_trace_id",
)

def __init__(
self,
*,
name: str,
attributes: "Optional[Attributes]" = None,
trace_id: "Optional[str]" = None,
):
self.name: str = name
self._attributes: "Attributes" = attributes or {}

self._trace_id = trace_id

def get_attributes(self) -> "Attributes":
return self._attributes

def set_attribute(self, key: str, value: "AttributeValue") -> None:
self._attributes[key] = format_attribute(value)

def set_attributes(self, attributes: "Attributes") -> None:
for key, value in attributes.items():
self.set_attribute(key, value)

def remove_attribute(self, key: str) -> None:
try:
del self._attributes[key]
except KeyError:
pass

@property
def trace_id(self) -> str:
if not self._trace_id:
Expand Down
Loading