Skip to content

Commit 194824b

Browse files
alrexmauriciovasquezbernaltoumorokoshi
authored
api: ensure status is always string (#640)
Ensuring that exporters can always expect status descriptions to be a string. This was implemented to be defensive against instrumentations such as PyMongo, which currently set status as a dict. Co-authored-by: Mauricio Vásquez <[email protected]> Co-authored-by: Yusuke Tsutsumi <[email protected]>
1 parent 34a9616 commit 194824b

File tree

4 files changed

+53
-1
lines changed

4 files changed

+53
-1
lines changed

ext/opentelemetry-ext-otcollector/src/opentelemetry/ext/otcollector/trace_exporter/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def translate_to_collector(spans: Sequence[Span]):
9898
code=span.status.canonical_code.value,
9999
message=span.status.description,
100100
)
101+
101102
collector_span = trace_pb2.Span(
102103
name=trace_pb2.TruncatableString(value=span.name),
103104
kind=utils.get_collector_span_kind(span.kind),

ext/opentelemetry-ext-otcollector/tests/test_otcollector_trace_exporter.py

+9
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ def test_translate_to_collector(self):
154154
)
155155
otel_spans[0].end(end_time=end_times[0])
156156
otel_spans[1].start(start_time=start_times[1])
157+
otel_spans[1].set_status(
158+
trace_api.Status(
159+
trace_api.status.StatusCanonicalCode.INTERNAL, {"test", "val"},
160+
)
161+
)
157162
otel_spans[1].end(end_time=end_times[1])
158163
otel_spans[2].start(start_time=start_times[2])
159164
otel_spans[2].end(end_time=end_times[2])
@@ -263,6 +268,10 @@ def test_translate_to_collector(self):
263268
output_spans[0].links.link[0].type,
264269
trace_pb2.Span.Link.Type.TYPE_UNSPECIFIED,
265270
)
271+
self.assertEqual(
272+
output_spans[1].status.code,
273+
trace_api.status.StatusCanonicalCode.INTERNAL.value,
274+
)
266275
self.assertEqual(
267276
output_spans[2].links.link[0].type,
268277
trace_pb2.Span.Link.Type.PARENT_LINKED_SPAN,

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
# limitations under the License.
1414

1515
import enum
16+
import logging
1617
import typing
1718

19+
logger = logging.getLogger(__name__)
20+
1821

1922
class StatusCanonicalCode(enum.Enum):
2023
"""Represents the canonical set of status codes of a finished Span."""
@@ -167,7 +170,11 @@ def __init__(
167170
description: typing.Optional[str] = None,
168171
):
169172
self._canonical_code = canonical_code
170-
self._description = description
173+
self._description = None
174+
if description is not None and not isinstance(description, str):
175+
logger.warning("Invalid status description type, expected str")
176+
else:
177+
self._description = description
171178

172179
@property
173180
def canonical_code(self) -> StatusCanonicalCode:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright The 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 unittest
16+
from logging import WARNING
17+
18+
from opentelemetry.trace.status import Status, StatusCanonicalCode
19+
20+
21+
class TestStatus(unittest.TestCase):
22+
def test_constructor(self):
23+
status = Status()
24+
self.assertIs(status.canonical_code, StatusCanonicalCode.OK)
25+
self.assertIsNone(status.description)
26+
27+
status = Status(StatusCanonicalCode.UNAVAILABLE, "unavailable")
28+
self.assertIs(status.canonical_code, StatusCanonicalCode.UNAVAILABLE)
29+
self.assertEqual(status.description, "unavailable")
30+
31+
def test_invalid_description(self):
32+
with self.assertLogs(level=WARNING):
33+
status = Status(description={"test": "val"}) # type: ignore
34+
self.assertIs(status.canonical_code, StatusCanonicalCode.OK)
35+
self.assertEqual(status.description, None)

0 commit comments

Comments
 (0)