diff --git a/opentracing/span.py b/opentracing/span.py index 97f9401..7786b7e 100644 --- a/opentracing/span.py +++ b/opentracing/span.py @@ -18,6 +18,14 @@ from opentracing.ext import tags +on_error_callbacks = [] + + +def add_on_error_callback(fn): + global on_error_callbacks + + on_error_callbacks.append(fn) + class SpanContext(object): """SpanContext represents :class:`Span` state that must propagate to @@ -223,6 +231,9 @@ def _on_error(span, exc_type, exc_val, exc_tb): if not span or not exc_val: return + for callback in on_error_callbacks: + callback(span, exc_type, exc_val, exc_tb) + span.set_tag(tags.ERROR, True) span.log_kv({ logs.EVENT: tags.ERROR, diff --git a/tests/test_noop_span.py b/tests/test_noop_span.py index 0753d32..01b8c4d 100644 --- a/tests/test_noop_span.py +++ b/tests/test_noop_span.py @@ -14,6 +14,7 @@ from __future__ import absolute_import import mock +import sys import time import types from opentracing import child_of @@ -21,6 +22,7 @@ from opentracing import Tracer from opentracing import logs from opentracing import tags +from opentracing import span as span_module def test_span(): @@ -67,26 +69,48 @@ def test_span_error_report(): span = tracer.start_span('foo') error_message = 'unexpected_situation' - with mock.patch.object(span, 'log_kv') as log_kv: - with mock.patch.object(span, 'set_tag') as set_tag: - try: - with span: - raise ValueError(error_message) - except ValueError: - pass - - assert set_tag.call_count == 1 - assert set_tag.call_args[0] == (tags.ERROR, True) - - assert log_kv.call_count == 1 - log_kv_args = log_kv.call_args[0][0] - assert log_kv_args.get(logs.EVENT, None) is tags.ERROR - assert log_kv_args.get(logs.MESSAGE, None) is error_message - assert log_kv_args.get(logs.ERROR_KIND, None) is ValueError - assert isinstance(log_kv_args.get(logs.ERROR_OBJECT, None), - ValueError) - assert isinstance(log_kv_args.get(logs.STACK, None), - types.TracebackType) + callback1 = mock.Mock() + callback2 = mock.Mock() + + with mock.patch.object(span_module, 'on_error_callbacks', []): + with mock.patch.object(span, 'log_kv') as log_kv: + with mock.patch.object(span, 'set_tag') as set_tag: + span_module.add_on_error_callback(callback1) + span_module.add_on_error_callback(callback2) + + exc_type = None + exc_value = None + exc_tb = None + + try: + with span: + raise ValueError(error_message) + except ValueError: + exc_type, exc_value, exc_tb = sys.exc_info() + pass + + def test_callback_called(callback): + assert callback.call_count == 1 + assert callback.call_args[0] == (span, + exc_type, + exc_value, + exc_tb) + + test_callback_called(callback1) + test_callback_called(callback2) + + assert set_tag.call_count == 1 + assert set_tag.call_args[0] == (tags.ERROR, True) + + assert log_kv.call_count == 1 + log_kv_args = log_kv.call_args[0][0] + assert log_kv_args.get(logs.EVENT, None) is tags.ERROR + assert log_kv_args.get(logs.MESSAGE, None) is error_message + assert log_kv_args.get(logs.ERROR_KIND, None) is ValueError + assert isinstance(log_kv_args.get(logs.ERROR_OBJECT, None), + ValueError) + assert isinstance(log_kv_args.get(logs.STACK, None), + types.TracebackType) def test_inject():