Skip to content

Commit 4686bef

Browse files
authored
Align optional parameters for span related to exceptions, add exception.escaped for record_exception (open-telemetry#1365)
1 parent 26bf23f commit 4686bef

File tree

5 files changed

+90
-40
lines changed

5 files changed

+90
-40
lines changed

opentelemetry-api/src/opentelemetry/trace/__init__.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ def start_span(
234234
attributes: types.Attributes = None,
235235
links: typing.Sequence[Link] = (),
236236
start_time: typing.Optional[int] = None,
237+
record_exception: bool = True,
237238
set_status_on_exception: bool = True,
238239
) -> "Span":
239240
"""Starts a span.
@@ -266,6 +267,8 @@ def start_span(
266267
attributes: The span's attributes.
267268
links: Links span to other spans
268269
start_time: Sets the start time of a span
270+
record_exception: Whether to record any exceptions raised within the
271+
context as error event on the span.
269272
set_status_on_exception: Only relevant if the returned span is used
270273
in a with/context manager. Defines wether the span status will
271274
be automatically set to ERROR when an uncaught exception is
@@ -285,7 +288,9 @@ def start_as_current_span(
285288
kind: SpanKind = SpanKind.INTERNAL,
286289
attributes: types.Attributes = None,
287290
links: typing.Sequence[Link] = (),
291+
start_time: typing.Optional[int] = None,
288292
record_exception: bool = True,
293+
set_status_on_exception: bool = True,
289294
) -> typing.Iterator["Span"]:
290295
"""Context manager for creating a new span and set it
291296
as the current span in this tracer's context.
@@ -325,8 +330,14 @@ def start_as_current_span(
325330
meaningful even if there is no parent.
326331
attributes: The span's attributes.
327332
links: Links span to other spans
333+
start_time: Sets the start time of a span
328334
record_exception: Whether to record any exceptions raised within the
329335
context as error event on the span.
336+
set_status_on_exception: Only relevant if the returned span is used
337+
in a with/context manager. Defines wether the span status will
338+
be automatically set to ERROR when an uncaught exception is
339+
raised in the span with block. The span status won't be set by
340+
this mechanism if it was previously set manually.
330341
331342
Yields:
332343
The newly-created span.
@@ -335,10 +346,7 @@ def start_as_current_span(
335346
@contextmanager # type: ignore
336347
@abc.abstractmethod
337348
def use_span(
338-
self,
339-
span: "Span",
340-
end_on_exit: bool = False,
341-
record_exception: bool = True,
349+
self, span: "Span", end_on_exit: bool = False,
342350
) -> typing.Iterator[None]:
343351
"""Context manager for setting the passed span as the
344352
current span in the context, as well as resetting the
@@ -355,8 +363,6 @@ def use_span(
355363
span: The span to start and make current.
356364
end_on_exit: Whether to end the span automatically when leaving the
357365
context manager.
358-
record_exception: Whether to record any exceptions raised within the
359-
context as error event on the span.
360366
"""
361367

362368

@@ -374,6 +380,7 @@ def start_span(
374380
attributes: types.Attributes = None,
375381
links: typing.Sequence[Link] = (),
376382
start_time: typing.Optional[int] = None,
383+
record_exception: bool = True,
377384
set_status_on_exception: bool = True,
378385
) -> "Span":
379386
# pylint: disable=unused-argument,no-self-use
@@ -387,17 +394,16 @@ def start_as_current_span(
387394
kind: SpanKind = SpanKind.INTERNAL,
388395
attributes: types.Attributes = None,
389396
links: typing.Sequence[Link] = (),
397+
start_time: typing.Optional[int] = None,
390398
record_exception: bool = True,
399+
set_status_on_exception: bool = True,
391400
) -> typing.Iterator["Span"]:
392401
# pylint: disable=unused-argument,no-self-use
393402
yield INVALID_SPAN
394403

395404
@contextmanager # type: ignore
396405
def use_span(
397-
self,
398-
span: "Span",
399-
end_on_exit: bool = False,
400-
record_exception: bool = True,
406+
self, span: "Span", end_on_exit: bool = False,
401407
) -> typing.Iterator[None]:
402408
# pylint: disable=unused-argument,no-self-use
403409
yield

opentelemetry-api/src/opentelemetry/trace/span.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def record_exception(
8686
exception: Exception,
8787
attributes: types.Attributes = None,
8888
timestamp: typing.Optional[int] = None,
89+
escaped: bool = False,
8990
) -> None:
9091
"""Records an exception as a span event."""
9192

@@ -275,6 +276,7 @@ def record_exception(
275276
exception: Exception,
276277
attributes: types.Attributes = None,
277278
timestamp: typing.Optional[int] = None,
279+
escaped: bool = False,
278280
) -> None:
279281
pass
280282

opentelemetry-sdk/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## Unreleased
44

55
- Add optional parameter to `record_exception` method ([#1314](https://github.com/open-telemetry/opentelemetry-python/pull/1314))
6+
- Update exception handling optional parameters, add escaped attribute to record_exception
7+
([#1365](https://github.com/open-telemetry/opentelemetry-python/pull/1365))
68

79
## Version 0.15b0
810

opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ def __new__(cls, *args, **kwargs):
412412
raise TypeError("Span must be instantiated via a tracer.")
413413
return super().__new__(cls)
414414

415+
# pylint: disable=too-many-locals
415416
def __init__(
416417
self,
417418
name: str,
@@ -426,6 +427,7 @@ def __init__(
426427
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
427428
span_processor: SpanProcessor = SpanProcessor(),
428429
instrumentation_info: InstrumentationInfo = None,
430+
record_exception: bool = True,
429431
set_status_on_exception: bool = True,
430432
) -> None:
431433

@@ -436,6 +438,7 @@ def __init__(
436438
self.trace_config = trace_config
437439
self.resource = resource
438440
self.kind = kind
441+
self._record_exception = record_exception
439442
self._set_status_on_exception = set_status_on_exception
440443

441444
self.span_processor = span_processor
@@ -663,20 +666,25 @@ def __exit__(
663666
exc_tb: Optional[TracebackType],
664667
) -> None:
665668
"""Ends context manager and calls `end` on the `Span`."""
666-
# Records status if span is used as context manager
667-
# i.e. with tracer.start_span() as span:
668-
# TODO: Record exception
669-
if (
670-
self.status.status_code is StatusCode.UNSET
671-
and self._set_status_on_exception
672-
and exc_val is not None
673-
):
674-
self.set_status(
675-
Status(
676-
status_code=StatusCode.ERROR,
677-
description="{}: {}".format(exc_type.__name__, exc_val),
669+
if exc_val is not None:
670+
# Record the exception as an event
671+
# pylint:disable=protected-access
672+
if self._record_exception:
673+
self.record_exception(exception=exc_val, escaped=True)
674+
# Records status if span is used as context manager
675+
# i.e. with tracer.start_span() as span:
676+
if (
677+
self.status.status_code is StatusCode.UNSET
678+
and self._set_status_on_exception
679+
):
680+
self.set_status(
681+
Status(
682+
status_code=StatusCode.ERROR,
683+
description="{}: {}".format(
684+
exc_type.__name__, exc_val
685+
),
686+
)
678687
)
679-
)
680688

681689
super().__exit__(exc_type, exc_val, exc_tb)
682690

@@ -685,6 +693,7 @@ def record_exception(
685693
exception: Exception,
686694
attributes: types.Attributes = None,
687695
timestamp: Optional[int] = None,
696+
escaped: bool = False,
688697
) -> None:
689698
"""Records an exception as a span event."""
690699
try:
@@ -698,6 +707,7 @@ def record_exception(
698707
"exception.type": exception.__class__.__name__,
699708
"exception.message": str(exception),
700709
"exception.stacktrace": stacktrace,
710+
"exception.escaped": str(escaped),
701711
}
702712
if attributes:
703713
_attributes.update(attributes)
@@ -740,12 +750,21 @@ def start_as_current_span(
740750
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
741751
attributes: types.Attributes = None,
742752
links: Sequence[trace_api.Link] = (),
753+
start_time: Optional[int] = None,
743754
record_exception: bool = True,
755+
set_status_on_exception: bool = True,
744756
) -> Iterator[trace_api.Span]:
745-
span = self.start_span(name, context, kind, attributes, links)
746-
return self.use_span(
747-
span, end_on_exit=True, record_exception=record_exception
757+
span = self.start_span(
758+
name=name,
759+
context=context,
760+
kind=kind,
761+
attributes=attributes,
762+
links=links,
763+
start_time=start_time,
764+
record_exception=record_exception,
765+
set_status_on_exception=set_status_on_exception,
748766
)
767+
return self.use_span(span, end_on_exit=True)
749768

750769
def start_span( # pylint: disable=too-many-locals
751770
self,
@@ -755,6 +774,7 @@ def start_span( # pylint: disable=too-many-locals
755774
attributes: types.Attributes = None,
756775
links: Sequence[trace_api.Link] = (),
757776
start_time: Optional[int] = None,
777+
record_exception: bool = True,
758778
set_status_on_exception: bool = True,
759779
) -> trace_api.Span:
760780

@@ -816,6 +836,7 @@ def start_span( # pylint: disable=too-many-locals
816836
kind=kind,
817837
links=links,
818838
instrumentation_info=self.instrumentation_info,
839+
record_exception=record_exception,
819840
set_status_on_exception=set_status_on_exception,
820841
)
821842
span.start(start_time=start_time, parent_context=context)
@@ -825,10 +846,7 @@ def start_span( # pylint: disable=too-many-locals
825846

826847
@contextmanager
827848
def use_span(
828-
self,
829-
span: trace_api.Span,
830-
end_on_exit: bool = False,
831-
record_exception: bool = True,
849+
self, span: trace_api.Span, end_on_exit: bool = False,
832850
) -> Iterator[trace_api.Span]:
833851
try:
834852
token = context_api.attach(context_api.set_value(SPAN_KEY, span))
@@ -837,11 +855,12 @@ def use_span(
837855
finally:
838856
context_api.detach(token)
839857

840-
except Exception as error: # pylint: disable=broad-except
841-
# pylint:disable=protected-access
858+
except Exception as exc: # pylint: disable=broad-except
859+
# Record the exception as an event
842860
if isinstance(span, Span):
843-
if record_exception:
844-
span.record_exception(error)
861+
# pylint:disable=protected-access
862+
if span._record_exception:
863+
span.record_exception(exc)
845864

846865
# Records status if use_span is used
847866
# i.e. with tracer.start_as_current_span() as span:
@@ -851,13 +870,9 @@ def use_span(
851870
):
852871
span.set_status(
853872
Status(
854-
status_code=getattr(
855-
error,
856-
EXCEPTION_STATUS_FIELD,
857-
StatusCode.ERROR,
858-
),
873+
status_code=StatusCode.ERROR,
859874
description="{}: {}".format(
860-
type(error).__name__, error
875+
type(exc).__name__, exc
861876
),
862877
)
863878
)

opentelemetry-sdk/tests/trace/test_trace.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,9 @@ def test_record_exception_with_attributes(self):
889889
self.assertEqual(
890890
"RuntimeError", exception_event.attributes["exception.type"]
891891
)
892+
self.assertEqual(
893+
"False", exception_event.attributes["exception.escaped"]
894+
)
892895
self.assertIn(
893896
"RuntimeError: error",
894897
exception_event.attributes["exception.stacktrace"],
@@ -900,6 +903,28 @@ def test_record_exception_with_attributes(self):
900903
True, exception_event.attributes["has_additional_attributes"],
901904
)
902905

906+
def test_record_exception_escaped(self):
907+
span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext))
908+
try:
909+
raise RuntimeError("error")
910+
except RuntimeError as err:
911+
span.record_exception(exception=err, escaped=True)
912+
exception_event = span.events[0]
913+
self.assertEqual("exception", exception_event.name)
914+
self.assertEqual(
915+
"error", exception_event.attributes["exception.message"]
916+
)
917+
self.assertEqual(
918+
"RuntimeError", exception_event.attributes["exception.type"]
919+
)
920+
self.assertIn(
921+
"RuntimeError: error",
922+
exception_event.attributes["exception.stacktrace"],
923+
)
924+
self.assertEqual(
925+
"True", exception_event.attributes["exception.escaped"]
926+
)
927+
903928
def test_record_exception_with_timestamp(self):
904929
span = trace._Span("name", mock.Mock(spec=trace_api.SpanContext))
905930
try:

0 commit comments

Comments
 (0)