Advanced SDK Topics

SDK

Manual spans, sampling, rate limiting, and advanced configuration.

Beyond the basics, Traccia provides advanced features for fine-grained control over tracing, performance optimization, and custom instrumentation scenarios.

Manual Span Creation

While the @observe() decorator covers most use cases, sometimes you need manual control over span creation and lifecycle.

Using Context Managers (Python) or Active Spans (Node)

manual.py
python
from traccia import span
# Simple span
with span("database_query"):
result = db.execute("SELECT * FROM users")
# With attributes
with span("api_call", {"endpoint": "/users", "method": "GET"}) as s:
response = requests.get("https://api.example.com/users")
s.set_attribute("status_code", response.status_code)
s.set_attribute("response_time_ms", response.elapsed.total_seconds() * 1000)

Using Tracer Directly

tracer.py
python
from traccia import get_tracer
tracer = get_tracer("my-service")
# Context manager
with tracer.start_as_current_span("operation") as span:
span.set_attribute("user_id", "123")
span.add_event("Processing started")
try:
result = process_data()
span.set_status(SpanStatus.OK)
except Exception as e:
span.record_exception(e)
span.set_status(SpanStatus.ERROR, str(e))
raise
# Manual start/end (use with caution)
span = tracer.start_span("manual-span")
try:
do_work()
finally:
span.end() # Always call end()!

Long-Lived Spans (Streaming)

For streaming LLM responses or multi-step async callbacks, use span_scope() / spanScope() to keep a parent span open until you explicitly call end(). Use run() / runAsync() so child spans attach correctly.

streaming.py
python
from traccia import span_scope
async def chat_turn(user_msg: str):
turn = span_scope("chat.turn", attributes={"span.type": "llm"})
try:
async def _stream():
async for chunk in llm.stream(user_msg):
yield chunk
async for chunk in turn.run_async(lambda: _stream()):
yield chunk
except Exception as e:
turn.end(error=e)
raise
else:
turn.end()

Span Methods

set_attribute(key, value) — Add or update a span attribute
add_event(name, attributes=) — Add a timestamped event
record_exception(exception) — Record an exception event
set_status(status, description="") — Set span status (OK, ERROR, UNSET)
end() — End the span (automatic with context managers)

Manual span management

When creating spans manually (not with context managers), always ensure span.end() is called, even if an exception occurs. Use try/finally blocks or context managers.

Sampling

Sampling reduces the volume of traces sent to your backend by randomly selecting a percentage of traces to export. This is useful for high-throughput applications where capturing every trace would be too expensive.

Head-Based Sampling

Traccia uses head-based sampling, which means the sampling decision is made when the trace starts and applies to all spans in that trace.

sampling.py
python
from traccia import init
# Sample 10% of traces
init(sample_rate=0.1)
# Sample 50% of traces
init(sample_rate=0.5)
# Sample all traces (default)
init(sample_rate=1.0)

Via Configuration

traccia.toml
toml
[tracing]
sample_rate = 0.1 # Sample 10% of traces

Cost vs. observability

A sample rate of 0.1 (10%) is often a good balance for production systems, capturing enough data for insights while reducing costs. Adjust based on your traffic volume and budget.

Rate Limiting

Rate limiting controls the maximum number of spans exported per second, preventing your application from overwhelming the backend or consuming too many resources during traffic spikes.

Configuration

rate_limiting.py
python
from traccia import init
# Limit to 100 spans per second
init(max_spans_per_second=100)
# With blocking configuration
init(
max_spans_per_second=100,
max_block_ms=100, # Block up to 100ms before dropping
max_queue_size=5000 # Max queued spans
)

Via Configuration File

traccia.toml
toml
[rate_limiting]
max_spans_per_second = 100.0
max_block_ms = 100
max_queue_size = 5000

How It Works

1. Token bucket algorithm: Traccia uses a token bucket to control span rate. Tokens are added at the configured rate (e.g., 100/second).
2. Blocking behavior: When rate limit is reached, spans block for up to max_block_ms waiting for tokens.
3. Dropping policy: If still no tokens after blocking, spans are dropped (oldest first by default) to prevent memory buildup.
4. Queue management: Up to max_queue_size spans can be queued. When full, oldest spans are dropped.

Dropped spans

When rate limiting causes spans to be dropped, you'll lose visibility into those operations. Monitor drop rates and adjust limits if you're losing too much data.

Runtime Configuration

Some configuration values can be changed at runtime without restarting your application. This is useful for per-request metadata like session IDs, user IDs, and tenant IDs.

runtime_config.py
python
from traccia import runtime_config
# Set runtime metadata (applies to all subsequent spans)
runtime_config.set_session_id("session-abc123")
runtime_config.set_user_id("user-xyz789")
runtime_config.set_tenant_id("tenant-acme")
runtime_config.set_project_id("project-ml")
runtime_config.set_agent_id("support-bot")
# Get current values
session_id = runtime_config.get_session_id()
user_id = runtime_config.get_user_id()
# Enable debug logging at runtime
runtime_config.set_debug(True)
# Set attribute truncation limit
runtime_config.set_attr_truncation_limit(2000)

Multi-Tenant Pattern

multi_tenant.py
python
from traccia import init, runtime_config, observe
# Initialize once at startup
init(api_key="tr_live_xxxxxxxxxxxx")
# Per-request handler
@observe()
def handle_request(request):
# Set tenant context from request
runtime_config.set_tenant_id(request.headers.get("X-Tenant-ID"))
runtime_config.set_user_id(request.user.id)
runtime_config.set_session_id(request.session.id)
# All spans in this request will have these IDs
return process_request(request)

Span Processors

Traccia uses span processors to enrich, transform, and export spans. Understanding the processor pipeline helps you understand what happens to your traces.

Built-in Processors

TokenCountingProcessor

Automatically counts tokens for LLM calls using tiktoken. Adds llm.usage.prompt_tokens, llm.usage.completion_tokens, llm.usage.total_tokens, and llm.usage.source.

Control: enable_token_counting=True/False

CostAnnotatingProcessor

Calculates estimated costs based on token counts and model pricing. Adds llm.cost.usd and llm.pricing.* metadata. Skips non-LLM spans when span.type is set to a value other than llm.

Control: enable_costs=True/False

AgentEnrichmentProcessor

Enriches spans with agent metadata from runtime config and agent_config.json. Adds session/user/tenant/project/agent IDs.

Always enabled (core functionality)

GuardrailDetectorProcessor

Detects missing or triggered guardrails and writes findings onto span attributes.

Control: guardrail_heuristics=True/False

GovernanceEnrichmentProcessor

Adds baseline governance attributes and optional EU AI Act overlay fields to every span.

Control: compliance={frameworks: ["eu_ai_act"]}

RedactionSpanProcessor

Masks email, phone, and SSN-like patterns on sensitive span attributes before export.

Control: redact_pii=True or TRACCIA_REDACT_PII=1

RateLimitingSpanProcessor

Enforces rate limits on span export. Blocks and drops spans when limit exceeded.

Control: max_spans_per_second

BatchSpanProcessor

Batches spans before export for efficiency. Flushes on schedule or when batch size reached.

Control: max_export_batch_size, schedule_delay_millis

Processor Pipeline

Processors run in registration order when a span ends. Optional processors are skipped when disabled.

Span Created
Via @observe, auto-instrumentation, or manual tracking
1. TokenCountingProcessor
Optional — if enabled
2. CostAnnotatingProcessor
Optional — if enabled
3. LoggingSpanProcessor
Optional — if enabled
4. AgentEnrichmentProcessor
Core — always runs
5. GuardrailDetectorProcessor
Core — always runs
6. GovernanceEnrichmentProcessor
Core — always runs
7. RedactionSpanProcessor
Optional — if redact_pii enabled
8. RateLimitingSpanProcessor
Optional — if max_spans_per_second set (Python OTLP path)
9. BatchSpanProcessor
Core — batches for export
Exporter
OTLP, console, file, or a combination

Context Propagation

For distributed systems with multiple services or agents, you need to propagate trace context across HTTP boundaries to maintain parent-child relationships.

Automatic Propagation

When auto-instrumentation is enabled for requests and FastAPI, context propagation happens automatically.

Manual Propagation

Both SDKs expose W3C Trace Context helpers for manual header injection and extraction. Auto-instrumentation for HTTP clients and servers handles this automatically when enabled.

propagation.py
python
from traccia.instrumentation import inject_http_headers, extract_parent_context
import requests
# Client: Inject context into outgoing request
headers = {}
inject_http_headers(headers)
response = requests.post(
"http://other-service/api",
headers=headers,
json=data
)
# Server: Extract context from incoming request
from fastapi import Request
@app.post("/api")
def handle_request(request: Request):
parent_context = extract_parent_context(dict(request.headers))
# Spans created here will be children of the incoming trace
...

W3C Trace Context

Traccia uses the W3C Trace Context standard (traceparent and tracestate headers) for maximum compatibility.

Performance Considerations

Minimize Overhead

  • • Use sampling for high-throughput applications
  • • Enable rate limiting to cap resource usage
  • • Skip large arguments/results with decorator options
  • • Increase batch size and delay for fewer exports

Typical Overhead

  • • Per-span overhead: ~50-200 microseconds
  • • Memory per span: ~1-2 KB
  • • Network: Batched exports reduce connection overhead
  • • CPU: Token counting adds ~1-5ms for LLM calls

Next Steps

© 2026 Traccia.