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
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ ignore = E203, E266, E501, W503, E402, E731
max-line-length = 80
max-complexity = 18
select = B,C,E,F,W,T4,B9
exclude=checkouts,lol*,.tox
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ venv
.vscode/tags
.pytest_cache
.hypothesis
checkouts
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ python:
- "3.6"
- "3.7-dev"

cache:
pip: true
directories:
- checkouts/semaphore/target/debug/
- ~/.cargo/registry/
- ~/.rustup/

branches:
only:
- master
Expand All @@ -26,7 +33,10 @@ matrix:
- zeus upload -t "application/zip+wheel" dist/*

install:
- curl https://sh.rustup.rs -sSf | sh -s -- -y
- . $HOME/.cargo/env
- pip install tox
- sh scripts/checkout-semaphore.sh

script:
- sh scripts/runtox.sh
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.black]
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| checkouts
)/
'''
23 changes: 23 additions & 0 deletions scripts/checkout-semaphore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh

# This script is able to restore partially checked out repositories,
# repositories that have all files but no git content, repositories checked out
# on the wrong branch, etc.
#
# This is mainly useful because Travis creates an empty folder in the attempt
# to restore the build cache.

set -xe

if [ ! -d checkouts/semaphore/.git ]; then
mkdir -p checkouts/semaphore/
fi

cd checkouts/semaphore/
git init
git remote remove origin || true
git remote add origin https://github.com/getsentry/semaphore
git fetch
git reset --hard origin/master
git clean -f
cargo build
4 changes: 2 additions & 2 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import random
import atexit

from .utils import Dsn, SkipEvent, ContextVar, Event
from .utils import Dsn, SkipEvent, ContextVar
from .transport import Transport
from .consts import DEFAULT_OPTIONS, SDK_INFO
from .stripping import strip_event, flatten_metadata
from .event import strip_event, flatten_metadata, Event


NO_DSN = object()
Expand Down
62 changes: 62 additions & 0 deletions sentry_sdk/stripping.py → sentry_sdk/event.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,70 @@
import uuid
from datetime import datetime

from collections import Mapping, Sequence

from .utils import exceptions_from_error_tuple
from ._compat import text_type


def datetime_to_json(dt):
return dt.strftime("%Y-%m-%dT%H:%M:%SZ")


class Event(Mapping):
__slots__ = ("_data", "_exc_value")

def __init__(self, data={}):
self._data = {
"event_id": uuid.uuid4().hex,
"timestamp": datetime_to_json(datetime.utcnow()),
"level": "error",
}

self._data.update(data)

self._exc_value = None

def set_exception(self, exc_type, exc_value, tb, with_locals):
self["exception"] = {
"values": exceptions_from_error_tuple(exc_type, exc_value, tb, with_locals)
}
self._exc_value = exc_value

def __getitem__(self, key):
return self._data[key]

def __contains__(self, key):
return key in self._data

def get(self, *a, **kw):
return self._data.get(*a, **kw)

def setdefault(self, *a, **kw):
return self._data.setdefault(*a, **kw)

def __setitem__(self, key, value):
self._data[key] = value

def __iter__(self):
return iter(self._data)

def __len__(self):
return len(self._data)

def iter_frames(self):
stacktraces = []
if "stacktrace" in self:
stacktraces.append(self["stacktrace"])
if "exception" in self:
for exception in self["exception"].get("values") or ():
if "stacktrace" in exception:
stacktraces.append(exception["stacktrace"])
for stacktrace in stacktraces:
for frame in stacktrace.get("frames") or ():
yield frame


class AnnotatedValue(object):
def __init__(self, value, metadata):
self.value = value
Expand Down
3 changes: 2 additions & 1 deletion sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from ._compat import with_metaclass
from .scope import Scope
from .utils import Event, skip_internal_frames, ContextVar
from .utils import skip_internal_frames, ContextVar
from .event import Event


_local = ContextVar("sentry_current_hub")
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/_wsgi.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json

from sentry_sdk.hub import _should_send_default_pii
from sentry_sdk.stripping import AnnotatedValue
from sentry_sdk.event import AnnotatedValue


def get_environ(environ):
Expand Down
7 changes: 6 additions & 1 deletion sentry_sdk/integrations/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

import sys
import logging
import datetime

from sentry_sdk import get_current_hub, capture_event, add_breadcrumb
from sentry_sdk.utils import to_string, Event, skip_internal_frames
from sentry_sdk.utils import to_string, skip_internal_frames
from sentry_sdk.event import Event, datetime_to_json
from sentry_sdk.hub import _internal_exceptions

from . import Integration
Expand Down Expand Up @@ -51,6 +53,9 @@ def _breadcrumb_from_record(self, record):
"level": self._logging_to_event_level(record.levelname),
"category": record.name,
"message": record.message,
"timestamp": datetime_to_json(
datetime.datetime.fromtimestamp(record.created)
),
}

def _emit(self, record):
Expand Down
58 changes: 0 additions & 58 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
import uuid
import linecache

from datetime import datetime
Expand Down Expand Up @@ -313,63 +312,6 @@ def exceptions_from_error_tuple(exc_type, exc_value, tb, with_locals=True):
return rv


class Event(Mapping):
__slots__ = ("_data", "_exc_value")

def __init__(self, data={}):
self._data = {
"event_id": uuid.uuid4().hex,
"timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
"level": "error",
}

self._data.update(data)

self._exc_value = None

def get_json(self):
return self._data

def set_exception(self, exc_type, exc_value, tb, with_locals):
self["exception"] = {
"values": exceptions_from_error_tuple(exc_type, exc_value, tb, with_locals)
}
self._exc_value = exc_value

def __getitem__(self, key):
return self._data[key]

def __contains__(self, key):
return key in self._data

def get(self, *a, **kw):
return self._data.get(*a, **kw)

def setdefault(self, *a, **kw):
return self._data.setdefault(*a, **kw)

def __setitem__(self, key, value):
self._data[key] = value

def __iter__(self):
return iter(self._data)

def __len__(self):
return len(self._data)

def iter_frames(self):
stacktraces = []
if "stacktrace" in self:
stacktraces.append(self["stacktrace"])
if "exception" in self:
for exception in self["exception"].get("values") or ():
if "stacktrace" in exception:
stacktraces.append(exception["stacktrace"])
for stacktrace in stacktraces:
for frame in stacktrace.get("frames") or ():
yield frame


class SkipEvent(Exception):
"""Risen from an event processor to indicate that the event should be
ignored and not be reported."""
Expand Down
100 changes: 100 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import os
import subprocess
import json

import pytest

import sentry_sdk
from sentry_sdk.client import Transport

SEMAPHORE = "./checkouts/semaphore/target/debug/semaphore"

if not os.path.isfile(SEMAPHORE):
SEMAPHORE = None


@pytest.fixture(autouse=True)
def reraise_internal_exceptions(monkeypatch):
def capture_internal_exception(error=None):
if not error:
raise
raise error

monkeypatch.setattr(
sentry_sdk.get_current_hub(),
"capture_internal_exception",
capture_internal_exception,
)


@pytest.fixture
def monkeypatch_test_transport(monkeypatch, assert_semaphore_acceptance):
def inner(client):
monkeypatch.setattr(
client, "_transport", TestTransport(assert_semaphore_acceptance)
)

return inner


def _no_errors_in_semaphore_response(obj):
"""Assert that semaphore didn't throw any errors when processing the
event."""

def inner(obj):
if not isinstance(obj, dict):
return

assert "err" not in obj

for value in obj.values():
inner(value)

try:
inner(obj.get("_meta"))
inner(obj.get(""))
except AssertionError:
raise AssertionError(obj)


@pytest.fixture
def assert_semaphore_acceptance(tmpdir):
if not SEMAPHORE:
return lambda event: None

def inner(event):
# not dealing with the subprocess API right now
file = tmpdir.join("event")
file.write(json.dumps(dict(event)))
output = json.loads(
subprocess.check_output(
[SEMAPHORE, "process-event"], stdin=file.open()
).decode("utf-8")
)
_no_errors_in_semaphore_response(output)

return inner


@pytest.fixture
def sentry_init(monkeypatch_test_transport, assert_semaphore_acceptance):
def inner(*a, **kw):
client = sentry_sdk.Client(*a, **kw)
monkeypatch_test_transport(client)
sentry_sdk.Hub.current.bind_client(client)

return inner


class TestTransport(Transport):
def __init__(self, capture_event_callback):
self.capture_event = capture_event_callback
self._queue = None

def start(self):
pass

def close(self):
pass

dsn = "LOL"
Loading