Skip to content

Commit 3347b35

Browse files
authored
Ensure record_exception is called once when calling LogfireSpan.record_exception (#915)
1 parent 88ccbac commit 3347b35

File tree

2 files changed

+87
-3
lines changed

2 files changed

+87
-3
lines changed

logfire/_internal/main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
)
6060
from .metrics import ProxyMeterProvider
6161
from .stack_info import get_user_stack_info
62-
from .tracer import ProxyTracerProvider, record_exception, set_exception_status
62+
from .tracer import ProxyTracerProvider, _LogfireWrappedSpan, record_exception, set_exception_status # type: ignore
6363
from .utils import get_version, handle_internal_errors, log_internal_error, uniquify_sequence
6464

6565
if TYPE_CHECKING:
@@ -2236,8 +2236,11 @@ def record_exception(
22362236
if not self._span.is_recording():
22372237
return
22382238

2239+
span = self._span
2240+
while isinstance(span, _LogfireWrappedSpan):
2241+
span = span.span
22392242
record_exception(
2240-
self._span,
2243+
span,
22412244
exception,
22422245
attributes=attributes,
22432246
timestamp=timestamp,

tests/test_logfire.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3165,7 +3165,7 @@ def test_suppress_scopes(exporter: TestExporter, metrics_reader: InMemoryMetricR
31653165
)
31663166

31673167

3168-
def test_logfire_span_records_exceptions_once():
3168+
def test_logfire_span_records_exceptions_once(exporter: TestExporter):
31693169
n_calls_to_record_exception = 0
31703170

31713171
def patched_record_exception(*args: Any, **kwargs: Any) -> Any:
@@ -3182,6 +3182,87 @@ def patched_record_exception(*args: Any, **kwargs: Any) -> Any:
31823182
raise RuntimeError('error')
31833183

31843184
assert n_calls_to_record_exception == 1
3185+
assert exporter.exported_spans_as_dict() == snapshot(
3186+
[
3187+
{
3188+
'name': 'foo',
3189+
'context': {'trace_id': 1, 'span_id': 1, 'is_remote': False},
3190+
'parent': None,
3191+
'start_time': 1000000000,
3192+
'end_time': 3000000000,
3193+
'attributes': {
3194+
'code.filepath': 'test_logfire.py',
3195+
'code.function': 'test_logfire_span_records_exceptions_once',
3196+
'code.lineno': 123,
3197+
'logfire.msg_template': 'foo',
3198+
'logfire.msg': 'foo',
3199+
'logfire.span_type': 'span',
3200+
'logfire.level_num': 17,
3201+
},
3202+
'events': [
3203+
{
3204+
'name': 'exception',
3205+
'timestamp': 2000000000,
3206+
'attributes': {
3207+
'exception.type': 'RuntimeError',
3208+
'exception.message': 'error',
3209+
'exception.stacktrace': 'RuntimeError: error',
3210+
'exception.escaped': 'True',
3211+
},
3212+
}
3213+
],
3214+
}
3215+
]
3216+
)
3217+
3218+
3219+
def test_logfire_span_records_exceptions_manually_once(exporter: TestExporter):
3220+
n_calls_to_record_exception = 0
3221+
3222+
def patched_record_exception(*args: Any, **kwargs: Any) -> Any:
3223+
nonlocal n_calls_to_record_exception
3224+
n_calls_to_record_exception += 1
3225+
3226+
return record_exception(*args, **kwargs)
3227+
3228+
with patch('logfire._internal.tracer.record_exception', patched_record_exception), patch(
3229+
'logfire._internal.main.record_exception', patched_record_exception
3230+
):
3231+
with logfire.span('foo') as span:
3232+
span.record_exception(RuntimeError('error'))
3233+
3234+
assert n_calls_to_record_exception == 1
3235+
assert exporter.exported_spans_as_dict() == snapshot(
3236+
[
3237+
{
3238+
'name': 'foo',
3239+
'context': {'trace_id': 1, 'span_id': 1, 'is_remote': False},
3240+
'parent': None,
3241+
'start_time': 1000000000,
3242+
'end_time': 2000000000,
3243+
'attributes': {
3244+
'code.filepath': 'test_logfire.py',
3245+
'code.function': 'test_logfire_span_records_exceptions_manually_once',
3246+
'code.lineno': 123,
3247+
'logfire.msg_template': 'foo',
3248+
'logfire.msg': 'foo',
3249+
'logfire.span_type': 'span',
3250+
},
3251+
'events': [
3252+
{
3253+
'name': 'exception',
3254+
'timestamp': IsInt(),
3255+
'attributes': {
3256+
'exception.type': 'RuntimeError',
3257+
'exception.message': 'error',
3258+
'exception.stacktrace': 'RuntimeError: error',
3259+
'exception.escaped': 'False',
3260+
},
3261+
}
3262+
],
3263+
}
3264+
]
3265+
)
31853266

31863267

31873268
def test_exit_ended_span(exporter: TestExporter):

0 commit comments

Comments
 (0)