Skip to content

Commit b423d16

Browse files
committed
Set status for ended spans
Fixes #292
1 parent 4ead3f4 commit b423d16

File tree

5 files changed

+83
-22
lines changed

5 files changed

+83
-22
lines changed

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

+8-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.
@@ -224,6 +226,7 @@ def __exit__(
224226
exc_tb: typing.Optional[python_types.TracebackType],
225227
) -> None:
226228
"""Ends context manager and calls `end` on the `Span`."""
229+
227230
self.end()
228231

229232

@@ -392,6 +395,7 @@ def start_span(
392395
attributes: typing.Optional[types.Attributes] = None,
393396
links: typing.Sequence[Link] = (),
394397
start_time: typing.Optional[int] = None,
398+
auto_update_status: bool = True,
395399
) -> "Span":
396400
"""Starts a span.
397401
@@ -423,6 +427,8 @@ def start_span(
423427
attributes: The span's attributes.
424428
links: Links span to other spans
425429
start_time: Sets the start time of a span
430+
auto_update_status: Defines if the status should be updated
431+
automatically when the span finishes
426432
427433
Returns:
428434
The newly-created span.

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

+24-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

@@ -183,3 +183,25 @@ def description(self) -> typing.Optional[str]:
183183
def is_ok(self) -> bool:
184184
"""Returns false if this represents an error, true otherwise."""
185185
return self._canonical_code is StatusCanonicalCode.OK
186+
187+
188+
class StatusError(Exception):
189+
def __init__(
190+
self,
191+
canonical_code: StatusCanonicalCode,
192+
description: typing.Optional[str] = None,
193+
):
194+
self._canonical_code = canonical_code
195+
self._description = description
196+
197+
super(StatusError, self).__init__()
198+
199+
@property
200+
def canonical_code(self) -> StatusCanonicalCode:
201+
"""Represents the canonical status code of a finished Span."""
202+
return self._canonical_code
203+
204+
@property
205+
def description(self) -> typing.Optional[str]:
206+
"""Status description"""
207+
return self._description

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

+37-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, StatusError
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+
auto_update_status: 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._auto_update_status = auto_update_status
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,
@@ -275,6 +282,30 @@ def set_status(self, status: trace_api.Status) -> None:
275282
return
276283
self.status = status
277284

285+
def __exit__(
286+
self,
287+
exc_type: Optional[Type[BaseException]],
288+
exc_val: Optional[BaseException],
289+
exc_tb: Optional[TracebackType],
290+
) -> None:
291+
"""Ends context manager and calls `end` on the `Span`."""
292+
293+
if self.status is None and self._auto_update_status:
294+
if exc_val is not None:
295+
296+
if isinstance(exc_val, StatusError):
297+
self.set_status(
298+
Status(exc_val.canonical_code, exc_val.description)
299+
)
300+
301+
else:
302+
self.set_status(Status(StatusCanonicalCode.UNKNOWN))
303+
304+
else:
305+
self.set_status(Status(StatusCanonicalCode.OK))
306+
307+
super().__exit__(exc_type, exc_val, exc_tb)
308+
278309

279310
def generate_span_id() -> int:
280311
"""Get a new random span ID.
@@ -334,14 +365,15 @@ def start_as_current_span(
334365
span = self.start_span(name, parent, kind, attributes, links)
335366
return self.use_span(span, end_on_exit=True)
336367

337-
def start_span(
368+
def start_span( # pylint: disable=too-many-locals
338369
self,
339370
name: str,
340371
parent: trace_api.ParentSpan = trace_api.Tracer.CURRENT_SPAN,
341372
kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
342373
attributes: Optional[types.Attributes] = None,
343374
links: Sequence[trace_api.Link] = (),
344375
start_time: Optional[int] = None,
376+
auto_update_status: bool = True,
345377
) -> "Span":
346378
"""See `opentelemetry.trace.Tracer.start_span`."""
347379

@@ -401,6 +433,7 @@ def start_span(
401433
span_processor=self._active_span_processor,
402434
kind=kind,
403435
links=links,
436+
auto_update_status=auto_update_status,
404437
)
405438
span.start(start_time=start_time)
406439
else:

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, StatusError
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+
with self.assertRaises(StatusError):
518+
with trace.Tracer("test_error_status").start_span("root") as root:
519+
raise StatusError(StatusCanonicalCode.CANCELLED, "cancelled")
520+
521+
self.assertIs(
522+
root.status.canonical_code, StatusCanonicalCode.CANCELLED
523+
)
524+
self.assertEqual(root.status.description, "cancelled")
525525

526526

527527
def span_event_start_fmt(span_processor_name, span_name):

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ commands_pre =
9090
mypyinstalled: pip install file://{toxinidir}/opentelemetry-api/
9191

9292
commands =
93-
test: pytest
93+
test: pytest {posargs}
9494
coverage: {toxinidir}/scripts/coverage.sh
9595

9696
mypy: mypy --namespace-packages opentelemetry-api/src/opentelemetry/

0 commit comments

Comments
 (0)