Skip to content

Commit 7f57455

Browse files
hectorhdzgc24t
authored andcommitted
Add set_status to Span (#213)
1 parent 63f559e commit 7f57455

File tree

6 files changed

+249
-1
lines changed

6 files changed

+249
-1
lines changed

docs/opentelemetry.trace.rst

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
opentelemetry.trace package
22
===========================
33

4-
.. automodule:: opentelemetry.trace
4+
Submodules
5+
----------
6+
7+
.. toctree::
8+
9+
opentelemetry.trace.status
10+
11+
Module contents
12+
---------------
13+
14+
.. automodule:: opentelemetry.trace

docs/opentelemetry.trace.status.rst

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
opentelemetry.trace.status
2+
==========================
3+
4+
.. automodule:: opentelemetry.trace.status
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

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

+6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import typing
6767
from contextlib import contextmanager
6868

69+
from opentelemetry.trace.status import Status
6970
from opentelemetry.util import loader, types
7071

7172
# TODO: quarantine
@@ -227,6 +228,11 @@ def is_recording_events(self) -> bool:
227228
events with the add_event operation and attributes using set_attribute.
228229
"""
229230

231+
def set_status(self, status: Status) -> None:
232+
"""Sets the Status of the Span. If used, this will override the default
233+
Span status, which is OK.
234+
"""
235+
230236
def __enter__(self) -> "Span":
231237
"""Invoked when `Span` is used as a context manager.
232238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Copyright 2019, OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import enum
16+
import typing
17+
18+
19+
class StatusCanonicalCode(enum.Enum):
20+
"""Represents the canonical set of status codes of a finished Span."""
21+
22+
OK = 0
23+
"""Not an error, returned on success."""
24+
25+
CANCELLED = 1
26+
"""The operation was cancelled, typically by the caller."""
27+
28+
UNKNOWN = 2
29+
"""Unknown error.
30+
31+
For example, this error may be returned when a Status value received from
32+
another address space belongs to an error space that is not known in this
33+
address space. Also errors raised by APIs that do not return enough error
34+
information may be converted to this error.
35+
"""
36+
37+
INVALID_ARGUMENT = 3
38+
"""The client specified an invalid argument.
39+
40+
Note that this differs from FAILED_PRECONDITION. INVALID_ARGUMENT indicates
41+
arguments that are problematic regardless of the state of the system (e.g.,
42+
a malformed file name).
43+
"""
44+
45+
DEADLINE_EXCEEDED = 4
46+
"""The deadline expired before the operation could complete.
47+
48+
For operations that change the state of the system, this error may be
49+
returned even if the operation has completed successfully. For example, a
50+
successful response from a server could have been delayed long
51+
"""
52+
53+
NOT_FOUND = 5
54+
"""Some requested entity (e.g., file or directory) was not found.
55+
56+
Note to server developers: if a request is denied for an entire class of
57+
users, such as gradual feature rollout or undocumented whitelist, NOT_FOUND
58+
may be used. If a request is denied for some users within a class of users,
59+
such as user-based access control, PERMISSION_DENIED must be used.
60+
"""
61+
62+
ALREADY_EXISTS = 6
63+
"""The entity that a client attempted to create (e.g., file or directory)
64+
already exists.
65+
"""
66+
67+
PERMISSION_DENIED = 7
68+
"""The caller does not have permission to execute the specified operation.
69+
70+
PERMISSION_DENIED must not be used for rejections caused by exhausting some
71+
resource (use RESOURCE_EXHAUSTED instead for those errors).
72+
PERMISSION_DENIED must not be used if the caller can not be identified (use
73+
UNAUTHENTICATED instead for those errors). This error code does not imply
74+
the request is valid or the requested entity exists or satisfies other
75+
pre-conditions.
76+
"""
77+
78+
RESOURCE_EXHAUSTED = 8
79+
"""Some resource has been exhausted, perhaps a per-user quota, or perhaps
80+
the entire file system is out of space.
81+
"""
82+
83+
FAILED_PRECONDITION = 9
84+
"""The operation was rejected because the system is not in a state required
85+
for the operation's execution.
86+
87+
For example, the directory to be deleted is non-empty, an rmdir operation
88+
is applied to a non-directory, etc. Service implementors can use the
89+
following guidelines to decide between FAILED_PRECONDITION, ABORTED, and
90+
UNAVAILABLE:
91+
92+
(a) Use UNAVAILABLE if the client can retry just the failing call.
93+
(b) Use ABORTED if the client should retry at a higher level (e.g.,
94+
when a client-specified test-and-set fails, indicating the client
95+
should restart a read-modify-write sequence).
96+
(c) Use FAILED_PRECONDITION if the client should not retry until the
97+
system state has been explicitly fixed.
98+
99+
E.g., if an "rmdir" fails because the directory is non-empty,
100+
FAILED_PRECONDITION should be returned since the client should not retry
101+
unless the files are deleted from the directory.
102+
"""
103+
104+
ABORTED = 10
105+
"""The operation was aborted, typically due to a concurrency issue such as a
106+
sequencer check failure or transaction abort.
107+
108+
See the guidelines above for deciding between FAILED_PRECONDITION, ABORTED,
109+
and UNAVAILABLE.
110+
"""
111+
112+
OUT_OF_RANGE = 11
113+
"""The operation was attempted past the valid range.
114+
115+
E.g., seeking or reading past end-of-file. Unlike INVALID_ARGUMENT, this
116+
error indicates a problem that may be fixed if the system state changes.
117+
For example, a 32-bit file system will generate INVALID_ARGUMENT if asked
118+
to read at an offset that is not in the range [0,2^32-1],but it will
119+
generate OUT_OF_RANGE if asked to read from an offset past the current file
120+
size. There is a fair bit of overlap between FAILED_PRECONDITION and
121+
OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
122+
when it applies so that callers who are iterating through a space can
123+
easily look for an OUT_OF_RANGE error to detect when they are done.
124+
"""
125+
126+
UNIMPLEMENTED = 12
127+
"""The operation is not implemented or is not supported/enabled in this
128+
service.
129+
"""
130+
131+
INTERNAL = 13
132+
"""Internal errors.
133+
134+
This means that some invariants expected by the underlying system have been
135+
broken. This error code is reserved for serious errors.
136+
"""
137+
138+
UNAVAILABLE = 14
139+
"""The service is currently unavailable.
140+
141+
This is most likely a transient condition, which can be corrected by
142+
retrying with a backoff. Note that it is not always safe to retry
143+
non-idempotent operations.
144+
"""
145+
146+
DATA_LOSS = 15
147+
"""Unrecoverable data loss or corruption."""
148+
149+
UNAUTHENTICATED = 16
150+
"""The request does not have valid authentication credentials for the
151+
operation.
152+
"""
153+
154+
155+
class Status:
156+
"""Represents the status of a finished Span.
157+
158+
Args:
159+
canonical_code: The canonical status code that describes the result
160+
status of the operation.
161+
description: An optional description of the status.
162+
"""
163+
164+
def __init__(
165+
self,
166+
canonical_code: "StatusCanonicalCode" = StatusCanonicalCode.OK,
167+
description: typing.Optional[str] = None,
168+
):
169+
self._canonical_code = canonical_code
170+
self._description = description
171+
172+
@property
173+
def canonical_code(self) -> "StatusCanonicalCode":
174+
"""Represents the canonical status code of a finished Span."""
175+
return self._canonical_code
176+
177+
@property
178+
def description(self) -> typing.Optional[str]:
179+
"""Status description"""
180+
return self._description
181+
182+
@property
183+
def is_ok(self) -> bool:
184+
"""Returns false if this represents an error, true otherwise."""
185+
return self._canonical_code is StatusCanonicalCode.OK

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

+9
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ def __init__(
143143
self.kind = kind
144144

145145
self.span_processor = span_processor
146+
self.status = trace_api.Status()
146147
self._lock = threading.Lock()
147148

148149
if attributes is None:
@@ -285,6 +286,14 @@ def update_name(self, name: str) -> None:
285286
def is_recording_events(self) -> bool:
286287
return True
287288

289+
def set_status(self, status: trace_api.Status) -> None:
290+
with self._lock:
291+
has_ended = self.end_time is not None
292+
if has_ended:
293+
logger.warning("Calling set_status() on an ended span.")
294+
return
295+
self.status = status
296+
288297

289298
def generate_span_id() -> int:
290299
"""Get a new random span ID.

opentelemetry-sdk/tests/trace/test_trace.py

+31
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,24 @@ def test_start_span(self):
288288
span.start()
289289
self.assertEqual(start_time, span.start_time)
290290

291+
# default status
292+
self.assertTrue(span.status.is_ok)
293+
self.assertIs(
294+
span.status.canonical_code, trace_api.status.StatusCanonicalCode.OK
295+
)
296+
self.assertIs(span.status.description, None)
297+
298+
# status
299+
new_status = trace_api.status.Status(
300+
trace_api.status.StatusCanonicalCode.CANCELLED, "Test description"
301+
)
302+
span.set_status(new_status)
303+
self.assertIs(
304+
span.status.canonical_code,
305+
trace_api.status.StatusCanonicalCode.CANCELLED,
306+
)
307+
self.assertIs(span.status.description, "Test description")
308+
291309
def test_span_override_start_and_end_time(self):
292310
"""Span sending custom start_time and end_time values"""
293311
span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext))
@@ -334,6 +352,19 @@ def test_ended_span(self):
334352
root.update_name("xxx")
335353
self.assertEqual(root.name, "root")
336354

355+
new_status = trace_api.status.Status(
356+
trace_api.status.StatusCanonicalCode.CANCELLED,
357+
"Test description",
358+
)
359+
root.set_status(new_status)
360+
# default status
361+
self.assertTrue(root.status.is_ok)
362+
self.assertEqual(
363+
root.status.canonical_code,
364+
trace_api.status.StatusCanonicalCode.OK,
365+
)
366+
self.assertIs(root.status.description, None)
367+
337368

338369
def span_event_start_fmt(span_processor_name, span_name):
339370
return span_processor_name + ":" + span_name + ":start"

0 commit comments

Comments
 (0)