Skip to content

Commit

Permalink
feat: New processor system (getsentry#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored Aug 30, 2018
1 parent 98399ca commit 6057951
Show file tree
Hide file tree
Showing 22 changed files with 543 additions and 450 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ dist:
test: .venv
@pip install -r test-requirements.txt
@pip install --editable .
@pytest tests
@pytest tests --tb=short
.PHONY: test

format:
@black sentry_sdk tests
.PHONY: format

tox-test:
@sh ./scripts/runtox.sh
.PHONY: tox-test
2 changes: 2 additions & 0 deletions sentry_sdk/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
text_type = unicode # noqa
import Queue as queue # noqa

string_types = (str, text_type)
number_types = (int, long, float) # noqa

def implements_str(cls):
Expand All @@ -29,6 +30,7 @@ def implements_iterator(cls):
import queue # noqa

text_type = str
string_types = (text_type,)
number_types = (int, float)

def _identity(x):
Expand Down
24 changes: 20 additions & 4 deletions sentry_sdk/api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .hub import Hub
from .client import Client
from .client import Client, get_options
from .integrations import setup_integrations


class _InitGuard(object):
Expand All @@ -15,12 +16,27 @@ def __exit__(self, exc_type, exc_value, tb):
c.close()


def init(*args, **kwargs):
client = Client(*args, **kwargs)
Hub.main.bind_client(client)
def _init_on_hub(hub, args, kwargs):
options = get_options(*args, **kwargs)
install = setup_integrations(options)
client = Client(options)
hub.bind_client(client)
install()
return _InitGuard(client)


def init(*args, **kwargs):
"""Initializes the SDK and optionally integrations."""
return _init_on_hub(Hub.main, args, kwargs)


def _init_on_current(*args, **kwargs):
# This function only exists to support unittests. Do not call it as
# initializing integrations on anything but the main hub is not going
# to yield the results you expect.
return _init_on_hub(Hub.current, args, kwargs)


from . import minimal as sentry_minimal

__all__ = ["Hub", "Scope", "Client", "init"] + sentry_minimal.__all__
Expand Down
149 changes: 92 additions & 57 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,57 @@
import uuid
import random
import atexit

from .utils import Dsn, SkipEvent, ContextVar
import weakref
from datetime import datetime

from ._compat import string_types
from .utils import (
strip_event,
flatten_metadata,
convert_types,
handle_in_app,
get_type_name,
pop_hidden_keys,
Dsn,
)
from .transport import Transport
from .consts import DEFAULT_OPTIONS, SDK_INFO
from .event import strip_event, flatten_metadata, convert_types, Event


NO_DSN = object()

_most_recent_exception = ContextVar("sentry_most_recent_exception")

def get_options(*args, **kwargs):
if args and (isinstance(args[0], string_types) or args[0] is None):
dsn = args[0]
args = args[1:]
else:
dsn = None

rv = dict(DEFAULT_OPTIONS)
options = dict(*args, **kwargs)
if dsn is not None and options.get("dsn") is None:
options["dsn"] = dsn

for key, value in options.items():
if key not in rv:
raise TypeError("Unknown option %r" % (key,))
rv[key] = value

def _get_default_integrations():
from .integrations.logging import LoggingIntegration
from .integrations.excepthook import ExcepthookIntegration
if rv["dsn"] is None:
rv["dsn"] = os.environ.get("SENTRY_DSN")

yield LoggingIntegration
yield ExcepthookIntegration
return rv


class Client(object):
def __init__(self, dsn=None, *args, **kwargs):
passed_dsn = dsn
if dsn is NO_DSN:
dsn = None
else:
if dsn is None:
dsn = os.environ.get("SENTRY_DSN")
if not dsn:
dsn = None
else:
dsn = Dsn(dsn)
options = dict(DEFAULT_OPTIONS)
options.update(*args, **kwargs)
def __init__(self, *args, **kwargs):
options = get_options(*args, **kwargs)

dsn = options["dsn"]
if dsn is not None:
dsn = Dsn(dsn)

self.options = options
self._transport = self.options.pop("transport")
if self._transport is None and dsn is not None:
Expand All @@ -45,18 +62,6 @@ def __init__(self, dsn=None, *args, **kwargs):
https_proxy=self.options.pop("https_proxy"),
)
self._transport.start()
elif passed_dsn is not None and self._transport is not None:
raise ValueError("Cannot pass DSN and a custom transport.")

integrations = list(options.pop("integrations") or ())

if options["default_integrations"]:
for cls in _get_default_integrations():
if not any(isinstance(x, cls) for x in integrations):
integrations.append(cls())

for integration in integrations:
integration(self)

request_bodies = ("always", "never", "small", "medium")
if options["request_bodies"] not in request_bodies:
Expand All @@ -66,6 +71,8 @@ def __init__(self, dsn=None, *args, **kwargs):
)
)

self._exceptions_seen = weakref.WeakKeyDictionary()

atexit.register(self.close)

@property
Expand All @@ -80,11 +87,13 @@ def disabled(cls):
return cls(NO_DSN)

def _prepare_event(self, event, scope):
if event.get("event_id") is None:
event["event_id"] = uuid.uuid4().hex
if event.get("timestamp") is None:
event["timestamp"] = datetime.utcnow()

if scope is not None:
scope.apply_to_event(event)
event = scope.apply_to_event(event)
if event is None:
return

for key in "release", "environment", "server_name", "repos", "dist":
if event.get(key) is None:
Expand All @@ -95,41 +104,67 @@ def _prepare_event(self, event, scope):
if event.get("platform") is None:
event["platform"] = "python"

event = handle_in_app(
event, self.options["in_app_exclude"], self.options["in_app_include"]
)
event = strip_event(event)
event = flatten_metadata(event)
event = convert_types(event)

before_send = self.options["before_send"]
if before_send is not None:
event = before_send(event)

if event is not None:
pop_hidden_keys(event)
event = flatten_metadata(event)
event = convert_types(event)

return event

def _check_should_capture(self, event):
def _is_ignored_error(self, event):
exc_info = event.get("__sentry_exc_info")

if not exc_info or exc_info[0] is None:
return False

type_name = get_type_name(exc_info[0])
full_name = "%s.%s" % (exc_info[0].__module__, type_name)

for errcls in self.options["ignore_errors"]:
# String types are matched against the type name in the
# exception only
if isinstance(errcls, string_types):
if errcls == full_name or errcls == type_name:
return True
else:
if issubclass(exc_info[0], errcls):
return True

return False

def _should_capture(self, event, scope=None):
if (
self.options["sample_rate"] < 1.0
and random.random() >= self.options["sample_rate"]
):
raise SkipEvent()
return False

if event._exc_value is not None:
exclusions = self.options["ignore_errors"]
exc_type = type(event._exc_value)
if self._is_ignored_error(event):
return False

if any(issubclass(exc_type, e) for e in exclusions):
raise SkipEvent()

if _most_recent_exception.get(None) is event._exc_value:
raise SkipEvent()
_most_recent_exception.set(event._exc_value)
return True

def capture_event(self, event, scope=None):
"""Captures an event."""
if self._transport is None:
return
if not isinstance(event, Event):
event = Event(event)
try:
self._check_should_capture(event)
rv = event.get("event_id")
if rv is None:
event["event_id"] = rv = uuid.uuid4().hex
if self._should_capture(event, scope):
event = self._prepare_event(event, scope)
except SkipEvent:
return
self._transport.capture_event(event)
if event is not None:
self._transport.capture_event(event)
return True

def drain_events(self, timeout=None):
if timeout is None:
Expand Down
6 changes: 5 additions & 1 deletion sentry_sdk/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
VERSION = "0.1"
DEFAULT_SERVER_NAME = socket.gethostname() if hasattr(socket, "gethostname") else None
DEFAULT_OPTIONS = {
"dsn": None,
"with_locals": True,
"max_breadcrumbs": 100,
"release": None,
"environment": None,
"server_name": DEFAULT_SERVER_NAME,
"shutdown_timeout": 2.0,
"integrations": [],
"in_app_include": [],
"in_app_exclude": [],
"default_integrations": True,
"repos": {},
"dist": None,
Expand All @@ -18,8 +21,9 @@
"send_default_pii": False,
"http_proxy": None,
"https_proxy": None,
"ignore_errors": (),
"ignore_errors": [],
"request_bodies": "medium",
"before_send": None,
}

SDK_INFO = {"name": "sentry-python", "version": VERSION}
Loading

0 comments on commit 6057951

Please sign in to comment.