Skip to content

Commit cb0169c

Browse files
ocelotlc24t
authored andcommitted
Set status for ended spans (open-telemetry#297)
Fixes open-telemetry#292
1 parent 43c7ccd commit cb0169c

File tree

4 files changed

+65
-21
lines changed

4 files changed

+65
-21
lines changed

Diff for: opentelemetry-api/src/opentelemetry/trace/__init__.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,12 @@ class SpanKind(enum.Enum):
122122
https://github.com/open-telemetry/opentelemetry-specification/pull/226.
123123
"""
124124

125-
#: Default value. Indicates that the span is used internally in the application.
125+
#: Default value. Indicates that the span is used internally in the
126+
# application.
126127
INTERNAL = 0
127128

128-
#: Indicates that the span describes an operation that handles a remote request.
129+
#: Indicates that the span describes an operation that handles a remote
130+
# request.
129131
SERVER = 1
130132

131133
#: Indicates that the span describes a request to some remote service.
@@ -228,6 +230,7 @@ def __exit__(
228230
exc_tb: typing.Optional[python_types.TracebackType],
229231
) -> None:
230232
"""Ends context manager and calls `end` on the `Span`."""
233+
231234
self.end()
232235

233236

@@ -396,6 +399,7 @@ def start_span(
396399
attributes: typing.Optional[types.Attributes] = None,
397400
links: typing.Sequence[Link] = (),
398401
start_time: typing.Optional[int] = None,
402+
set_status_on_exception: bool = True,
399403
) -> "Span":
400404
"""Starts a span.
401405
@@ -427,6 +431,11 @@ def start_span(
427431
attributes: The span's attributes.
428432
links: Links span to other spans
429433
start_time: Sets the start time of a span
434+
set_status_on_exception: Only relevant if the returned span is used
435+
in a with/context manager. Defines wether the span status will
436+
be automatically set to UNKNOWN when an uncaught exception is
437+
raised in the span with block. The span status won't be set by
438+
this mechanism if it was previousy set manually.
430439
431440
Returns:
432441
The newly-created span.

Diff for: opentelemetry-api/src/opentelemetry/trace/status.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,14 @@ class Status:
163163

164164
def __init__(
165165
self,
166-
canonical_code: "StatusCanonicalCode" = StatusCanonicalCode.OK,
166+
canonical_code: StatusCanonicalCode = StatusCanonicalCode.OK,
167167
description: typing.Optional[str] = None,
168168
):
169169
self._canonical_code = canonical_code
170170
self._description = description
171171

172172
@property
173-
def canonical_code(self) -> "StatusCanonicalCode":
173+
def canonical_code(self) -> StatusCanonicalCode:
174174
"""Represents the canonical status code of a finished Span."""
175175
return self._canonical_code
176176

Diff for: opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

+39-4
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
import random
1919
import threading
2020
from contextlib import contextmanager
21-
from typing import Iterator, Optional, Sequence, Tuple
21+
from types import TracebackType
22+
from typing import Iterator, Optional, Sequence, Tuple, Type
2223

2324
from opentelemetry import trace as trace_api
2425
from opentelemetry.context import Context
2526
from opentelemetry.sdk import util
2627
from opentelemetry.sdk.util import BoundedDict, BoundedList
2728
from opentelemetry.trace import sampling
29+
from opentelemetry.trace.status import Status, StatusCanonicalCode
2830
from opentelemetry.util import time_ns, types
2931

3032
logger = logging.getLogger(__name__)
@@ -134,6 +136,7 @@ def __init__(
134136
links: Sequence[trace_api.Link] = (),
135137
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
136138
span_processor: SpanProcessor = SpanProcessor(),
139+
set_status_on_exception: bool = True,
137140
) -> None:
138141

139142
self.name = name
@@ -143,9 +146,10 @@ def __init__(
143146
self.trace_config = trace_config
144147
self.resource = resource
145148
self.kind = kind
149+
self._set_status_on_exception = set_status_on_exception
146150

147151
self.span_processor = span_processor
148-
self.status = trace_api.Status()
152+
self.status = None
149153
self._lock = threading.Lock()
150154

151155
if attributes is None:
@@ -174,7 +178,10 @@ def __repr__(self):
174178
)
175179

176180
def __str__(self):
177-
return '{}(name="{}", context={}, kind={}, parent={}, start_time={}, end_time={})'.format(
181+
return (
182+
'{}(name="{}", context={}, kind={}, '
183+
"parent={}, start_time={}, end_time={})"
184+
).format(
178185
type(self).__name__,
179186
self.name,
180187
self.context,
@@ -254,6 +261,9 @@ def end(self, end_time: Optional[int] = None) -> None:
254261
logger.warning("Calling end() on an ended span.")
255262
return
256263

264+
if self.status is None:
265+
self.set_status(Status(canonical_code=StatusCanonicalCode.OK))
266+
257267
self.span_processor.on_end(self)
258268

259269
def update_name(self, name: str) -> None:
@@ -275,6 +285,29 @@ def set_status(self, status: trace_api.Status) -> None:
275285
return
276286
self.status = status
277287

288+
def __exit__(
289+
self,
290+
exc_type: Optional[Type[BaseException]],
291+
exc_val: Optional[BaseException],
292+
exc_tb: Optional[TracebackType],
293+
) -> None:
294+
"""Ends context manager and calls `end` on the `Span`."""
295+
296+
if (
297+
self.status is None
298+
and self._set_status_on_exception
299+
and exc_val is not None
300+
):
301+
302+
self.set_status(
303+
Status(
304+
canonical_code=StatusCanonicalCode.UNKNOWN,
305+
description="{}: {}".format(exc_type.__name__, exc_val),
306+
)
307+
)
308+
309+
super().__exit__(exc_type, exc_val, exc_tb)
310+
278311

279312
def generate_span_id() -> int:
280313
"""Get a new random span ID.
@@ -334,14 +367,15 @@ def start_as_current_span(
334367
span = self.start_span(name, parent, kind, attributes, links)
335368
return self.use_span(span, end_on_exit=True)
336369

337-
def start_span(
370+
def start_span( # pylint: disable=too-many-locals
338371
self,
339372
name: str,
340373
parent: trace_api.ParentSpan = trace_api.Tracer.CURRENT_SPAN,
341374
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
342375
attributes: Optional[types.Attributes] = None,
343376
links: Sequence[trace_api.Link] = (),
344377
start_time: Optional[int] = None,
378+
set_status_on_exception: bool = True,
345379
) -> "Span":
346380
"""See `opentelemetry.trace.Tracer.start_span`."""
347381

@@ -401,6 +435,7 @@ def start_span(
401435
span_processor=self._active_span_processor,
402436
kind=kind,
403437
links=links,
438+
set_status_on_exception=set_status_on_exception,
404439
)
405440
span.start(start_time=start_time)
406441
else:

Diff for: opentelemetry-sdk/tests/trace/test_trace.py

+13-13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from opentelemetry import trace as trace_api
2121
from opentelemetry.sdk import trace
2222
from opentelemetry.trace import sampling
23+
from opentelemetry.trace.status import StatusCanonicalCode
2324
from opentelemetry.util import time_ns
2425

2526

@@ -455,12 +456,7 @@ def test_start_span(self):
455456
span.start()
456457
self.assertEqual(start_time, span.start_time)
457458

458-
# default status
459-
self.assertTrue(span.status.is_ok)
460-
self.assertIs(
461-
span.status.canonical_code, trace_api.status.StatusCanonicalCode.OK
462-
)
463-
self.assertIs(span.status.description, None)
459+
self.assertIs(span.status, None)
464460

465461
# status
466462
new_status = trace_api.status.Status(
@@ -515,13 +511,17 @@ def test_ended_span(self):
515511
"Test description",
516512
)
517513
root.set_status(new_status)
518-
# default status
519-
self.assertTrue(root.status.is_ok)
520-
self.assertEqual(
521-
root.status.canonical_code,
522-
trace_api.status.StatusCanonicalCode.OK,
523-
)
524-
self.assertIs(root.status.description, None)
514+
self.assertIs(root.status, None)
515+
516+
def test_error_status(self):
517+
try:
518+
with trace.Tracer("test_error_status").start_span("root") as root:
519+
raise Exception("unknown")
520+
except Exception: # pylint: disable=broad-except
521+
pass
522+
523+
self.assertIs(root.status.canonical_code, StatusCanonicalCode.UNKNOWN)
524+
self.assertEqual(root.status.description, "Exception: unknown")
525525

526526

527527
def span_event_start_fmt(span_processor_name, span_name):

0 commit comments

Comments
 (0)