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
5 changes: 5 additions & 0 deletions sentry_sdk/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ def capture_exception(self, error=None):
}
return self.capture_event(event)

def capture_internal_exception(self, error=None):
"""Capture an exception that is likely caused by a bug in the SDK
itself."""
pass

def add_breadcrumb(self, crumb):
"""Adds a breadcrumb."""
client, scope = self._stack[-1]
Expand Down
46 changes: 46 additions & 0 deletions sentry_sdk/integrations/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import absolute_import
from __future__ import print_function

import logging

from sentry_sdk import get_current_hub, capture_event
from sentry_sdk.utils import to_string, create_event, exceptions_from_error_tuple, skip_internal_frames

class SentryHandler(logging.Handler, object):
def emit(self, record):
try:
self.format(record)
return self._emit(record)
except Exception:
get_current_hub().capture_internal_exception()

def can_record(self, record):
return not record.name.startswith('sentry_sdk')

def _emit(self, record):
if not self.can_record(record):
print(to_string(record.message), file=sys.stderr)
return

event = create_event()

# exc_info might be None or (None, None, None)
if record.exc_info and all(record.exc_info):
exc_type, exc_value, tb = record.exc_info
event['exception'] = exceptions_from_error_tuple(
exc_type, exc_value, skip_internal_frames(tb),
get_current_hub().client.options['with_locals']
)

event['level'] = record.levelname.lower()
event['logger'] = record.name

event['logentry'] = {
'message': to_string(record.msg),
'params': record.args
}

capture_event(event)


HANDLER = SentryHandler()
7 changes: 7 additions & 0 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,10 @@ def create_event():
'timestamp': datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
'level': 'error',
}


def to_string(value):
try:
return text_type(value)
except UnicodeDecodeError:
return repr(value)[1:-1]
21 changes: 21 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest
import sentry_sdk
sentry_sdk.init()


@pytest.fixture
def capture_exceptions(monkeypatch):
errors = []
def capture_exception(error=None):
errors.append(error or sys.exc_info()[1])

monkeypatch.setattr(sentry_sdk.Hub.current,
'capture_exception', capture_exception)
return errors


@pytest.fixture
def capture_events(monkeypatch):
events = []
monkeypatch.setattr(sentry_sdk.Hub.current, 'capture_event', events.append)
return events
2 changes: 0 additions & 2 deletions tests/django/myapp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

import sentry_sdk

sentry_sdk.init()

def self_check(request):
with sentry_sdk.configure_scope() as scope:
assert scope._data['transaction'] == self_check
Expand Down
17 changes: 17 additions & 0 deletions tests/logging/test_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest
import logging

from sentry_sdk.integrations.logging import HANDLER

logger = logging.getLogger(__name__)

logger.handlers = [HANDLER]
logger.setLevel(logging.DEBUG)

@pytest.mark.parametrize('level', ['info', 'debug', 'warning', 'error'])
def test_logging(capture_events, level):
getattr(logger, level)('LOL')
event, = capture_events
assert event['level'] == level
assert not event['logentry']['params']
assert event['logentry']['message'] == 'LOL'