Skip to content

Commit edb55fa

Browse files
committed
feat(transport): Experimental sdk outcomes support
1 parent e06c9c5 commit edb55fa

File tree

3 files changed

+46
-5
lines changed

3 files changed

+46
-5
lines changed

sentry_sdk/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ def _prepare_event(
200200
new_event = before_send(event, hint or {})
201201
if new_event is None:
202202
logger.info("before send dropped event (%s)", event)
203+
self.tansport.record_lost_event("before_send")
203204
event = new_event # type: ignore
204205

205206
return event

sentry_sdk/envelope.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ def data_category(self):
236236
return "transaction"
237237
elif ty == "event":
238238
return "error"
239+
elif ty == "sdk_outcomes":
240+
return "sdk_outcomes"
239241
else:
240242
return "default"
241243

sentry_sdk/transport.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
import urllib3 # type: ignore
55
import certifi
66
import gzip
7+
import time
78

89
from datetime import datetime, timedelta
910

1011
from sentry_sdk.utils import Dsn, logger, capture_internal_exceptions, json_dumps
1112
from sentry_sdk.worker import BackgroundWorker
12-
from sentry_sdk.envelope import Envelope
13+
from sentry_sdk.envelope import Envelope, Item, PayloadRef
1314

1415
from sentry_sdk._types import MYPY
1516

@@ -131,6 +132,8 @@ def __init__(
131132
self._auth = self.parsed_dsn.to_auth("sentry.python/%s" % VERSION)
132133
self._disabled_until = {} # type: Dict[DataCategory, datetime]
133134
self._retry = urllib3.util.Retry()
135+
self._event_loss_counters = {}
136+
self._last_event_loss_sent = None
134137

135138
self._pool = self._make_pool(
136139
self.parsed_dsn,
@@ -143,6 +146,13 @@ def __init__(
143146

144147
self.hub_cls = Hub
145148

149+
def record_lost_event(self, reason):
150+
"""This increments a counter for event loss by reason. The counters are flushed
151+
periodically automatically (typically once a minute).
152+
"""
153+
# This is not locked because we are okay with small mismeasuring.
154+
self._event_loss_counters[reason] = self._event_loss_counters.get(reason, 0) + 1
155+
146156
def _update_rate_limits(self, response):
147157
# type: (urllib3.HTTPResponse) -> None
148158

@@ -207,7 +217,24 @@ def _send_request(
207217

208218
def on_dropped_event(self, reason):
209219
# type: (str) -> None
210-
pass
220+
self.record_lost_event(reason)
221+
222+
def _flush_stats(self, force=False):
223+
if not (force or self._last_event_loss_sent is None or \
224+
self._last_event_loss_sent < time.time() - 60):
225+
return
226+
outcomes = self._event_loss_counters
227+
self._event_loss_counters = {}
228+
229+
if outcomes:
230+
self.capture_envelope(Envelope(
231+
items=[Item(PayloadRef(json={
232+
"timestamp": time.time(),
233+
"outcomes": outcomes,
234+
}), type="sdk_outomes")],
235+
))
236+
237+
self._last_event_loss_sent = time.time()
211238

212239
def _check_disabled(self, category):
213240
# type: (str) -> bool
@@ -254,9 +281,15 @@ def _send_envelope(
254281
# type: (...) -> None
255282

256283
# remove all items from the envelope which are over quota
257-
envelope.items[:] = [
258-
x for x in envelope.items if not self._check_disabled(x.data_category)
259-
]
284+
new_items = []
285+
for item in envelope.items:
286+
if self._check_disabled(item.data_category):
287+
if item.data_category in ("transaction", "error", "default"):
288+
self.on_dropped_event("self_rate_limits")
289+
else:
290+
new_items.append(item)
291+
292+
envelope.items[:] = new_items
260293
if not envelope.items:
261294
return None
262295

@@ -341,6 +374,8 @@ def send_event_wrapper():
341374
if not self._worker.submit(send_event_wrapper):
342375
self.on_dropped_event("full_queue")
343376

377+
self._flush_stats()
378+
344379
def capture_envelope(
345380
self, envelope # type: Envelope
346381
):
@@ -356,13 +391,16 @@ def send_envelope_wrapper():
356391
if not self._worker.submit(send_envelope_wrapper):
357392
self.on_dropped_event("full_queue")
358393

394+
self._flush_stats()
395+
359396
def flush(
360397
self,
361398
timeout, # type: float
362399
callback=None, # type: Optional[Any]
363400
):
364401
# type: (...) -> None
365402
logger.debug("Flushing HTTP transport")
403+
self._flush_stats(force=True)
366404
if timeout > 0:
367405
self._worker.flush(timeout, callback)
368406

0 commit comments

Comments
 (0)