Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New processor system #34

Merged
merged 9 commits into from
Aug 30, 2018
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
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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to be unused.


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