From 7f574557d95fcd7bf27b8f5ee5fb116c3e9ce460 Mon Sep 17 00:00:00 2001 From: Hector Hernandez <39923391+hectorhdzg@users.noreply.github.com> Date: Wed, 16 Oct 2019 16:19:46 -0700 Subject: [PATCH] Add set_status to Span (#213) --- docs/opentelemetry.trace.rst | 12 +- docs/opentelemetry.trace.status.rst | 7 + .../src/opentelemetry/trace/__init__.py | 6 + .../src/opentelemetry/trace/status.py | 185 ++++++++++++++++++ .../src/opentelemetry/sdk/trace/__init__.py | 9 + opentelemetry-sdk/tests/trace/test_trace.py | 31 +++ 6 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 docs/opentelemetry.trace.status.rst create mode 100644 opentelemetry-api/src/opentelemetry/trace/status.py diff --git a/docs/opentelemetry.trace.rst b/docs/opentelemetry.trace.rst index cec44bd817..a57b5dcbff 100644 --- a/docs/opentelemetry.trace.rst +++ b/docs/opentelemetry.trace.rst @@ -1,4 +1,14 @@ opentelemetry.trace package =========================== -.. automodule:: opentelemetry.trace +Submodules +---------- + +.. toctree:: + + opentelemetry.trace.status + +Module contents +--------------- + +.. automodule:: opentelemetry.trace \ No newline at end of file diff --git a/docs/opentelemetry.trace.status.rst b/docs/opentelemetry.trace.status.rst new file mode 100644 index 0000000000..0205446c80 --- /dev/null +++ b/docs/opentelemetry.trace.status.rst @@ -0,0 +1,7 @@ +opentelemetry.trace.status +========================== + +.. automodule:: opentelemetry.trace.status + :members: + :undoc-members: + :show-inheritance: diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 1b0a3e758f..fac9f55da7 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -66,6 +66,7 @@ import typing from contextlib import contextmanager +from opentelemetry.trace.status import Status from opentelemetry.util import loader, types # TODO: quarantine @@ -227,6 +228,11 @@ def is_recording_events(self) -> bool: events with the add_event operation and attributes using set_attribute. """ + def set_status(self, status: Status) -> None: + """Sets the Status of the Span. If used, this will override the default + Span status, which is OK. + """ + def __enter__(self) -> "Span": """Invoked when `Span` is used as a context manager. diff --git a/opentelemetry-api/src/opentelemetry/trace/status.py b/opentelemetry-api/src/opentelemetry/trace/status.py new file mode 100644 index 0000000000..4fc50b33e5 --- /dev/null +++ b/opentelemetry-api/src/opentelemetry/trace/status.py @@ -0,0 +1,185 @@ +# Copyright 2019, OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import enum +import typing + + +class StatusCanonicalCode(enum.Enum): + """Represents the canonical set of status codes of a finished Span.""" + + OK = 0 + """Not an error, returned on success.""" + + CANCELLED = 1 + """The operation was cancelled, typically by the caller.""" + + UNKNOWN = 2 + """Unknown error. + + For example, this error may be returned when a Status value received from + another address space belongs to an error space that is not known in this + address space. Also errors raised by APIs that do not return enough error + information may be converted to this error. + """ + + INVALID_ARGUMENT = 3 + """The client specified an invalid argument. + + Note that this differs from FAILED_PRECONDITION. INVALID_ARGUMENT indicates + arguments that are problematic regardless of the state of the system (e.g., + a malformed file name). + """ + + DEADLINE_EXCEEDED = 4 + """The deadline expired before the operation could complete. + + For operations that change the state of the system, this error may be + returned even if the operation has completed successfully. For example, a + successful response from a server could have been delayed long + """ + + NOT_FOUND = 5 + """Some requested entity (e.g., file or directory) was not found. + + Note to server developers: if a request is denied for an entire class of + users, such as gradual feature rollout or undocumented whitelist, NOT_FOUND + may be used. If a request is denied for some users within a class of users, + such as user-based access control, PERMISSION_DENIED must be used. + """ + + ALREADY_EXISTS = 6 + """The entity that a client attempted to create (e.g., file or directory) + already exists. + """ + + PERMISSION_DENIED = 7 + """The caller does not have permission to execute the specified operation. + + PERMISSION_DENIED must not be used for rejections caused by exhausting some + resource (use RESOURCE_EXHAUSTED instead for those errors). + PERMISSION_DENIED must not be used if the caller can not be identified (use + UNAUTHENTICATED instead for those errors). This error code does not imply + the request is valid or the requested entity exists or satisfies other + pre-conditions. + """ + + RESOURCE_EXHAUSTED = 8 + """Some resource has been exhausted, perhaps a per-user quota, or perhaps + the entire file system is out of space. + """ + + FAILED_PRECONDITION = 9 + """The operation was rejected because the system is not in a state required + for the operation's execution. + + For example, the directory to be deleted is non-empty, an rmdir operation + is applied to a non-directory, etc. Service implementors can use the + following guidelines to decide between FAILED_PRECONDITION, ABORTED, and + UNAVAILABLE: + + (a) Use UNAVAILABLE if the client can retry just the failing call. + (b) Use ABORTED if the client should retry at a higher level (e.g., + when a client-specified test-and-set fails, indicating the client + should restart a read-modify-write sequence). + (c) Use FAILED_PRECONDITION if the client should not retry until the + system state has been explicitly fixed. + + E.g., if an "rmdir" fails because the directory is non-empty, + FAILED_PRECONDITION should be returned since the client should not retry + unless the files are deleted from the directory. + """ + + ABORTED = 10 + """The operation was aborted, typically due to a concurrency issue such as a + sequencer check failure or transaction abort. + + See the guidelines above for deciding between FAILED_PRECONDITION, ABORTED, + and UNAVAILABLE. + """ + + OUT_OF_RANGE = 11 + """The operation was attempted past the valid range. + + E.g., seeking or reading past end-of-file. Unlike INVALID_ARGUMENT, this + error indicates a problem that may be fixed if the system state changes. + For example, a 32-bit file system will generate INVALID_ARGUMENT if asked + to read at an offset that is not in the range [0,2^32-1],but it will + generate OUT_OF_RANGE if asked to read from an offset past the current file + size. There is a fair bit of overlap between FAILED_PRECONDITION and + OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error) + when it applies so that callers who are iterating through a space can + easily look for an OUT_OF_RANGE error to detect when they are done. + """ + + UNIMPLEMENTED = 12 + """The operation is not implemented or is not supported/enabled in this + service. + """ + + INTERNAL = 13 + """Internal errors. + + This means that some invariants expected by the underlying system have been + broken. This error code is reserved for serious errors. + """ + + UNAVAILABLE = 14 + """The service is currently unavailable. + + This is most likely a transient condition, which can be corrected by + retrying with a backoff. Note that it is not always safe to retry + non-idempotent operations. + """ + + DATA_LOSS = 15 + """Unrecoverable data loss or corruption.""" + + UNAUTHENTICATED = 16 + """The request does not have valid authentication credentials for the + operation. + """ + + +class Status: + """Represents the status of a finished Span. + + Args: + canonical_code: The canonical status code that describes the result + status of the operation. + description: An optional description of the status. + """ + + def __init__( + self, + canonical_code: "StatusCanonicalCode" = StatusCanonicalCode.OK, + description: typing.Optional[str] = None, + ): + self._canonical_code = canonical_code + self._description = description + + @property + def canonical_code(self) -> "StatusCanonicalCode": + """Represents the canonical status code of a finished Span.""" + return self._canonical_code + + @property + def description(self) -> typing.Optional[str]: + """Status description""" + return self._description + + @property + def is_ok(self) -> bool: + """Returns false if this represents an error, true otherwise.""" + return self._canonical_code is StatusCanonicalCode.OK diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index c7b33a353d..7b274f852f 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -143,6 +143,7 @@ def __init__( self.kind = kind self.span_processor = span_processor + self.status = trace_api.Status() self._lock = threading.Lock() if attributes is None: @@ -285,6 +286,14 @@ def update_name(self, name: str) -> None: def is_recording_events(self) -> bool: return True + def set_status(self, status: trace_api.Status) -> None: + with self._lock: + has_ended = self.end_time is not None + if has_ended: + logger.warning("Calling set_status() on an ended span.") + return + self.status = status + def generate_span_id() -> int: """Get a new random span ID. diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index b55ea897fa..fa8547a0a5 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -288,6 +288,24 @@ def test_start_span(self): span.start() self.assertEqual(start_time, span.start_time) + # default status + self.assertTrue(span.status.is_ok) + self.assertIs( + span.status.canonical_code, trace_api.status.StatusCanonicalCode.OK + ) + self.assertIs(span.status.description, None) + + # status + new_status = trace_api.status.Status( + trace_api.status.StatusCanonicalCode.CANCELLED, "Test description" + ) + span.set_status(new_status) + self.assertIs( + span.status.canonical_code, + trace_api.status.StatusCanonicalCode.CANCELLED, + ) + self.assertIs(span.status.description, "Test description") + def test_span_override_start_and_end_time(self): """Span sending custom start_time and end_time values""" span = trace.Span("name", mock.Mock(spec=trace_api.SpanContext)) @@ -334,6 +352,19 @@ def test_ended_span(self): root.update_name("xxx") self.assertEqual(root.name, "root") + new_status = trace_api.status.Status( + trace_api.status.StatusCanonicalCode.CANCELLED, + "Test description", + ) + root.set_status(new_status) + # default status + self.assertTrue(root.status.is_ok) + self.assertEqual( + root.status.canonical_code, + trace_api.status.StatusCanonicalCode.OK, + ) + self.assertIs(root.status.description, None) + def span_event_start_fmt(span_processor_name, span_name): return span_processor_name + ":" + span_name + ":start"