Skip to content

Commit bb517e1

Browse files
authored
Map span status properly to GCT (#113)
1 parent 721ede5 commit bb517e1

3 files changed

Lines changed: 66 additions & 42 deletions

File tree

opentelemetry-exporter-google-cloud/src/opentelemetry/exporter/cloud_trace/__init__.py

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,9 @@
5656
import opentelemetry.trace as trace_api
5757
import pkg_resources
5858
from google.cloud.trace_v2 import TraceServiceClient
59-
from google.cloud.trace_v2.proto.trace_pb2 import AttributeValue
60-
from google.cloud.trace_v2.proto.trace_pb2 import Span as ProtoSpan
61-
from google.cloud.trace_v2.proto.trace_pb2 import TruncatableString
59+
from google.cloud.trace_v2.proto import trace_pb2
6260
from google.protobuf.timestamp_pb2 import Timestamp
63-
from google.rpc.status_pb2 import Status
61+
from google.rpc import code_pb2, status_pb2
6462
from opentelemetry.exporter.google.version import (
6563
__version__ as google_ext_version,
6664
)
@@ -72,6 +70,7 @@
7270
get_hexadecimal_span_id,
7371
get_hexadecimal_trace_id,
7472
)
73+
from opentelemetry.trace.status import StatusCode
7574
from opentelemetry.util import types
7675

7776
logger = logging.getLogger(__name__)
@@ -204,7 +203,7 @@ def _get_truncatable_str_object(str_to_convert: str, max_length: int):
204203
truncated bytes count."""
205204
truncated, truncated_byte_count = _truncate_str(str_to_convert, max_length)
206205

207-
return TruncatableString(
206+
return trace_pb2.TruncatableString(
208207
value=truncated, truncated_byte_count=truncated_byte_count
209208
)
210209

@@ -216,22 +215,30 @@ def _truncate_str(str_to_check: str, limit: int) -> Tuple[str, int]:
216215
return truncated_str, len(encoded) - len(truncated_str.encode("utf-8"))
217216

218217

219-
def _extract_status(status: trace_api.Status) -> Optional[Status]:
220-
"""Convert a Status object to protobuf object."""
221-
if not status:
222-
return None
223-
status_dict = {
224-
"details": None,
225-
"code": status.status_code.value,
226-
} # type: Dict[str, Any]
227-
228-
if status.description is not None:
229-
status_dict["message"] = status.description
218+
def _extract_status(status: trace_api.Status) -> Optional[status_pb2.Status]:
219+
"""Convert a OTel Status to protobuf Status."""
220+
if status.status_code is StatusCode.UNSET:
221+
status_proto = None
222+
elif status.status_code is StatusCode.OK:
223+
status_proto = status_pb2.Status(code=code_pb2.OK)
224+
elif status.status_code is StatusCode.ERROR:
225+
status_proto = status_pb2.Status(
226+
code=code_pb2.UNKNOWN, message=status.description
227+
)
228+
# future added value
229+
else:
230+
logger.info(
231+
"Couldn't handle OTel status code %s, assuming error",
232+
status.status_code,
233+
)
234+
status_proto = status_pb2.Status(
235+
code=code_pb2.UNKNOWN, message=status.description
236+
)
230237

231-
return Status(**status_dict)
238+
return status_proto
232239

233240

234-
def _extract_links(links: Sequence[trace_api.Link]) -> ProtoSpan.Links:
241+
def _extract_links(links: Sequence[trace_api.Link]) -> trace_pb2.Span.Links:
235242
"""Convert span.links"""
236243
if not links:
237244
return None
@@ -263,12 +270,12 @@ def _extract_links(links: Sequence[trace_api.Link]) -> ProtoSpan.Links:
263270
),
264271
}
265272
)
266-
return ProtoSpan.Links(
273+
return trace_pb2.Span.Links(
267274
link=extracted_links, dropped_links_count=dropped_links
268275
)
269276

270277

271-
def _extract_events(events: Sequence[Event]) -> ProtoSpan.TimeEvents:
278+
def _extract_events(events: Sequence[Event]) -> trace_pb2.Span.TimeEvents:
272279
"""Convert span.events to dict."""
273280
if not events:
274281
return None
@@ -301,7 +308,7 @@ def _extract_events(events: Sequence[Event]) -> ProtoSpan.TimeEvents:
301308
},
302309
}
303310
)
304-
return ProtoSpan.TimeEvents(
311+
return trace_pb2.Span.TimeEvents(
305312
time_event=logs,
306313
dropped_annotations_count=dropped_annontations,
307314
dropped_message_events_count=0,
@@ -310,18 +317,20 @@ def _extract_events(events: Sequence[Event]) -> ProtoSpan.TimeEvents:
310317

311318
# pylint: disable=no-member
312319
SPAN_KIND_MAPPING = {
313-
trace_api.SpanKind.INTERNAL: ProtoSpan.SpanKind.INTERNAL,
314-
trace_api.SpanKind.CLIENT: ProtoSpan.SpanKind.CLIENT,
315-
trace_api.SpanKind.SERVER: ProtoSpan.SpanKind.SERVER,
316-
trace_api.SpanKind.PRODUCER: ProtoSpan.SpanKind.PRODUCER,
317-
trace_api.SpanKind.CONSUMER: ProtoSpan.SpanKind.CONSUMER,
320+
trace_api.SpanKind.INTERNAL: trace_pb2.Span.SpanKind.INTERNAL,
321+
trace_api.SpanKind.CLIENT: trace_pb2.Span.SpanKind.CLIENT,
322+
trace_api.SpanKind.SERVER: trace_pb2.Span.SpanKind.SERVER,
323+
trace_api.SpanKind.PRODUCER: trace_pb2.Span.SpanKind.PRODUCER,
324+
trace_api.SpanKind.CONSUMER: trace_pb2.Span.SpanKind.CONSUMER,
318325
}
319326

320327

321328
# pylint: disable=no-member
322-
def _extract_span_kind(span_kind: trace_api.SpanKind) -> ProtoSpan.SpanKind:
329+
def _extract_span_kind(
330+
span_kind: trace_api.SpanKind,
331+
) -> trace_pb2.Span.SpanKind:
323332
return SPAN_KIND_MAPPING.get(
324-
span_kind, ProtoSpan.SpanKind.SPAN_KIND_UNSPECIFIED
333+
span_kind, trace_pb2.Span.SpanKind.SPAN_KIND_UNSPECIFIED
325334
)
326335

327336

@@ -386,11 +395,11 @@ def _extract_attributes(
386395
attrs: types.Attributes,
387396
num_attrs_limit: int,
388397
add_agent_attr: bool = False,
389-
) -> ProtoSpan.Attributes:
398+
) -> trace_pb2.Span.Attributes:
390399
"""Convert span.attributes to dict."""
391400
attributes_dict = BoundedDict(
392401
num_attrs_limit
393-
) # type: BoundedDict[str, AttributeValue]
402+
) # type: BoundedDict[str, trace_pb2.AttributeValue]
394403
invalid_value_dropped_count = 0
395404
for key, value in attrs.items() if attrs else []:
396405
key = _truncate_str(key, 128)[0]
@@ -411,14 +420,16 @@ def _extract_attributes(
411420
_strip_characters(google_ext_version),
412421
)
413422
)
414-
return ProtoSpan.Attributes(
423+
return trace_pb2.Span.Attributes(
415424
attribute_map=attributes_dict,
416425
dropped_attributes_count=attributes_dict.dropped # type: ignore[attr-defined]
417426
+ invalid_value_dropped_count,
418427
)
419428

420429

421-
def _format_attribute_value(value: types.AttributeValue) -> AttributeValue:
430+
def _format_attribute_value(
431+
value: types.AttributeValue,
432+
) -> trace_pb2.AttributeValue:
422433
if isinstance(value, bool):
423434
value_type = "bool_value"
424435
elif isinstance(value, int):
@@ -443,4 +454,4 @@ def _format_attribute_value(value: types.AttributeValue) -> AttributeValue:
443454
)
444455
return None
445456

446-
return AttributeValue(**{value_type: value})
457+
return trace_pb2.AttributeValue(**{value_type: value})

opentelemetry-exporter-google-cloud/tests/test_cloud_trace_exporter.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from google.cloud.trace_v2.proto.trace_pb2 import AttributeValue
2020
from google.cloud.trace_v2.proto.trace_pb2 import Span as ProtoSpan
2121
from google.cloud.trace_v2.proto.trace_pb2 import TruncatableString
22+
from google.rpc import code_pb2
2223
from google.rpc.status_pb2 import Status
2324
from opentelemetry.exporter.cloud_trace import (
2425
MAX_EVENT_ATTRS,
@@ -159,7 +160,7 @@ def test_export(self):
159160
}
160161
),
161162
"links": None,
162-
"status": Status(code=StatusCode.UNSET.value),
163+
"status": None,
163164
"time_events": None,
164165
"start_time": None,
165166
"end_time": None,
@@ -178,25 +179,35 @@ def test_export(self):
178179
"projects/{}".format(self.project_id), [cloud_trace_spans]
179180
)
180181

181-
def test_extract_none_status(self):
182-
self.assertIsNone(_extract_status(None))
182+
def test_extract_status_code_unset(self):
183+
self.assertIsNone(
184+
_extract_status(SpanStatus(status_code=StatusCode.UNSET))
185+
)
183186

184-
def test_extract_status_code(self):
187+
def test_extract_status_code_ok(self):
185188
self.assertEqual(
186189
_extract_status(SpanStatus(status_code=StatusCode.OK)),
187-
Status(details=None, code=StatusCode.OK.value),
190+
Status(code=code_pb2.OK),
188191
)
189192

190-
def test_extract_status_code_and_desc(self):
193+
def test_extract_status_code_error(self):
191194
self.assertEqual(
192195
_extract_status(
193196
SpanStatus(
194-
status_code=StatusCode.UNSET, description="error_desc",
197+
status_code=StatusCode.ERROR, description="error_desc",
195198
)
196199
),
197-
Status(
198-
details=None, code=StatusCode.UNSET.value, message="error_desc"
200+
Status(code=code_pb2.UNKNOWN, message="error_desc"),
201+
)
202+
203+
def test_extract_status_code_future_added(self):
204+
self.assertEqual(
205+
_extract_status(
206+
SpanStatus(
207+
status_code=mock.Mock(), description="unknown_description",
208+
)
199209
),
210+
Status(code=code_pb2.UNKNOWN, message="unknown_description"),
200211
)
201212

202213
def test_extract_empty_attributes(self):

opentelemetry-tools-google-cloud/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Map span status code properly to GCT
6+
([#113](https://github.com/GoogleCloudPlatform/opentelemetry-operations-python/pull/113))
57
- Handle mixed case cloud propagator headers
68
([#112](https://github.com/GoogleCloudPlatform/opentelemetry-operations-python/pull/112))
79

0 commit comments

Comments
 (0)