Repository: kiwicom/structlog-sentry

Issue:
Can't use format_exc_info with SentryProcessor
The `SentryProcessor` as well as the structlog built-in `format_exc_info` processor both use `event_dict.pop("exc_info", None)`. This means, when the built-in processor is used for nicely converting the exception into a readable traceback and the former is used to send the event and exception to Sentry, the one that comes later in structlog's processors list doesn't do anything.

I'd like to propose to change the `SentryProcessor` to use `event_dict.get("exc_info")` instead of `pop()` in https://github.com/kiwicom/structlog-sentry/blob/00ba79ef470ed81f4e396fde134f6612aa34551d/structlog_sentry/__init__.py#L42

The documentation should be changed such that it points out that structlog's built-in `format_exc_info` processor must come _after_ the `SentryProcessor`.

Comments:

Patch:
diff --git a/README.md b/README.md
index f0cfe2f..7d67e81 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,12 @@ except RequestException:
 This will automatically collect `sys.exc_info()` along with the message, if you want
 to turn this behavior off, just pass `exc_info=False`.
 
+When you want to use structlog's built-in
+[`format_exc_info`](http://www.structlog.org/en/stable/api.html#structlog.processors.format_exc_info)
+processor, make that the `SentryProcessor` comes *before* `format_exc_info`!
+Otherwise, the `SentryProcessor` won't have an `exc_info` to work with, because
+it's removed from the event by `format_exc_info`.
+
 Logging calls with no `sys.exc_info()` are also automatically captured by Sentry:
 
 ```python
diff --git a/structlog_sentry/__init__.py b/structlog_sentry/__init__.py
index 8a5d041..d175653 100644
--- a/structlog_sentry/__init__.py
+++ b/structlog_sentry/__init__.py
@@ -39,7 +39,7 @@ class SentryProcessor:
 
         :param event_dict: structlog event_dict
         """
-        exc_info = event_dict.pop("exc_info", True)
+        exc_info = event_dict.get("exc_info", True)
         if exc_info is True:
             # logger.exeception() or logger.error(exc_info=True)
             exc_info = sys.exc_info()


Test patch:
diff --git a/test/test_sentry_processor.py b/test/test_sentry_processor.py
index 22ca188..39f69f3 100644
--- a/test/test_sentry_processor.py
+++ b/test/test_sentry_processor.py
@@ -98,6 +98,27 @@ def test_sentry_log_failure_exc_info_true(mocker, level):
     assert kwargs["hint"]["exc_info"][0] == ZeroDivisionError
 
 
+def test_sentry_log_leave_exc_info_untouched(mocker):
+    """Make sure exc_info remains in event_data at the end of the processor.
+
+    The structlog built-in format_exc_info processor pops the key and formats
+    it. Using SentryProcessor, and format_exc_info wasn't possible before,
+    because the latter one didn't have an exc_info to work with.
+
+    https://github.com/kiwicom/structlog-sentry/issues/16
+    """
+    mocker.patch("structlog_sentry.capture_event")
+
+    event_data = {"level": "warning", "event": "some.event", "exc_info": True}
+    processor = SentryProcessor()
+    try:
+        1 / 0
+    except ZeroDivisionError:
+        processor(None, None, event_data)
+
+    assert "exc_info" in event_data
+
+
 @pytest.mark.parametrize("level", ["debug", "info", "warning"])
 def test_sentry_log_no_extra(mocker, level):
     m_capture_event = mocker.patch("structlog_sentry.capture_event")