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