OpenTelemetry baggage lets a service attach a small key-value item to the active context and send it with an outgoing request. Propagating a safe value such as a tenant or routing hint makes the same context available to downstream code without adding a custom application parameter to every function call.
A direct Python implementation uses the W3C baggage propagator to write the baggage HTTP header on the sending side and read it on the receiving side. Framework instrumentation often handles propagation automatically, but the manual path is useful for custom clients, workers, and transport wrappers where the request headers are assembled in application code.
Baggage travels as request metadata and may cross a trust boundary when a service calls another service. Use bounded values that are safe to disclose, avoid credentials and personal data, and copy baggage into span attributes or logs only when receiving code explicitly needs that value.
Steps to propagate OpenTelemetry baggage in a Python request:
- Create a Python virtual environment for the demo.
$ python3 -m venv baggage-demo
- Activate the virtual environment.
$ . baggage-demo/bin/activate
- Install the OpenTelemetry API package.
$ python -m pip install opentelemetry-api ##### snipped ##### Successfully installed opentelemetry-api-1.38.0
- Create the downstream service that extracts baggage from the incoming request.
- downstream.py
from http.server import BaseHTTPRequestHandler, HTTPServer from opentelemetry import baggage from opentelemetry.baggage.propagation import W3CBaggagePropagator class Handler(BaseHTTPRequestHandler): def do_GET(self): carrier = {"baggage": self.headers.get("baggage", "")} context = W3CBaggagePropagator().extract(carrier) tenant = baggage.get_baggage("tenant", context=context) or "missing" body = f"downstream baggage tenant={tenant}\n".encode() self.send_response(200) self.send_header("Content-Type", "text/plain") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def log_message(self, format, *args): return HTTPServer(("127.0.0.1", 8080), Handler).serve_forever()
The explicit carrier keeps the key as baggage, which is what the default text-map getter reads from a plain Python dictionary.
- Create the sending script that injects baggage before the HTTP request.
- send_request.py
from urllib.request import Request, urlopen from opentelemetry import baggage from opentelemetry.baggage.propagation import W3CBaggagePropagator context = baggage.set_baggage("tenant", "acme-retail") headers = {} W3CBaggagePropagator().inject(headers, context=context) print(f"outgoing baggage header: {headers['baggage']}") request = Request("http://127.0.0.1:8080/orders", headers=headers) with urlopen(request, timeout=5) as response: print(response.read().decode().strip())
Do not put access tokens, email addresses, or other personal data in baggage. Automatic instrumentation may forward baggage on service requests where headers are visible to another team or vendor.
- Open a second terminal in the same directory and activate the virtual environment there.
$ . baggage-demo/bin/activate
- Start the downstream service in the second terminal.
$ python downstream.py
The service listens on 127.0.0.1:8080 and keeps the terminal open until it is stopped.
- Send the request from the first terminal.
$ python send_request.py outgoing baggage header: tenant=acme-retail downstream baggage tenant=acme-retail
- Stop the downstream service in the second terminal.
Ctrl+C
- Remove the demo files after the propagation test.
$ rm -r baggage-demo downstream.py send_request.py
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.