18
18
from sentry_sdk.consts import DEFAULT_OPTIONS, SDK_INFO, ClientConstructor
19
19
from sentry_sdk.integrations import setup_integrations
20
20
from sentry_sdk.utils import ContextVar
21
+ from sentry_sdk.sessions import SessionFlusher
22
+ from sentry_sdk.envelope import Envelope
21
23
22
24
from sentry_sdk._types import MYPY
23
25
24
26
if MYPY:
25
27
from typing import Any
26
28
from typing import Callable
27
29
from typing import Dict
30
+ from typing import List
28
31
from typing import Optional
29
32
30
33
from sentry_sdk.scope import Scope
31
34
from sentry_sdk._types import Event, Hint
35
+ from sentry_sdk.sessions import Session
32
36
33
37
34
38
_client_init_debug = ContextVar("client_init_debug")
@@ -91,9 +95,20 @@ def __setstate__(self, state):
91
95
def _init_impl(self):
92
96
# type: () -> None
93
97
old_debug = _client_init_debug.get(False)
98
+
99
+ def _send_sessions(sessions):
100
+ # type: (List[Any]) -> None
101
+ transport = self.transport
102
+ if sessions and transport:
103
+ envelope = Envelope()
104
+ for session in sessions:
105
+ envelope.add_session(session)
106
+ transport.capture_envelope(envelope)
107
+
94
108
try:
95
109
_client_init_debug.set(self.options["debug"])
96
110
self.transport = make_transport(self.options)
111
+ self.session_flusher = SessionFlusher(flush_func=_send_sessions)
97
112
98
113
request_bodies = ("always", "never", "small", "medium")
99
114
if self.options["request_bodies"] not in request_bodies:
@@ -230,6 +245,48 @@ def _should_capture(
230
245
231
246
return True
232
247
248
+ def _update_session_from_event(
249
+ self,
250
+ session, # type: Session
251
+ event, # type: Event
252
+ ):
253
+ # type: (...) -> None
254
+
255
+ crashed = False
256
+ errored = False
257
+ user_agent = None
258
+
259
+ # Figure out if this counts as an error and if we should mark the
260
+ # session as crashed.
261
+ level = event.get("level")
262
+ if level == "fatal":
263
+ crashed = True
264
+ if not crashed:
265
+ exceptions = (event.get("exception") or {}).get("values")
266
+ if exceptions:
267
+ errored = True
268
+ for error in exceptions:
269
+ mechanism = error.get("mechanism")
270
+ if mechanism and mechanism.get("handled") is False:
271
+ crashed = True
272
+ break
273
+
274
+ user = event.get("user")
275
+
276
+ if session.user_agent is None:
277
+ headers = (event.get("request") or {}).get("headers")
278
+ for (k, v) in iteritems(headers or {}):
279
+ if k.lower() == "user-agent":
280
+ user_agent = v
281
+ break
282
+
283
+ session.update(
284
+ status="crashed" if crashed else None,
285
+ user=user,
286
+ user_agent=user_agent,
287
+ errors=session.errors + (errored or crashed),
288
+ )
289
+
233
290
def capture_event(
234
291
self,
235
292
event, # type: Event
@@ -260,9 +317,25 @@ def capture_event(
260
317
event_opt = self._prepare_event(event, hint, scope)
261
318
if event_opt is None:
262
319
return None
320
+
321
+ # whenever we capture an event we also check if the session needs
322
+ # to be updated based on that information.
323
+ session = scope.session if scope else None
324
+ if session:
325
+ self._update_session_from_event(session, event)
326
+
263
327
self.transport.capture_event(event_opt)
264
328
return event_id
265
329
330
+ def capture_session(
331
+ self, session # type: Session
332
+ ):
333
+ # type: (...) -> None
334
+ if not session.release:
335
+ logger.info("Discarded session update because of missing release")
336
+ else:
337
+ self.session_flusher.add_session(session)
338
+
266
339
def close(
267
340
self,
268
341
timeout=None, # type: Optional[float]
@@ -275,6 +348,7 @@ def close(
275
348
"""
276
349
if self.transport is not None:
277
350
self.flush(timeout=timeout, callback=callback)
351
+ self.session_flusher.kill()
278
352
self.transport.kill()
279
353
self.transport = None
280
354
@@ -294,6 +368,7 @@ def flush(
294
368
if self.transport is not None:
295
369
if timeout is None:
296
370
timeout = self.options["shutdown_timeout"]
371
+ self.session_flusher.flush()
297
372
self.transport.flush(timeout=timeout, callback=callback)
298
373
299
374
def __enter__(self):
0 commit comments