Skip to content

Commit ef83cb3

Browse files
committed
add some attrs
1 parent 7bf2cb9 commit ef83cb3

File tree

6 files changed

+138
-41
lines changed

6 files changed

+138
-41
lines changed

sentry_sdk/_span_batcher.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from typing import Optional, List, Callable, TYPE_CHECKING, Any
1111

1212
from sentry_sdk.consts import SPANSTATUS
13-
from sentry_sdk.utils import format_attribute_value, format_timestamp, safe_repr
1413
from sentry_sdk.envelope import Envelope, Item, PayloadRef
1514
from sentry_sdk.tracing import Transaction
1615

@@ -112,7 +111,6 @@ def add(self, span):
112111
self.get_size() >= self.MAX_SPANS_BEFORE_FLUSH
113112
): # TODO[span-first] should this be per bucket?
114113
self._flush_event.set()
115-
print(self._span_buffer)
116114

117115
def kill(self):
118116
# type: (...) -> None
@@ -130,6 +128,8 @@ def flush(self):
130128
@staticmethod
131129
def _span_to_transport_format(span):
132130
# type: (Span) -> SpanV2
131+
from sentry_sdk.utils import attribute_value_to_transport_format, safe_repr
132+
133133
res = {
134134
"trace_id": span.trace_id,
135135
"span_id": span.span_id,
@@ -145,15 +145,18 @@ def _span_to_transport_format(span):
145145
if span.parent_span_id:
146146
res["parent_span_id"] = span.parent_span_id
147147

148-
if span.attributes:
148+
if span._attributes:
149149
res["attributes"] = {
150-
k: format_attribute_value(v) for (k, v) in span["attributes"].items()
150+
k: attribute_value_to_transport_format(v)
151+
for (k, v) in span._attributes.items()
151152
}
152153

153154
return res
154155

155156
def _flush(self):
156157
# type: (...) -> Optional[Envelope]
158+
from sentry_sdk.utils import format_timestamp
159+
157160
with self._lock:
158161
if len(self._span_buffer) == 0:
159162
return None

sentry_sdk/_types.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,16 @@ class SDKInfo(TypedDict):
223223
# TODO: Make a proper type definition for this (PRs welcome!)
224224
Hint = Dict[str, Any]
225225

226-
# TODO: Consolidate attribute types. E.g. metrics use the same attr structure.
227-
# I'm keeping span attrs separate for now so that the span first stuff is
228-
# isolated until it's not experimental.
229-
SpanAttributeValue = TypedDict(
230-
"SpanAttributeValue",
226+
AttributeValue = (
227+
str | bool | float | int | list[str] | list[bool] | list[float] | list[int]
228+
)
229+
Attributes = dict[str, AttributeValue]
230+
231+
SerializedAttributeValue = TypedDict(
232+
"SerializedAttributeValue",
231233
{
232234
"type": Literal["string", "boolean", "double", "integer"],
233-
"value": str | bool | float | int,
235+
"value": AttributeValue,
234236
},
235237
)
236238

@@ -240,22 +242,14 @@ class SDKInfo(TypedDict):
240242
"severity_text": str,
241243
"severity_number": int,
242244
"body": str,
243-
"attributes": dict[str, str | bool | float | int],
245+
"attributes": Attributes,
244246
"time_unix_nano": int,
245247
"trace_id": Optional[str],
246248
},
247249
)
248250

249251
MetricType = Literal["counter", "gauge", "distribution"]
250252

251-
MetricAttributeValue = TypedDict(
252-
"MetricAttributeValue",
253-
{
254-
"value": Union[str, bool, float, int],
255-
"type": Literal["string", "boolean", "double", "integer"],
256-
},
257-
)
258-
259253
Metric = TypedDict(
260254
"Metric",
261255
{
@@ -266,7 +260,7 @@ class SDKInfo(TypedDict):
266260
"type": MetricType,
267261
"value": float,
268262
"unit": Optional[str],
269-
"attributes": dict[str, str | bool | float | int],
263+
"attributes": Attributes,
270264
},
271265
)
272266

@@ -285,7 +279,7 @@ class SDKInfo(TypedDict):
285279
"is_segment": bool,
286280
"start_timestamp": float,
287281
"end_timestamp": float,
288-
"attributes": Optional[dict[str, SpanAttributeValue]],
282+
"attributes": Attributes,
289283
},
290284
)
291285

sentry_sdk/client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from sentry_sdk._compat import PY37, check_uwsgi_thread_support
1313
from sentry_sdk._metrics_batcher import MetricsBatcher
1414
from sentry_sdk._span_batcher import SpanBatcher
15+
from sentry_sdk.tracing_utils import has_span_streaming_enabled
1516
from sentry_sdk.utils import (
1617
AnnotatedValue,
1718
ContextVar,
@@ -938,6 +939,7 @@ def capture_event(
938939

939940
def _capture_log(self, log):
940941
# type: (Optional[Log]) -> None
942+
# TODO[ivana]: Use get_default_attributes here
941943
if not has_logs_enabled(self.options) or log is None:
942944
return
943945

@@ -1006,6 +1008,7 @@ def _capture_log(self, log):
10061008

10071009
def _capture_metric(self, metric):
10081010
# type: (Optional[Metric]) -> None
1011+
# TODO[ivana]: Use get_default_attributes here
10091012
if not has_metrics_enabled(self.options) or metric is None:
10101013
return
10111014

sentry_sdk/tracing.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
logger,
1414
nanosecond_time,
1515
should_be_treated_as_error,
16+
serialize_attribute,
17+
get_default_attributes,
1618
)
1719

1820
from typing import TYPE_CHECKING
@@ -44,6 +46,8 @@
4446
MeasurementUnit,
4547
SamplingContext,
4648
MeasurementValue,
49+
Attributes,
50+
AttributeValue,
4751
)
4852

4953
class SpanKwargs(TypedDict, total=False):
@@ -282,7 +286,7 @@ class Span:
282286
"_flags",
283287
"_flags_capacity",
284288
"_mode",
285-
"attributes",
289+
"_attributes",
286290
)
287291

288292
def __init__(
@@ -321,8 +325,7 @@ def __init__(
321325
self._containing_transaction = containing_transaction
322326
self._flags = {} # type: Dict[str, bool]
323327
self._flags_capacity = 10
324-
self.attributes = attributes or {}
325-
# TODO[span-first]: fill attributes
328+
self._attributes = attributes or {} # type: Attributes
326329

327330
if hub is not None:
328331
warnings.warn(
@@ -352,6 +355,16 @@ def __init__(
352355

353356
self.update_active_thread()
354357
self.set_profiler_id(get_profiler_id())
358+
self._set_initial_attributes()
359+
360+
def _set_initial_attributes(self):
361+
attributes = get_default_attributes()
362+
self._attributes = attributes | self._attributes
363+
364+
self._attributes["sentry.segment.id"] = self.containing_transaction.span_id
365+
if hasattr(self.containing_transaction, "name"):
366+
# TODO[span-first]: fix this properly
367+
self._attributes["sentry.segment.name"] = self.containing_transaction.name
355368

356369
# TODO this should really live on the Transaction class rather than the Span
357370
# class
@@ -631,6 +644,21 @@ def update_data(self, data):
631644
# type: (Dict[str, Any]) -> None
632645
self._data.update(data)
633646

647+
def get_attributes(self):
648+
# type: () -> Attributes
649+
return self._attributes
650+
651+
def set_attribute(self, attribute, value):
652+
# type: (str, AttributeValue) -> None
653+
self._attributes[attribute] = serialize_attribute(value)
654+
655+
def remove_attribute(self, attribute):
656+
# type: (str) -> None
657+
try:
658+
del self._attributes[attribute]
659+
except KeyError:
660+
pass
661+
634662
def set_flag(self, flag, result):
635663
# type: (str, bool) -> None
636664
if len(self._flags) < self._flags_capacity:

sentry_sdk/transport.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,13 @@ def _send_envelope(self, envelope):
475475
print(envelope.headers)
476476
for i, item in enumerate(envelope.items):
477477
print("Item", i, item.type)
478+
print("Headers")
478479
print(item.headers)
480+
print("Attributes")
481+
for i in item.payload.json["items"]:
482+
for attribute, value in i["attributes"].items():
483+
print(attribute, value)
484+
print("Payload")
479485
print(item.payload.json)
480486
print()
481487

sentry_sdk/utils.py

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
DEFAULT_ADD_FULL_STACK,
3131
DEFAULT_MAX_STACK_FRAMES,
3232
DEFAULT_MAX_VALUE_LENGTH,
33+
SPANDATA,
3334
EndpointType,
3435
)
3536
from sentry_sdk._types import Annotated, AnnotatedValue, SENSITIVE_DATA_SUBSTITUTE
@@ -1726,6 +1727,85 @@ def is_sentry_url(client, url):
17261727
)
17271728

17281729

1730+
def serialize_attribute(value):
1731+
# type: (Any) -> AttributeValue
1732+
# check for allowed primitives
1733+
if isinstance(value, (int, str, float, bool)):
1734+
return value
1735+
1736+
# lists are allowed too, as long as they don't mix types
1737+
if isinstance(value, (list, tuple)):
1738+
for type_ in (int, str, float, bool):
1739+
if all(isinstance(item, type_) for item in value):
1740+
return list(value)
1741+
1742+
return safe_repr(value)
1743+
1744+
1745+
def attribute_value_to_transport_format(value):
1746+
# type: (Any) -> dict[str, bool | str | int | float]
1747+
if isinstance(value, bool):
1748+
return {"value": value, "type": "boolean"}
1749+
1750+
if isinstance(value, int):
1751+
return {"value": value, "type": "integer"}
1752+
1753+
if isinstance(value, float):
1754+
return {"value": value, "type": "double"}
1755+
1756+
if isinstance(value, str):
1757+
return {"value": value, "type": "string"}
1758+
1759+
return {"value": safe_repr(value), "type": "string"}
1760+
1761+
1762+
def get_default_attributes():
1763+
# type: () -> Attributes
1764+
# TODO[ivana]: standardize attr names into an enum/take from sentry convs
1765+
from sentry_sdk.client import SDK_INFO
1766+
from sentry_sdk.scope import should_send_default_pii
1767+
1768+
attributes = {}
1769+
1770+
attributes["sentry.sdk.name"] = SDK_INFO["name"]
1771+
attributes["sentry.sdk.version"] = SDK_INFO["version"]
1772+
1773+
options = sentry_sdk.get_client().options
1774+
1775+
server_name = options.get("server_name")
1776+
if server_name is not None:
1777+
attributes[SPANDATA.SERVER_ADDRESS] = server_name
1778+
1779+
environment = options.get("environment")
1780+
if environment is not None:
1781+
attributes["sentry.environment"] = environment
1782+
1783+
release = options.get("release")
1784+
if release is not None:
1785+
attributes["sentry.release"] = release
1786+
1787+
thread_id, thread_name = get_current_thread_meta()
1788+
if thread_id is not None:
1789+
attributes["thread.id"] = thread_id
1790+
if thread_name is not None:
1791+
attributes["thread.name"] = thread_name
1792+
1793+
# The user, if present, is always set on the isolation scope.
1794+
# TODO[ivana]: gate behing PII?
1795+
if should_send_default_pii():
1796+
isolation_scope = sentry_sdk.get_isolation_scope()
1797+
if isolation_scope._user is not None:
1798+
for attribute, user_attribute in (
1799+
("user.id", "id"),
1800+
("user.name", "username"),
1801+
("user.email", "email"),
1802+
):
1803+
if attribute in isolation_scope._user:
1804+
attributes[attribute] = isolation_scope._user[user_attribute]
1805+
1806+
return attributes
1807+
1808+
17291809
def _generate_installed_modules():
17301810
# type: () -> Iterator[Tuple[str, str]]
17311811
try:
@@ -2076,20 +2156,3 @@ def get_before_send_metric(options):
20762156
return options.get("before_send_metric") or options["_experiments"].get(
20772157
"before_send_metric"
20782158
)
2079-
2080-
2081-
def format_attribute_value(value):
2082-
# type: (Any) -> dict[str, bool | str | int | float]
2083-
if isinstance(value, bool):
2084-
return {"value": value, "type": "boolean"}
2085-
2086-
if isinstance(value, int):
2087-
return {"value": value, "type": "integer"}
2088-
2089-
if isinstance(value, float):
2090-
return {"value": value, "type": "double"}
2091-
2092-
if isinstance(value, str):
2093-
return {"value": value, "type": "string"}
2094-
2095-
return {"value": safe_repr(value), "type": "string"}

0 commit comments

Comments
 (0)