Spaces:
Running
Running
File size: 5,947 Bytes
a6a5d8e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | # SPDX-License-Identifier: Apache-2.0
# © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173
"""vsp_otel.middleware — real W3C-traceparent + OTLP/gRPC middleware for FastAPI/Starlette organs.
Field leaders harnessed (cited):
- W3C Trace Context §3.2 (traceparent) https://www.w3.org/TR/trace-context/
- OpenTelemetry OTLP exporter spec https://opentelemetry.io/docs/specs/otel/protocol/exporter/
`install(app)` adds an ASGI middleware that, for every request:
1. parses any incoming `traceparent` (W3C §3.2), validating shape/non-zero ids;
2. mints a child span id, keeping the same trace-id (cross-pod continuity);
3. if the OpenTelemetry SDK + OTLP/gRPC exporter are installed, emits a real
server span to OTEL_EXPORTER_OTLP_ENDPOINT (default localhost:4317);
4. ALWAYS echoes a valid outbound `traceparent` response header so the next
organ continues the SAME trace-id (this is what cross_pod_trace_test asserts).
Honest disclosure: if the OTel SDK is absent the span is NOT exported to a
collector — but the traceparent propagation (the cross-pod contract) still works
purely in-process. We never claim "exported" when the exporter is unavailable;
the middleware sets request.state.vsp_otel_exporter to the real status string.
Doctrine v11 LOCKED 749/14/163 @ c7c0ba17 · Λ = Conjecture 1.
"""
from __future__ import annotations
import os
import secrets
import time
_VERSION = "0.1.0"
_ENDPOINT = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
def _hex(n: int) -> str:
return secrets.token_hex(n)
def parse_traceparent(tp):
"""Parse/validate a W3C traceparent. Returns dict or None (W3C §3.2)."""
if not tp or not isinstance(tp, str):
return None
parts = tp.strip().split("-")
if len(parts) != 4:
return None
ver, tid, pid, flags = parts
if len(ver) != 2 or len(tid) != 32 or len(pid) != 16 or len(flags) != 2:
return None
if tid == "0" * 32 or pid == "0" * 16:
return None
try:
int(tid, 16); int(pid, 16); int(flags, 16)
except ValueError:
return None
return {"trace_id": tid, "parent_id": pid, "flags": flags,
"sampled": bool(int(flags, 16) & 0x01)}
def make_traceparent(trace_id=None, parent_id=None, sampled=True):
tid = trace_id or _hex(16)
pid = parent_id or _hex(8)
return f"00-{tid}-{pid}-{'01' if sampled else '00'}"
class _Tracer:
"""Lazily initialises a real OTel TracerProvider + OTLP/gRPC exporter."""
def __init__(self, service_name: str, endpoint: str = _ENDPOINT):
self.service_name = service_name
self.endpoint = endpoint
self.exporter = "in-process-only"
self._tracer = None
try:
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
resource = Resource.create({
"service.name": service_name,
"szl.doctrine": "v11-749-14-163",
"szl.kernel_commit": "c7c0ba17",
"szl.lambda": "Conjecture-1",
})
provider = TracerProvider(resource=resource)
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint, insecure=True)))
trace.set_tracer_provider(provider)
self._tracer = trace.get_tracer(f"vsp-otel.{service_name}")
self.exporter = f"otlp-grpc:{endpoint}"
except Exception as e: # SDK absent — propagation still works
self.exporter = f"in-process-only ({type(e).__name__})"
def server_span(self, name: str, attrs: dict):
if self._tracer is None:
return
try:
with self._tracer.start_as_current_span(name) as s:
for k, v in attrs.items():
s.set_attribute(k, str(v))
except Exception:
pass
def install(app, service_name: str | None = None, endpoint: str = _ENDPOINT):
"""Install the vsp-otel middleware on a FastAPI/Starlette `app`.
Idempotent: a second install() on the same app is a no-op.
"""
if getattr(app, "_vsp_otel_installed", False):
return app
svc = service_name or os.environ.get("OTEL_SERVICE_NAME") \
or getattr(app, "title", "organ").split()[0].lower()
tracer = _Tracer(svc, endpoint)
@app.middleware("http")
async def _vsp_otel_mw(request, call_next):
incoming = request.headers.get("traceparent")
parsed = parse_traceparent(incoming)
trace_id = parsed["trace_id"] if parsed else _hex(16)
span_id = _hex(8)
sampled = parsed["sampled"] if parsed else True
request.state.trace_id = trace_id
request.state.traceparent = make_traceparent(trace_id, span_id, sampled)
request.state.vsp_otel_exporter = tracer.exporter
t0 = time.time_ns()
response = await call_next(request)
tracer.server_span(f"{svc} {request.method} {request.url.path}", {
"http.method": request.method,
"http.route": request.url.path,
"http.status_code": getattr(response, "status_code", 0),
"szl.service": svc,
"szl.parent_trace_id": parsed["trace_id"] if parsed else "(root)",
"duration_ns": time.time_ns() - t0,
})
# Echo a continuing traceparent so the next organ keeps the trace-id.
response.headers["traceparent"] = request.state.traceparent
response.headers["x-vsp-otel"] = f"{_VERSION};{tracer.exporter}"
return response
app._vsp_otel_installed = True
app._vsp_otel_service = svc
return app
|