Integrate Botanu with your existing OpenTelemetry configuration.
If you already have OpenTelemetry configured (via Datadog, Splunk, New Relic, or custom setup), Botanu integrates seamlessly. You only need to add the RunContextEnricher span processor.
Add just the span processor to your existing provider:
from opentelemetry import trace
from botanu.processors.enricher import RunContextEnricher
# Your existing TracerProvider
provider = trace.get_tracer_provider()
# Add Botanu's enricher
provider.add_span_processor(RunContextEnricher())That's it. All spans will now receive run_id from baggage.
Botanu works alongside any existing instrumentation:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from botanu.processors.enricher import RunContextEnricher
# Your existing setup
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
# Your existing instrumentation
RequestsInstrumentor().instrument()
# Add Botanu enricher (order doesn't matter)
provider.add_span_processor(RunContextEnricher())from ddtrace import tracer
from ddtrace.opentelemetry import TracerProvider
from opentelemetry import trace
from botanu.processors.enricher import RunContextEnricher
# Datadog's TracerProvider
provider = TracerProvider()
trace.set_tracer_provider(provider)
# Add Botanu enricher
provider.add_span_processor(RunContextEnricher())from splunk_otel.tracing import start_tracing
from opentelemetry import trace
from botanu.processors.enricher import RunContextEnricher
# Start Splunk tracing
start_tracing()
# Add Botanu enricher
provider = trace.get_tracer_provider()
provider.add_span_processor(RunContextEnricher())from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from botanu.processors.enricher import RunContextEnricher
# New Relic OTLP endpoint
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(
endpoint="https://otlp.nr-data.net/v1/traces",
headers={"api-key": "YOUR_LICENSE_KEY"},
)
)
)
trace.set_tracer_provider(provider)
# Add Botanu enricher
provider.add_span_processor(RunContextEnricher())from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from botanu.processors.enricher import RunContextEnricher
# Jaeger setup
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(
JaegerExporter(
agent_host_name="localhost",
agent_port=6831,
)
)
)
trace.set_tracer_provider(provider)
# Add Botanu enricher
provider.add_span_processor(RunContextEnricher())Send to both your APM and a cost-attribution backend:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from botanu.processors.enricher import RunContextEnricher
provider = TracerProvider()
# Your APM (e.g., Datadog)
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(endpoint="https://your-apm.example.com/v1/traces")
)
)
# Botanu collector for cost attribution
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(endpoint="http://botanu-collector:4318/v1/traces")
)
)
# Botanu enricher (adds run_id to all spans)
provider.add_span_processor(RunContextEnricher())
trace.set_tracer_provider(provider)The enricher reads baggage and writes to span attributes:
class RunContextEnricher(SpanProcessor):
def on_start(self, span, parent_context):
# Read run_id from baggage
run_id = baggage.get_baggage("botanu.run_id", parent_context)
if run_id:
span.set_attribute("botanu.run_id", run_id)
# Read workflow from baggage
workflow = baggage.get_baggage("botanu.workflow", parent_context)
if workflow:
span.set_attribute("botanu.workflow", workflow)This means:
- Every span gets
run_idif it exists in baggage - Auto-instrumented spans are enriched automatically
- No code changes needed in your existing instrumentation
With the enricher in place, use Botanu decorators:
from botanu import botanu_workflow, emit_outcome
@botanu_workflow("do_work", event_id=event_id, customer_id=customer_id)
async def do_work(event_id: str, customer_id: str):
# All spans created here (by any instrumentation) get run_id
data = do_something()
result = await process(data)
emit_outcome("success")If you don't want to use enable(), manually set up propagation:
from opentelemetry import propagate
from opentelemetry.propagators.composite import CompositePropagator
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.baggage.propagation import W3CBaggagePropagator
# Ensure baggage propagation is enabled
propagate.set_global_textmap(
CompositePropagator([
TraceContextTextMapPropagator(),
W3CBaggagePropagator(),
])
)Check that run_id appears on spans:
from opentelemetry import trace, baggage, context
# Set baggage (normally done by @botanu_workflow)
ctx = baggage.set_baggage("botanu.run_id", "test-123")
token = context.attach(ctx)
try:
tracer = trace.get_tracer("test")
with tracer.start_as_current_span("test-span") as span:
# Check attribute was set
print(span.attributes.get("botanu.run_id")) # Should print "test-123"
finally:
context.detach(token)Span processors are called in order. The enricher should be added after your span exporters:
# 1. Exporters (send spans to backends)
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
# 2. Enrichers (modify spans before export)
provider.add_span_processor(RunContextEnricher())However, RunContextEnricher uses on_start(), so it runs before export regardless.
-
Check enricher is added:
provider = trace.get_tracer_provider() # Verify RunContextEnricher is in the list
-
Check baggage is set:
from opentelemetry import baggage print(baggage.get_baggage("botanu.run_id"))
-
Ensure
@botanu_workflowis used at entry points
Check propagators are configured:
from opentelemetry import propagate
print(propagate.get_global_textmap())Should include W3CBaggagePropagator.
- Auto-Instrumentation - Library instrumentation
- Collector Configuration - Collector setup
- Architecture - SDK design