Skip to content

Commit b8111be

Browse files
committed
Merge branch 'master' into major/3.0
2 parents 74402a2 + 4e4ea83 commit b8111be

44 files changed

Lines changed: 4239 additions & 664 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/populate_tox/package_dependencies.jsonl

Lines changed: 81 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sentry_sdk/_span_batcher.py

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,12 @@ class SpanBatcher(Batcher["SpanJSON"]):
2121
# MAX_BEFORE_FLUSH should be lower than MAX_BEFORE_DROP, so that there is
2222
# a bit of a buffer for spans that appear between the trigger to flush
2323
# and actually flushing the buffer.
24+
#
25+
# The max limits are all per trace (per bucket).
2426
MAX_ENVELOPE_SIZE = 1000 # spans
25-
2627
MAX_BEFORE_FLUSH = 1000
27-
GLOBAL_MAX_BEFORE_FLUSH = 5_000
28-
2928
MAX_BEFORE_DROP = 2000
30-
GLOBAL_MAX_BEFORE_DROP = 10_000
31-
3229
MAX_BYTES_BEFORE_FLUSH = 5 * 1024 * 1024 # 5 MB
33-
GLOBAL_MAX_BYTES_BEFORE_FLUSH = 25 * 1024 * 1024 # 25 MB
3430

3531
FLUSH_WAIT_TIME = 5.0
3632

@@ -48,11 +44,7 @@ def __init__(
4844
# envelope.
4945
# trace_id -> span buffer
5046
self._span_buffer: dict[str, list["SpanJSON"]] = defaultdict(list)
51-
self._span_number: int = 0
52-
5347
self._running_size: dict[str, int] = defaultdict(lambda: 0)
54-
self._total_running_size: int = 0
55-
5648
self._capture_func = capture_func
5749
self._record_lost_func = record_lost_func
5850
self._running = True
@@ -79,11 +71,7 @@ def _reset_in_child() -> None:
7971

8072
def _reset_thread_state(self) -> None:
8173
self._span_buffer = defaultdict(list)
82-
self._span_number = 0
83-
8474
self._running_size = defaultdict(lambda: 0)
85-
self._total_running_size = 0
86-
8775
self._running = True
8876

8977
self._lock = threading.Lock()
@@ -106,12 +94,8 @@ def _flush_loop(self) -> None:
10694
self._flush(only_pending=True)
10795

10896
if (
109-
self._span_number >= self.GLOBAL_MAX_BEFORE_FLUSH
110-
or self._total_running_size >= self.GLOBAL_MAX_BYTES_BEFORE_FLUSH
111-
or (
112-
time.monotonic() - self._last_full_flush
113-
>= self.FLUSH_WAIT_TIME + jitter
114-
)
97+
time.monotonic() - self._last_full_flush
98+
>= self.FLUSH_WAIT_TIME + jitter
11599
):
116100
self._flush()
117101
self._last_full_flush = time.monotonic()
@@ -132,10 +116,8 @@ def add(self, span: "SpanJSON") -> None:
132116
return None
133117

134118
with self._lock:
135-
if (
136-
self._span_number >= self.GLOBAL_MAX_BEFORE_DROP
137-
or len(self._span_buffer[span["trace_id"]]) >= self.MAX_BEFORE_DROP
138-
):
119+
size = len(self._span_buffer[span["trace_id"]])
120+
if size >= self.MAX_BEFORE_DROP:
139121
self._record_lost_func(
140122
reason="queue_overflow",
141123
data_category="span",
@@ -144,25 +126,17 @@ def add(self, span: "SpanJSON") -> None:
144126
return None
145127

146128
self._span_buffer[span["trace_id"]].append(span)
147-
self._span_number += 1
148-
149-
estimated_size = self._estimate_size(span)
150-
self._running_size[span["trace_id"]] += estimated_size
151-
self._total_running_size += estimated_size
129+
self._running_size[span["trace_id"]] += self._estimate_size(span)
152130

153131
if (
154-
len(self._span_buffer[span["trace_id"]]) >= self.MAX_BEFORE_FLUSH
132+
size + 1 >= self.MAX_BEFORE_FLUSH
155133
or self._running_size[span["trace_id"]]
156134
>= self.MAX_BYTES_BEFORE_FLUSH
157135
):
158136
self._pending_flush.add(span["trace_id"])
159137
notify = True
160138
else:
161-
notify = (
162-
self._span_number >= self.GLOBAL_MAX_BEFORE_FLUSH
163-
or self._total_running_size
164-
>= self.GLOBAL_MAX_BYTES_BEFORE_FLUSH
165-
)
139+
notify = False
166140

167141
if notify:
168142
self._flush_event.set()
@@ -253,10 +227,7 @@ def _flush(self, only_pending: bool = False) -> None:
253227

254228
envelopes.append(envelope)
255229

256-
self._span_number -= len(self._span_buffer[bucket_id])
257230
del self._span_buffer[bucket_id]
258-
259-
self._total_running_size -= self._running_size[bucket_id]
260231
del self._running_size[bucket_id]
261232

262233
for envelope in envelopes:

sentry_sdk/client.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import TYPE_CHECKING, Dict, List, cast, overload
1414

1515
from sentry_sdk._compat import check_uwsgi_thread_support
16+
from sentry_sdk._log_batcher import LogBatcher
1617
from sentry_sdk._metrics_batcher import MetricsBatcher
1718
from sentry_sdk._span_batcher import SpanBatcher
1819
from sentry_sdk.consts import (
@@ -58,8 +59,6 @@
5859
get_type_name,
5960
handle_in_app,
6061
has_data_collection_enabled,
61-
has_logs_enabled,
62-
has_metrics_enabled,
6362
logger,
6463
)
6564

@@ -629,22 +628,27 @@ def _record_lost_event(
629628

630629
self.session_flusher = SessionFlusher(capture_func=_capture_envelope)
631630

632-
self.log_batcher = None
631+
if self.options.get("enable_logs", False) or self.options[
632+
"_experiments"
633+
].get("enable_logs", False):
634+
logger.warning(
635+
"The enable_logs option has no effect and will be removed in the next major."
636+
)
633637

634-
if has_logs_enabled(self.options):
635-
from sentry_sdk._log_batcher import LogBatcher
638+
self.log_batcher = LogBatcher(
639+
capture_func=_capture_envelope,
640+
record_lost_func=_record_lost_event,
641+
)
636642

637-
self.log_batcher = LogBatcher(
638-
capture_func=_capture_envelope,
639-
record_lost_func=_record_lost_event,
643+
if self.options.get("enable_metrics", True) is False:
644+
logger.warning(
645+
"The enable_metrics option has no effect and will be removed in the next major."
640646
)
641647

642-
self.metrics_batcher = None
643-
if has_metrics_enabled(self.options):
644-
self.metrics_batcher = MetricsBatcher(
645-
capture_func=_capture_envelope,
646-
record_lost_func=_record_lost_event,
647-
)
648+
self.metrics_batcher = MetricsBatcher(
649+
capture_func=_capture_envelope,
650+
record_lost_func=_record_lost_event,
651+
)
648652

649653
self.span_batcher = None
650654
if has_span_streaming_enabled(self.options):

sentry_sdk/integrations/aiohttp.py

Lines changed: 80 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
TransactionSource,
3737
)
3838
from sentry_sdk.tracing_utils import (
39+
add_http_breadcrumb,
3940
add_http_request_source,
4041
has_span_streaming_enabled,
4142
should_propagate_trace,
@@ -378,57 +379,64 @@ async def on_request_start(
378379
with capture_internal_exceptions():
379380
parsed_url = parse_url(str(params.url), sanitize=False)
380381

382+
breadcrumb = {}
383+
381384
span_name = "%s %s" % (
382385
method,
383386
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
384387
)
385388

386-
span: "Union[Span, StreamedSpan, None]"
389+
span: "Union[Span, StreamedSpan, None]" = None
387390
if has_span_streaming_enabled(client.options):
388-
if sentry_sdk.traces.get_current_span() is None:
389-
span = None
390-
else:
391-
attributes: "Attributes" = {
392-
"sentry.op": OP.HTTP_CLIENT,
393-
"sentry.origin": AioHttpIntegration.origin,
394-
"http.request.method": method,
395-
}
396-
if parsed_url is not None:
397-
if has_data_collection_enabled(client.options):
398-
url_full = parsed_url.url
399-
attributes["url.path"] = params.url.path
400-
401-
if parsed_url.query:
402-
filtered_query = (
403-
_apply_data_collection_filtering_to_query_string(
404-
query_string=parsed_url.query,
405-
behaviour=client.options["data_collection"][
406-
"url_query_params"
407-
],
408-
)
391+
attributes: "Attributes" = {
392+
"sentry.op": OP.HTTP_CLIENT,
393+
"sentry.origin": AioHttpIntegration.origin,
394+
"http.request.method": method,
395+
}
396+
if parsed_url is not None:
397+
if has_data_collection_enabled(client.options):
398+
url_full = parsed_url.url
399+
attributes["url.path"] = params.url.path
400+
401+
if parsed_url.query:
402+
filtered_query = (
403+
_apply_data_collection_filtering_to_query_string(
404+
query_string=parsed_url.query,
405+
behaviour=client.options["data_collection"][
406+
"url_query_params"
407+
],
409408
)
410-
if filtered_query:
411-
attributes["url.query"] = filtered_query
412-
url_full += "?" + filtered_query
413-
414-
if parsed_url.fragment:
415-
attributes["url.fragment"] = parsed_url.fragment
416-
url_full += "#" + parsed_url.fragment
417-
418-
attributes["url.full"] = url_full
419-
elif should_send_default_pii():
420-
url_full = parsed_url.url
421-
attributes["url.path"] = params.url.path
422-
423-
if parsed_url.query:
424-
url_full += "?" + parsed_url.query
425-
attributes["url.query"] = parsed_url.query
426-
if parsed_url.fragment:
427-
url_full += "#" + parsed_url.fragment
428-
attributes["url.fragment"] = parsed_url.fragment
429-
430-
attributes["url.full"] = url_full
431-
409+
)
410+
if filtered_query:
411+
attributes["url.query"] = filtered_query
412+
url_full += "?" + filtered_query
413+
breadcrumb[SPANDATA.HTTP_QUERY] = filtered_query
414+
415+
if parsed_url.fragment:
416+
attributes["url.fragment"] = parsed_url.fragment
417+
url_full += "#" + parsed_url.fragment
418+
breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
419+
420+
attributes["url.full"] = url_full
421+
breadcrumb["url"] = url_full
422+
423+
elif should_send_default_pii():
424+
url_full = parsed_url.url
425+
attributes["url.path"] = params.url.path
426+
427+
if parsed_url.query:
428+
url_full += "?" + parsed_url.query
429+
attributes["url.query"] = parsed_url.query
430+
breadcrumb[SPANDATA.HTTP_QUERY] = parsed_url.query
431+
if parsed_url.fragment:
432+
url_full += "#" + parsed_url.fragment
433+
attributes["url.fragment"] = parsed_url.fragment
434+
breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment
435+
436+
attributes["url.full"] = url_full
437+
breadcrumb["url"] = url_full
438+
439+
if sentry_sdk.traces.get_current_span() is not None:
432440
span = sentry_sdk.traces.start_span(
433441
name=span_name, attributes=attributes
434442
)
@@ -443,6 +451,13 @@ async def on_request_start(
443451
legacy_span.set_data("url", parsed_url.url)
444452
legacy_span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
445453
legacy_span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
454+
breadcrumb.update(
455+
{
456+
SPANDATA.HTTP_QUERY: parsed_url.query,
457+
SPANDATA.HTTP_FRAGMENT: parsed_url.fragment,
458+
"url": parsed_url.url,
459+
}
460+
)
446461
span = legacy_span
447462

448463
if should_propagate_trace(client, str(params.url)):
@@ -465,19 +480,35 @@ async def on_request_start(
465480
else:
466481
params.headers[key] = value
467482

468-
trace_config_ctx.span = span
483+
trace_config_ctx._sentry_span = span
484+
trace_config_ctx._sentry_breadcrumb = breadcrumb
469485

470486
async def on_request_end(
471487
session: "ClientSession",
472488
trace_config_ctx: "SimpleNamespace",
473489
params: "TraceRequestEndParams",
474490
) -> None:
475-
if trace_config_ctx.span is None:
476-
return
477-
478-
span = trace_config_ctx.span
479491
status = int(params.response.status)
480492

493+
breadcrumb = getattr(trace_config_ctx, "_sentry_breadcrumb", None)
494+
if breadcrumb is not None:
495+
breadcrumb.update(
496+
{
497+
SPANDATA.HTTP_METHOD: params.method.upper(),
498+
SPANDATA.HTTP_STATUS_CODE: status,
499+
"reason": params.response.reason,
500+
}
501+
)
502+
503+
add_http_breadcrumb(
504+
status,
505+
breadcrumb,
506+
)
507+
508+
span = getattr(trace_config_ctx, "_sentry_span", None)
509+
if span is None:
510+
return
511+
481512
if isinstance(span, StreamedSpan):
482513
span.set_attribute("http.response.status_code", status)
483514
span.status = (

sentry_sdk/integrations/anthropic.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -609,14 +609,16 @@ def _set_output_data(
609609
set_on_span(SPANDATA.GEN_AI_RESPONSE_FINISH_REASONS, [finish_reason])
610610

611611
client = sentry_sdk.get_client()
612+
record_inputs = False
612613
record_outputs = False
613614
if has_data_collection_enabled(client.options):
614-
if client.options["data_collection"]["gen_ai"]["outputs"]:
615-
record_outputs = True
615+
record_inputs = client.options["data_collection"]["gen_ai"]["inputs"]
616+
record_outputs = client.options["data_collection"]["gen_ai"]["outputs"]
616617
elif should_send_default_pii() and integration.include_prompts:
618+
record_inputs = True
617619
record_outputs = True
618620

619-
if record_outputs:
621+
if record_inputs or record_outputs:
620622
output_messages: "dict[str, list[Any]]" = {
621623
"response": [],
622624
"tool": [],
@@ -628,15 +630,15 @@ def _set_output_data(
628630
elif output["type"] == "tool_use":
629631
output_messages["tool"].append(output)
630632

631-
if len(output_messages["tool"]) > 0:
633+
if record_inputs and len(output_messages["tool"]) > 0:
632634
set_data_normalized(
633635
span,
634636
SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS,
635637
output_messages["tool"],
636638
unpack=False,
637639
)
638640

639-
if len(output_messages["response"]) > 0:
641+
if record_outputs and len(output_messages["response"]) > 0:
640642
set_data_normalized(
641643
span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"]
642644
)

0 commit comments

Comments
 (0)