Changed code to support older Python versions
This commit is contained in:
parent
eb92d2d36f
commit
582458cdd0
5027 changed files with 794942 additions and 4 deletions
275
venv/lib/python3.11/site-packages/sentry_sdk/sessions.py
Normal file
275
venv/lib/python3.11/site-packages/sentry_sdk/sessions.py
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import os
|
||||
import warnings
|
||||
from threading import Thread, Lock, Event
|
||||
from contextlib import contextmanager
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.envelope import Envelope
|
||||
from sentry_sdk.session import Session
|
||||
from sentry_sdk.utils import format_timestamp
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
|
||||
def is_auto_session_tracking_enabled(hub=None):
|
||||
# type: (Optional[sentry_sdk.Hub]) -> Union[Any, bool, None]
|
||||
"""DEPRECATED: Utility function to find out if session tracking is enabled."""
|
||||
|
||||
# Internal callers should use private _is_auto_session_tracking_enabled, instead.
|
||||
warnings.warn(
|
||||
"This function is deprecated and will be removed in the next major release. "
|
||||
"There is no public API replacement.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if hub is None:
|
||||
hub = sentry_sdk.Hub.current
|
||||
|
||||
should_track = hub.scope._force_auto_session_tracking
|
||||
|
||||
if should_track is None:
|
||||
client_options = hub.client.options if hub.client else {}
|
||||
should_track = client_options.get("auto_session_tracking", False)
|
||||
|
||||
return should_track
|
||||
|
||||
|
||||
@contextmanager
|
||||
def auto_session_tracking(hub=None, session_mode="application"):
|
||||
# type: (Optional[sentry_sdk.Hub], str) -> Generator[None, None, None]
|
||||
"""DEPRECATED: Use track_session instead
|
||||
Starts and stops a session automatically around a block.
|
||||
"""
|
||||
warnings.warn(
|
||||
"This function is deprecated and will be removed in the next major release. "
|
||||
"Use track_session instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if hub is None:
|
||||
hub = sentry_sdk.Hub.current
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
should_track = is_auto_session_tracking_enabled(hub)
|
||||
if should_track:
|
||||
hub.start_session(session_mode=session_mode)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if should_track:
|
||||
hub.end_session()
|
||||
|
||||
|
||||
def is_auto_session_tracking_enabled_scope(scope):
|
||||
# type: (sentry_sdk.Scope) -> bool
|
||||
"""
|
||||
DEPRECATED: Utility function to find out if session tracking is enabled.
|
||||
"""
|
||||
|
||||
warnings.warn(
|
||||
"This function is deprecated and will be removed in the next major release. "
|
||||
"There is no public API replacement.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
# Internal callers should use private _is_auto_session_tracking_enabled, instead.
|
||||
return _is_auto_session_tracking_enabled(scope)
|
||||
|
||||
|
||||
def _is_auto_session_tracking_enabled(scope):
|
||||
# type: (sentry_sdk.Scope) -> bool
|
||||
"""
|
||||
Utility function to find out if session tracking is enabled.
|
||||
"""
|
||||
|
||||
should_track = scope._force_auto_session_tracking
|
||||
if should_track is None:
|
||||
client_options = sentry_sdk.get_client().options
|
||||
should_track = client_options.get("auto_session_tracking", False)
|
||||
|
||||
return should_track
|
||||
|
||||
|
||||
@contextmanager
|
||||
def auto_session_tracking_scope(scope, session_mode="application"):
|
||||
# type: (sentry_sdk.Scope, str) -> Generator[None, None, None]
|
||||
"""DEPRECATED: This function is a deprecated alias for track_session.
|
||||
Starts and stops a session automatically around a block.
|
||||
"""
|
||||
|
||||
warnings.warn(
|
||||
"This function is a deprecated alias for track_session and will be removed in the next major release.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
with track_session(scope, session_mode=session_mode):
|
||||
yield
|
||||
|
||||
|
||||
@contextmanager
|
||||
def track_session(scope, session_mode="application"):
|
||||
# type: (sentry_sdk.Scope, str) -> Generator[None, None, None]
|
||||
"""
|
||||
Start a new session in the provided scope, assuming session tracking is enabled.
|
||||
This is a no-op context manager if session tracking is not enabled.
|
||||
"""
|
||||
|
||||
should_track = _is_auto_session_tracking_enabled(scope)
|
||||
if should_track:
|
||||
scope.start_session(session_mode=session_mode)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if should_track:
|
||||
scope.end_session()
|
||||
|
||||
|
||||
TERMINAL_SESSION_STATES = ("exited", "abnormal", "crashed")
|
||||
MAX_ENVELOPE_ITEMS = 100
|
||||
|
||||
|
||||
def make_aggregate_envelope(aggregate_states, attrs):
|
||||
# type: (Any, Any) -> Any
|
||||
return {"attrs": dict(attrs), "aggregates": list(aggregate_states.values())}
|
||||
|
||||
|
||||
class SessionFlusher:
|
||||
def __init__(
|
||||
self,
|
||||
capture_func, # type: Callable[[Envelope], None]
|
||||
flush_interval=60, # type: int
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.capture_func = capture_func
|
||||
self.flush_interval = flush_interval
|
||||
self.pending_sessions = [] # type: List[Any]
|
||||
self.pending_aggregates = {} # type: Dict[Any, Any]
|
||||
self._thread = None # type: Optional[Thread]
|
||||
self._thread_lock = Lock()
|
||||
self._aggregate_lock = Lock()
|
||||
self._thread_for_pid = None # type: Optional[int]
|
||||
self.__shutdown_requested = Event()
|
||||
|
||||
def flush(self):
|
||||
# type: (...) -> None
|
||||
pending_sessions = self.pending_sessions
|
||||
self.pending_sessions = []
|
||||
|
||||
with self._aggregate_lock:
|
||||
pending_aggregates = self.pending_aggregates
|
||||
self.pending_aggregates = {}
|
||||
|
||||
envelope = Envelope()
|
||||
for session in pending_sessions:
|
||||
if len(envelope.items) == MAX_ENVELOPE_ITEMS:
|
||||
self.capture_func(envelope)
|
||||
envelope = Envelope()
|
||||
|
||||
envelope.add_session(session)
|
||||
|
||||
for attrs, states in pending_aggregates.items():
|
||||
if len(envelope.items) == MAX_ENVELOPE_ITEMS:
|
||||
self.capture_func(envelope)
|
||||
envelope = Envelope()
|
||||
|
||||
envelope.add_sessions(make_aggregate_envelope(states, attrs))
|
||||
|
||||
if len(envelope.items) > 0:
|
||||
self.capture_func(envelope)
|
||||
|
||||
def _ensure_running(self):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Check that we have an active thread to run in, or create one if not.
|
||||
|
||||
Note that this might fail (e.g. in Python 3.12 it's not possible to
|
||||
spawn new threads at interpreter shutdown). In that case self._running
|
||||
will be False after running this function.
|
||||
"""
|
||||
if self._thread_for_pid == os.getpid() and self._thread is not None:
|
||||
return None
|
||||
with self._thread_lock:
|
||||
if self._thread_for_pid == os.getpid() and self._thread is not None:
|
||||
return None
|
||||
|
||||
def _thread():
|
||||
# type: (...) -> None
|
||||
running = True
|
||||
while running:
|
||||
running = not self.__shutdown_requested.wait(self.flush_interval)
|
||||
self.flush()
|
||||
|
||||
thread = Thread(target=_thread)
|
||||
thread.daemon = True
|
||||
try:
|
||||
thread.start()
|
||||
except RuntimeError:
|
||||
# Unfortunately at this point the interpreter is in a state that no
|
||||
# longer allows us to spawn a thread and we have to bail.
|
||||
self.__shutdown_requested.set()
|
||||
return None
|
||||
|
||||
self._thread = thread
|
||||
self._thread_for_pid = os.getpid()
|
||||
|
||||
return None
|
||||
|
||||
def add_aggregate_session(
|
||||
self,
|
||||
session, # type: Session
|
||||
):
|
||||
# type: (...) -> None
|
||||
# NOTE on `session.did`:
|
||||
# the protocol can deal with buckets that have a distinct-id, however
|
||||
# in practice we expect the python SDK to have an extremely high cardinality
|
||||
# here, effectively making aggregation useless, therefore we do not
|
||||
# aggregate per-did.
|
||||
|
||||
# For this part we can get away with using the global interpreter lock
|
||||
with self._aggregate_lock:
|
||||
attrs = session.get_json_attrs(with_user_info=False)
|
||||
primary_key = tuple(sorted(attrs.items()))
|
||||
secondary_key = session.truncated_started # (, session.did)
|
||||
states = self.pending_aggregates.setdefault(primary_key, {})
|
||||
state = states.setdefault(secondary_key, {})
|
||||
|
||||
if "started" not in state:
|
||||
state["started"] = format_timestamp(session.truncated_started)
|
||||
# if session.did is not None:
|
||||
# state["did"] = session.did
|
||||
if session.status == "crashed":
|
||||
state["crashed"] = state.get("crashed", 0) + 1
|
||||
elif session.status == "abnormal":
|
||||
state["abnormal"] = state.get("abnormal", 0) + 1
|
||||
elif session.errors > 0:
|
||||
state["errored"] = state.get("errored", 0) + 1
|
||||
else:
|
||||
state["exited"] = state.get("exited", 0) + 1
|
||||
|
||||
def add_session(
|
||||
self,
|
||||
session, # type: Session
|
||||
):
|
||||
# type: (...) -> None
|
||||
if session.session_mode == "request":
|
||||
self.add_aggregate_session(session)
|
||||
else:
|
||||
self.pending_sessions.append(session.to_json())
|
||||
self._ensure_running()
|
||||
|
||||
def kill(self):
|
||||
# type: (...) -> None
|
||||
self.__shutdown_requested.set()
|
||||
Loading…
Add table
Add a link
Reference in a new issue