When trying to add span links in a span, the straightforward way
I have read in the documentation does not work. I always get AttributeError: 'Context' object has no attribute 'trace_id'
My latest attempt
Simplified code to reproduce issue
from opentelemetry import trace
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from shared_functionality.common.observability import make_global_tracer
make_global_tracer()
tracer = trace.get_tracer(__name__)
def message_broker_consumer_runner():
with tracer.start_as_current_span(name=f"consumer_runner.{None}") as consume_span:
carrier = {}
TraceContextTextMapPropagator().inject(carrier)
return carrier
def api_gateway():
with tracer.start_as_current_span(name=f"api_gateway.{None}") as consume_span:
carrier = {}
TraceContextTextMapPropagator().inject(carrier)
return carrier
def update_info(__trace_propagator):
print(f"{__trace_propagator=}")
def end(consumer_runner_carrier):
print(f"{consumer_runner_carrier=}")
consumer_runner_span = trace.NonRecordingSpan(
TraceContextTextMapPropagator().extract(consumer_runner_carrier)
)
consumer_runner_context = consumer_runner_span.get_span_context()
with tracer.start_as_current_span(
name="trigger_update.info",
context=api_gateway(), # to show the API Gateway trigger as causal parent.
links=[
trace.Link(consumer_runner_context)
], # to show consumer running function as co-parent/linked.
) as msg_span:
update_info(
__trace_propagator=msg_span.get_span_context()
) # Trace further passed to track business logic function.
if __name__ == "__main__":
end(message_broker_consumer_runner())
Error
consumer_runner_carrier={'traceparent': '00-624066a40219dec50ed88de4feb4ee7a-1be9a825202fa729-01'}
__trace_propagator=SpanContext(trace_id=0x7ae344302d28c0000ea8d4b5457aff39, span_id=0x851a6758cb28f8b7, trace_flags=0x01, trace_state=[], is_remote=False)
{
"name": "consumer_runner.None",
"context": {
"trace_id": "0x624066a40219dec50ed88de4feb4ee7a",
"span_id": "0x1be9a825202fa729",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": null,
"start_time": "2023-10-10T18:39:20.205144Z",
"end_time": "2023-10-10T18:39:20.205172Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.20.0",
"service.name": "data_fetcher",
"service.version": "1.0",
"deployment.environment": "development"
},
"schema_url": ""
}
}
{
"name": "api_gateway.None",
"context": {
"trace_id": "0x565151dfcc4cffbbd44e953d2c8ba27a",
"span_id": "0x3e3e8a6265a6e481",
"trace_state": "[]"
},
"kind": "SpanKind.INTERNAL",
"parent_id": null,
"start_time": "2023-10-10T18:39:20.205270Z",
"end_time": "2023-10-10T18:39:20.205287Z",
"status": {
"status_code": "UNSET"
},
"attributes": {},
"events": [],
"links": [],
"resource": {
"attributes": {
"telemetry.sdk.language": "python",
"telemetry.sdk.name": "opentelemetry",
"telemetry.sdk.version": "1.20.0",
"service.name": "data_fetcher",
"service.version": "1.0",
"deployment.environment": "development"
},
"schema_url": ""
}
}
Exception while exporting Span batch.
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/opentelemetry/sdk/trace/export/__init__.py", line 368, in _export_batch
self.span_exporter.export(self.spans_list[:idx]) # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/opentelemetry/sdk/trace/export/__init__.py", line 522, in export
self.out.write(self.formatter(span))
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/opentelemetry/sdk/trace/export/__init__.py", line 513, in <lambda>
] = lambda span: span.to_json()
^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/opentelemetry/sdk/trace/__init__.py", line 492, in to_json
f_span["links"] = self._format_links(self._links)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/opentelemetry/sdk/trace/__init__.py", line 535, in _format_links
] = Span._format_context( # pylint: disable=protected-access
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/opentelemetry/sdk/trace/__init__.py", line 500, in _format_context
x_ctx["trace_id"] = f"0x{trace_api.format_trace_id(context.trace_id)}"
^^^^^^^^^^^^^^^^
AttributeError: 'Context' object has no attribute 'trace_id'
OpenTelemetry versions
Python 3.11.4
opentelemetry-api 1.20.0
opentelemetry-distro 0.41b0
opentelemetry-exporter-otlp 1.20.0
opentelemetry-exporter-otlp-proto-common 1.20.0
opentelemetry-exporter-otlp-proto-grpc 1.20.0
opentelemetry-exporter-otlp-proto-http 1.20.0
opentelemetry-instrumentation 0.41b0
opentelemetry-instrumentation-aws-lambda 0.41b0
opentelemetry-instrumentation-dbapi 0.41b0
opentelemetry-instrumentation-grpc 0.41b0
opentelemetry-instrumentation-httpx 0.41b0
opentelemetry-instrumentation-logging 0.41b0
opentelemetry-instrumentation-pymongo 0.41b0
opentelemetry-instrumentation-requests 0.41b0
opentelemetry-instrumentation-sqlite3 0.41b0
opentelemetry-instrumentation-tortoiseorm 0.41b0
opentelemetry-instrumentation-urllib 0.41b0
opentelemetry-instrumentation-urllib3 0.41b0
opentelemetry-instrumentation-wsgi 0.41b0
opentelemetry-propagator-aws-xray 1.0.1
opentelemetry-proto 1.20.0
opentelemetry-sdk 1.20.0
opentelemetry-semantic-conventions 0.41b0
opentelemetry-util-http 0.41b0
When I use Pycharm's debugger to pause execution, I can see an attribute object of consumer_runner_context
which has a trace_id deep down in its attributes tree, and I can access it thusly:
consumer_runner_context.get(list(consumer_runner_context.keys())[0]).get_span_context()
and this works:
with tracer.start_as_current_span(
name="trigger_update.info",
context=api_gateway(), # to show the API Gateway trigger as causal parent.
links=[trace.Link(consumer_runner_context.get(list(consumer_runner_context.keys())[0]).get_span_context())]
)
But this hardly seems the right way to do this.
What is the right way to do this?
As for the answer - yes, it is the way how to do it (at the moment) as far as I know.
After experimenting with Links for some time, they seem to me to be (still) a bit neglected child in the Otel family. Please, correct me if I'm wrong.
A two examples that come to my mind now:
A visualization of links (e.g. in Jaeger, SigNoz...) is very basic. One just sees plain "references" (leading somewhere), which is better then nothing, but makes the debugging diffucult.
Often spans to link are discovered only while in a current span, not known beforehand. Creating new children spans just to capture links, is often unwanted. Here, I'd love to see
span.add_link()
method.