Noticed while writing extensive tests for modernized tracing functionality that we've got this code
| withtracer.start_as_current_span( |
| name, kind=trace.SpanKind.CLIENT, attributes=attributes |
| ) asspan: |
| try: |
| yieldspan |
| exceptExceptionaserror: |
| span.set_status(Status(StatusCode.ERROR, str(error))) |
| span.record_exception(error) |
| raise |
| else: |
| span.set_status(Status(StatusCode.OK)) |
which doesn't do exactly what you expect with OpenTelemetry
Once yield span is invoked, if no exception was encountered, we unconditionally set the span's value to OK even if it previously were set to ERROR and here is a test
Reproduction
fromtestsimport_helpersasot_helpers@pytest.mark.skipif(notot_helpers.HAS_OPENTELEMETRY_INSTALLED,reason="Tracing requires OpenTelemetry",)deftest_trace_call_status():
fromopentelemetry.sdk.trace.exportimportSimpleSpanProcessorfromopentelemetry.sdk.trace.export.in_memory_span_exporterimport (
InMemorySpanExporter,
)
fromgoogle.cloud.spanner_v1._opentelemetry_tracingimporttrace_callfromopentelemetry.trace.statusimportStatus, StatusCodefromopentelemetry.sdk.traceimportTracerProviderfromopentelemetry.sdk.trace.samplingimportALWAYS_ONfromopentelemetryimporttracetracer_provider=TracerProvider(sampler=ALWAYS_ON)
trace_exporter=InMemorySpanExporter()
tracer_provider.add_span_processor(SimpleSpanProcessor(trace_exporter))
observability_options=dict(tracer_provider=tracer_provider)
withtrace_call(
"VerifyBehavior", observability_options=observability_options
) asspan:
span.set_status(Status(StatusCode.ERROR, "Our error exhibit"))
span_list=trace_exporter.get_finished_spans()
got_statuses= []
forspaninspan_list:
got_statuses.append(
(span.name, span.status.status_code, span.status.description)
)
want_statuses= [
("VerifyBehavior", StatusCode.ERROR, "Our error exhibit"),
]
assertgot_statuses==want_statuseswhich would fail with
$ nox -s system-3.8 -- -k test_trace_call_status
> assert got_statuses == want_statuses
E AssertionError: assert [('VerifyBeha...OK: 1>, None)] == [('VerifyBeha...ror exhibit')]E E At index 0 diff: ('VerifyBehavior', <StatusCode.OK: 1>, None) != ('VerifyBehavior', <StatusCode.ERROR: 2>, 'Our error exhibit')E Use -v to get more difftests/system/test_observability_options.py:180: AssertionErrorFix
diff --git a/google/cloud/spanner_v1/_opentelemetry_tracing.py b/google/cloud/spanner_v1/_opentelemetry_tracing.py
index 9472251..65b3441 100644
--- a/google/cloud/spanner_v1/_opentelemetry_tracing.py+++ b/google/cloud/spanner_v1/_opentelemetry_tracing.py@@ -142,6 +142,9 @@ def trace_call_end_lazily(
ctx_manager.__enter__()
def discard(exc_type=None, exc_value=None, exc_traceback=None):
+ if not exc_type:+ span.set_status(Status(StatusCode.OK))+
ctx_manager.__exit__(exc_type, exc_value, exc_traceback)
return discard
@@ -175,8 +178,12 @@ def trace_call(name, session=None, extra_attributes=None, observability_options=
span.record_exception(error)
raise
else:
- span.set_status(Status(StatusCode.OK))-+ if span._status.status_code == StatusCode.UNSET:+ # OpenTelemetry-Python only allows a status change+ # if the current code is UNSET or ERROR. At the end+ # of the generator's consumption, only set it to OK+ # it wasn't previously set otherwise+ span.set_status(Status(StatusCode.OK))
/cc @harshachinta
Noticed while writing extensive tests for modernized tracing functionality that we've got this code
python-spanner/google/cloud/spanner_v1/_opentelemetry_tracing.py
Lines 98 to 108 in ccae6e0
which doesn't do exactly what you expect with OpenTelemetry
Once
yield spanis invoked, if no exception was encountered, we unconditionally set the span's value to OK even if it previously were set to ERROR and here is a testReproduction
which would fail with
Fix
/cc @harshachinta