1818from sentry_sdk .consts import DEFAULT_OPTIONS , SDK_INFO , ClientConstructor
1919from sentry_sdk .integrations import setup_integrations
2020from sentry_sdk .utils import ContextVar
21+ from sentry_sdk .sessions import SessionFlusher
22+ from sentry_sdk .envelope import Envelope
2123
2224from sentry_sdk ._types import MYPY
2325
2426if MYPY :
2527 from typing import Any
2628 from typing import Callable
2729 from typing import Dict
30+ from typing import List
2831 from typing import Optional
2932
3033 from sentry_sdk .scope import Scope
3134 from sentry_sdk ._types import Event , Hint
35+ from sentry_sdk .sessions import Session
3236
3337
3438_client_init_debug = ContextVar ("client_init_debug" )
@@ -91,9 +95,20 @@ def __setstate__(self, state):
9195 def _init_impl (self ):
9296 # type: () -> None
9397 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+
94108 try :
95109 _client_init_debug .set (self .options ["debug" ])
96110 self .transport = make_transport (self .options )
111+ self .session_flusher = SessionFlusher (flush_func = _send_sessions )
97112
98113 request_bodies = ("always" , "never" , "small" , "medium" )
99114 if self .options ["request_bodies" ] not in request_bodies :
@@ -230,6 +245,48 @@ def _should_capture(
230245
231246 return True
232247
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+
233290 def capture_event (
234291 self ,
235292 event , # type: Event
@@ -260,9 +317,25 @@ def capture_event(
260317 event_opt = self ._prepare_event (event , hint , scope )
261318 if event_opt is None :
262319 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+
263327 self .transport .capture_event (event_opt )
264328 return event_id
265329
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+
266339 def close (
267340 self ,
268341 timeout = None , # type: Optional[float]
@@ -275,6 +348,7 @@ def close(
275348 """
276349 if self .transport is not None :
277350 self .flush (timeout = timeout , callback = callback )
351+ self .session_flusher .kill ()
278352 self .transport .kill ()
279353 self .transport = None
280354
@@ -294,6 +368,7 @@ def flush(
294368 if self .transport is not None :
295369 if timeout is None :
296370 timeout = self .options ["shutdown_timeout" ]
371+ self .session_flusher .flush ()
297372 self .transport .flush (timeout = timeout , callback = callback )
298373
299374 def __enter__ (self ):
0 commit comments