Opentelemetry: Unable to Link spans with trace in a straightforward/clean way

624 views Asked by At

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?

1

There are 1 answers

1
alephzarro On BEST ANSWER

As for the answer - yes, it is the way how to do it (at the moment) as far as I know.

ctx = propagate.extract(carrier)
sctx = next(iter(ctx.values())).get_span_context()
link = trace.Link(sctx)

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.