Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 11 additions & 17 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import uuid
import random
from datetime import datetime
from itertools import islice
import socket

from sentry_sdk._compat import string_types, text_type, iteritems
Expand Down Expand Up @@ -30,12 +29,11 @@
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional

from sentry_sdk.scope import Scope
from sentry_sdk._types import Event, Hint
from sentry_sdk.sessions import Session
from sentry_sdk.session import Session


_client_init_debug = ContextVar("client_init_debug")
Expand Down Expand Up @@ -99,24 +97,20 @@ def _init_impl(self):
# type: () -> None
old_debug = _client_init_debug.get(False)

def _send_sessions(sessions):
# type: (List[Any]) -> None
transport = self.transport
if not transport or not sessions:
return
sessions_iter = iter(sessions)
while True:
envelope = Envelope()
for session in islice(sessions_iter, 100):
envelope.add_session(session)
if not envelope.items:
break
transport.capture_envelope(envelope)
def _capture_envelope(envelope):
# type: (Envelope) -> None
if self.transport is not None:
self.transport.capture_envelope(envelope)

try:
_client_init_debug.set(self.options["debug"])
self.transport = make_transport(self.options)
self.session_flusher = SessionFlusher(flush_func=_send_sessions)
session_mode = self.options["_experiments"].get(
"session_mode", "application"
)
self.session_flusher = SessionFlusher(
capture_func=_capture_envelope, session_mode=session_mode
)

request_bodies = ("always", "never", "small", "medium")
if self.options["request_bodies"] not in request_bodies:
Expand Down
8 changes: 7 additions & 1 deletion sentry_sdk/envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from sentry_sdk._compat import text_type
from sentry_sdk._types import MYPY
from sentry_sdk.sessions import Session
from sentry_sdk.session import Session
from sentry_sdk.utils import json_dumps, capture_internal_exceptions

if MYPY:
Expand Down Expand Up @@ -62,6 +62,12 @@ def add_session(
session = session.to_json()
self.add_item(Item(payload=PayloadRef(json=session), type="session"))

def add_sessions(
self, sessions # type: Any
):
# type: (...) -> None
self.add_item(Item(payload=PayloadRef(json=sessions), type="sessions"))

def add_item(
self, item # type: Item
):
Expand Down
5 changes: 3 additions & 2 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from sentry_sdk.scope import Scope
from sentry_sdk.client import Client
from sentry_sdk.tracing import Span, Transaction
from sentry_sdk.sessions import Session
from sentry_sdk.session import Session
from sentry_sdk.utils import (
exc_info_from_error,
event_from_exception,
Expand Down Expand Up @@ -639,11 +639,12 @@ def end_session(self):
"""Ends the current session if there is one."""
client, scope = self._stack[-1]
session = scope._session
self.scope._session = None

if session is not None:
session.close()
if client is not None:
client.capture_session(session)
self.scope._session = None

def stop_auto_session_tracking(self):
# type: (...) -> None
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
)

from sentry_sdk.tracing import Span
from sentry_sdk.sessions import Session
from sentry_sdk.session import Session

F = TypeVar("F", bound=Callable[..., Any])
T = TypeVar("T")
Expand Down
172 changes: 172 additions & 0 deletions sentry_sdk/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import uuid
from datetime import datetime

from sentry_sdk._types import MYPY
from sentry_sdk.utils import format_timestamp

if MYPY:
from typing import Optional
from typing import Union
from typing import Any
from typing import Dict

from sentry_sdk._types import SessionStatus


def _minute_trunc(ts):
# type: (datetime) -> datetime
return ts.replace(second=0, microsecond=0)


def _make_uuid(
val, # type: Union[str, uuid.UUID]
):
# type: (...) -> uuid.UUID
if isinstance(val, uuid.UUID):
return val
return uuid.UUID(val)


class Session(object):
def __init__(
self,
sid=None, # type: Optional[Union[str, uuid.UUID]]
did=None, # type: Optional[str]
timestamp=None, # type: Optional[datetime]
started=None, # type: Optional[datetime]
duration=None, # type: Optional[float]
status=None, # type: Optional[SessionStatus]
release=None, # type: Optional[str]
environment=None, # type: Optional[str]
user_agent=None, # type: Optional[str]
ip_address=None, # type: Optional[str]
errors=None, # type: Optional[int]
user=None, # type: Optional[Any]
):
# type: (...) -> None
if sid is None:
sid = uuid.uuid4()
if started is None:
started = datetime.utcnow()
if status is None:
status = "ok"
self.status = status
self.did = None # type: Optional[str]
self.started = started
self.release = None # type: Optional[str]
self.environment = None # type: Optional[str]
self.duration = None # type: Optional[float]
self.user_agent = None # type: Optional[str]
self.ip_address = None # type: Optional[str]
self.errors = 0

self.update(
sid=sid,
did=did,
timestamp=timestamp,
duration=duration,
release=release,
environment=environment,
user_agent=user_agent,
ip_address=ip_address,
errors=errors,
user=user,
)

@property
def truncated_started(self):
# type: (...) -> datetime
return _minute_trunc(self.started)

def update(
self,
sid=None, # type: Optional[Union[str, uuid.UUID]]
did=None, # type: Optional[str]
timestamp=None, # type: Optional[datetime]
started=None, # type: Optional[datetime]
duration=None, # type: Optional[float]
status=None, # type: Optional[SessionStatus]
release=None, # type: Optional[str]
environment=None, # type: Optional[str]
user_agent=None, # type: Optional[str]
ip_address=None, # type: Optional[str]
errors=None, # type: Optional[int]
user=None, # type: Optional[Any]
):
# type: (...) -> None
# If a user is supplied we pull some data form it
if user:
if ip_address is None:
ip_address = user.get("ip_address")
if did is None:
did = user.get("id") or user.get("email") or user.get("username")

if sid is not None:
self.sid = _make_uuid(sid)
if did is not None:
self.did = str(did)
if timestamp is None:
timestamp = datetime.utcnow()
self.timestamp = timestamp
if started is not None:
self.started = started
if duration is not None:
self.duration = duration
if release is not None:
self.release = release
if environment is not None:
self.environment = environment
if ip_address is not None:
self.ip_address = ip_address
if user_agent is not None:
self.user_agent = user_agent
if errors is not None:
self.errors = errors

if status is not None:
self.status = status

def close(
self, status=None # type: Optional[SessionStatus]
):
# type: (...) -> Any
if status is None and self.status == "ok":
status = "exited"
if status is not None:
self.update(status=status)

def get_json_attrs(
self, with_user_info=True # type: Optional[bool]
):
# type: (...) -> Any
attrs = {}
if self.release is not None:
attrs["release"] = self.release
if self.environment is not None:
attrs["environment"] = self.environment
if with_user_info:
if self.ip_address is not None:
attrs["ip_address"] = self.ip_address
if self.user_agent is not None:
attrs["user_agent"] = self.user_agent
return attrs

def to_json(self):
# type: (...) -> Any
rv = {
"sid": str(self.sid),
"init": True,
"started": format_timestamp(self.started),
"timestamp": format_timestamp(self.timestamp),
"status": self.status,
} # type: Dict[str, Any]
if self.errors:
rv["errors"] = self.errors
if self.did is not None:
rv["did"] = self.did
if self.duration is not None:
rv["duration"] = self.duration
attrs = self.get_json_attrs()
if attrs:
rv["attrs"] = attrs
return rv
Loading