Skip to content

Commit b24a465

Browse files
hectorhdzgc24t
authored andcommitted
Add set_status to Span (open-telemetry#213)
1 parent c3efeb0 commit b24a465

File tree

6 files changed

+249
-1
lines changed

6 files changed

+249
-1
lines changed

docs/opentelemetry.trace.rst

Lines changed: 11 additions & 1 deletion
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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
Lines changed: 185 additions & 0 deletions
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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def __init__(
144144
self.kind = kind
145145

146146
self.span_processor = span_processor
147+
self.status = trace_api.Status()
147148
self._lock = threading.Lock()
148149

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

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

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

opentelemetry-sdk/tests/trace/test_trace.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,24 @@ def test_start_span(self):
312312
span.start()
313313
self.assertEqual(start_time, span.start_time)
314314

315+
# default status
316+
self.assertTrue(span.status.is_ok)
317+
self.assertIs(
318+
span.status.canonical_code, trace_api.status.StatusCanonicalCode.OK
319+
)
320+
self.assertIs(span.status.description, None)
321+
322+
# status
323+
new_status = trace_api.status.Status(
324+
trace_api.status.StatusCanonicalCode.CANCELLED, "Test description"
325+
)
326+
span.set_status(new_status)
327+
self.assertIs(
328+
span.status.canonical_code,
329+
trace_api.status.StatusCanonicalCode.CANCELLED,
330+
)
331+
self.assertIs(span.status.description, "Test description")
332+
315333
def test_span_override_start_and_end_time(self):
316334
"""Span sending custom start_time and end_time values"""
317335
span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext))
@@ -358,6 +376,19 @@ def test_ended_span(self):
358376
root.update_name("xxx")
359377
self.assertEqual(root.name, "root")
360378

379+
new_status = trace_api.status.Status(
380+
trace_api.status.StatusCanonicalCode.CANCELLED,
381+
"Test description",
382+
)
383+
root.set_status(new_status)
384+
# default status
385+
self.assertTrue(root.status.is_ok)
386+
self.assertEqual(
387+
root.status.canonical_code,
388+
trace_api.status.StatusCanonicalCode.OK,
389+
)
390+
self.assertIs(root.status.description, None)
391+
361392

362393
def span_event_start_fmt(span_processor_name, span_name):
363394
return span_processor_name + ":" + span_name + ":start"

0 commit comments

Comments
 (0)