Skip to content

Commit fe1d18f

Browse files
authored
added a safeguard for processors that raise exceptions (#1138)
* added a safeguard for processors that raise exceptions Without this safeguard, exceptions in processors would bubble all the way up, killing the transport thread. This would then result in the agent stopping to report any data. * fix style issue
1 parent 4943f76 commit fe1d18f

File tree

3 files changed

+47
-12
lines changed

3 files changed

+47
-12
lines changed

docs/sanitizing-data.asciidoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ and returns the modified event.
1010

1111
To completely drop an event, your processor should return `False` (or any other "falsy" value) instead of the event.
1212

13+
An event will also be dropped if any processor raises an exception while processing it.
14+
A log message with level `WARNING` will be issued in this case.
15+
1316
This is an example of a processor that removes the exception stacktrace from an error:
1417

1518
[source,python]
@@ -46,7 +49,7 @@ ELASTIC_APM = {
4649
}
4750
----
4851

49-
NOTE: We recommend to use the above list of processors that sanitize passwords and secrets in different places of the event object.
52+
NOTE: We recommend using the above list of processors that sanitize passwords and secrets in different places of the event object.
5053

5154
The default set of processors sanitize fields based on a set of defaults defined in `elasticapm.conf.constants`. This set can be configured with the `SANITIZE_FIELD_NAMES` configuration option. For example, if your application produces a sensitive field called `My-Sensitive-Field`, the default processors can be used to automatically sanitize this field. You can specify what fields to santize within default processors like this:
5255

elasticapm/transport/base.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,23 @@ def _process_event(self, event_type, data):
178178
# Run the data through processors
179179
for processor in self._processors:
180180
if not hasattr(processor, "event_types") or event_type in processor.event_types:
181-
data = processor(self.client, data)
182-
if not data:
183-
logger.debug(
184-
"Dropped event of type %s due to processor %s.%s",
181+
try:
182+
data = processor(self.client, data)
183+
if not data:
184+
logger.debug(
185+
"Dropped event of type %s due to processor %s.%s",
186+
event_type,
187+
processor.__module__,
188+
processor.__name__,
189+
)
190+
return None
191+
except Exception:
192+
logger.warning(
193+
"Dropped event of type %s due to exception in processor %s.%s",
185194
event_type,
186-
getattr(processor, "__module__"),
187-
getattr(processor, "__name__"),
195+
processor.__module__,
196+
processor.__name__,
197+
exc_info=True,
188198
)
189199
return None
190200
return data
@@ -215,7 +225,6 @@ def _init_event_queue(self, chill_until, max_chill_time):
215225
def _flush(self, buffer):
216226
"""
217227
Flush the queue. This method should only be called from the event processing queue
218-
:param sync: if true, flushes the queue synchronously in the current thread
219228
:return: None
220229
"""
221230
if not self.state.should_try():

tests/processors/tests.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,10 @@ def dummy_processor_no_events(client, data):
356356
return data
357357

358358

359+
def dummy_processor_failing(client, data):
360+
raise ValueError("oops")
361+
362+
359363
@pytest.mark.parametrize(
360364
"elasticapm_client",
361365
[{"processors": "tests.processors.tests.dummy_processor,tests.processors.tests.dummy_processor_no_events"}],
@@ -428,17 +432,36 @@ def foo(client, event):
428432

429433

430434
def test_drop_events_in_processor(elasticapm_client, caplog):
431-
dropping_processor = mock.MagicMock(return_value=None, event_types=[SPAN], __name__="dropper")
435+
dropping_processor = mock.MagicMock(return_value=None, event_types=[TRANSACTION], __name__="dropper")
432436
shouldnt_be_called_processor = mock.Mock(event_types=[])
433437

434438
elasticapm_client._transport._processors = [dropping_processor, shouldnt_be_called_processor]
435439
with caplog.at_level(logging.DEBUG, logger="elasticapm.transport"):
436-
elasticapm_client.queue(SPAN, {"some": "data"})
440+
elasticapm_client.begin_transaction("test")
441+
elasticapm_client.end_transaction("test", "FAIL")
437442
assert dropping_processor.call_count == 1
438443
assert shouldnt_be_called_processor.call_count == 0
439-
assert elasticapm_client._transport.events[SPAN][0] is None
444+
assert elasticapm_client._transport.events[TRANSACTION][0] is None
445+
assert_any_record_contains(
446+
caplog.records, "Dropped event of type transaction due to processor mock.mock.dropper", "elasticapm.transport"
447+
)
448+
449+
450+
@pytest.mark.parametrize(
451+
"elasticapm_client",
452+
[{"processors": "tests.processors.tests.dummy_processor,tests.processors.tests.dummy_processor_failing"}],
453+
indirect=True,
454+
)
455+
def test_drop_events_in_failing_processor(elasticapm_client, caplog):
456+
457+
with caplog.at_level(logging.WARNING, logger="elasticapm.transport"):
458+
elasticapm_client.begin_transaction("test")
459+
elasticapm_client.end_transaction("test", "FAIL")
460+
assert elasticapm_client._transport.events[TRANSACTION][0] is None
440461
assert_any_record_contains(
441-
caplog.records, "Dropped event of type span due to processor mock.mock.dropper", "elasticapm.transport"
462+
caplog.records,
463+
"Dropped event of type transaction due to exception in processor tests.processors.tests.dummy_processor_failing",
464+
"elasticapm.transport",
442465
)
443466

444467

0 commit comments

Comments
 (0)