Skip to main content
Initialize HoneyHiveTracer once per process in the right place for your runtime so every LLM call and custom span lands in the correct session. Placement differs for scripts, evaluate(), Lambda, and web servers because session state is handled differently in each pattern. If you are new to tracing, complete the tracing quickstart first; for cross-service setups, pair this with distributed tracing.

Which initialization pattern should you use?

Initialize the tracer before any instrumentor. Call HoneyHiveTracer.init(...) first, then pass tracer.provider into instrumentor.instrument(...).What changes by runtime is not whether you initialize the tracer. What changes is where you place that initialization and how you create session context.For request-scoped and invocation-scoped runtimes, create_session() and acreate_session() put the active session ID in OpenTelemetry baggage. HoneyHive resolves that baggage session before falling back to the tracer instance’s startup session.
evaluate() is the main exception to the table above. When you’re running experiments with evaluate(), do not initialize your own tracer. The SDK creates and manages a separate tracer for each datapoint.

How do you initialize the tracer in scripts and notebooks?

Initialize once at module level. All traced operations share the same session.
This is the simplest pattern. Use it for scripts, notebooks, and quick debugging.

Should you initialize a tracer with evaluate()?

When running experiments with evaluate(), don’t create your own tracer. The SDK creates a new tracer per datapoint automatically, giving each datapoint its own isolated session.
Don’t initialize a global tracer alongside evaluate().A global tracer can conflict with the per-datapoint tracers that evaluate() creates. If you see traces landing in the wrong session, remove the global HoneyHiveTracer.init() call.

How do you initialize the tracer in serverless?

In serverless environments like Lambda and Cloud Functions, initialize the tracer outside the handler and reuse it across warm starts. Then call create_session() inside the handler so each invocation gets its own active session. The invocation-scoped baggage session takes precedence over any default session on the shared tracer.
Batched export and serverless: By default, the SDK batches spans before exporting. In serverless environments where the runtime freezes between invocations, we recommend setting disable_batch=True so spans are exported immediately rather than queued. You can also set this via the HH_DISABLE_BATCH=true environment variable. Alternatively, you can keep the default batched mode and call tracer.force_flush() before returning to drain the queue, but disable_batch=True is simpler since it removes the dependency on remembering to flush.
LRU cache alternative for lazy initialization:

Linking Lambda Invocations

To link multiple invocations into the same session (e.g., multi-turn conversations), pass a session_id through your event payload and reuse get_tracer() from the lazy-init pattern above:

Skipping Init-Time Session Creation

Set skip_backend_session_creation=True when you do not want HoneyHiveTracer.init() to create a backend session synchronously. This is useful when another service already created the session, or when you create request-scoped sessions later with create_session(skip_api_call=True).
If you pass a valid session_id, the tracer attaches spans to that existing session without making a creation call during initialization:
If you omit session_id, the tracer still skips the init-time creation call and does not generate a session ID during initialization. Set the request-scoped session later, for example with create_session(session_id=..., skip_api_call=True). Spans emitted before a request-scoped session is set do not carry that session ID. Default behavior is unchanged when skip_backend_session_creation is not set.
This is different from create_session(skip_api_call=True), which skips the API call for a per-request session. skip_backend_session_creation skips the API call during tracer initialization itself.

How do you initialize the tracer in web servers?

For long-running servers (FastAPI, Flask, Django), initialize one tracer at startup and create a new session per request using create_session() or its async variant acreate_session().
How session isolation works: create_session() and acreate_session() store the active session ID in OpenTelemetry baggage, which uses Python context propagation and ContextVar for async/task-local state. HoneyHive reads the baggage session first and only falls back to the tracer instance when no request-scoped session is present, so one shared tracer can safely serve concurrent requests.

FastAPI

Flask

For synchronous frameworks, use create_session() instead of acreate_session():
Don’t use session_start() for web servers. session_start() stores the session ID on the tracer instance itself, which causes race conditions when multiple requests run concurrently. Use create_session() or acreate_session() instead. They store the session ID in request-scoped baggage.

Multi-Turn Conversations

For multi-turn conversations, the first request creates a session and returns the ID to the client. Subsequent requests link to that session using skip_api_call=True, which sets the session context without making an API call.

Scoped Sessions

For single-use scripts, dedicated worker runs, or batch tasks where the rest of the current execution context belongs to the same logical unit of work, with_session can be convenient. For web requests, prefer create_session() or acreate_session() in middleware:

Thread and Process Safety

The global tracer + create_session() pattern is safe for:
  • Multi-threaded servers (FastAPI, Flask with threads) — baggage uses ContextVar, which is inherently thread-local
  • Multi-process deployments (Gunicorn workers, uWSGI) — each process gets its own tracer instance; processes don’t share state

Which span export mode should you use?

By default, the SDK exports spans asynchronously in batches using a background thread. This means span.end() returns immediately and spans are sent in the background, so export latency never blocks your application.

Batched Async Export (Default)

With batched export, spans accumulate in an internal queue and are sent in bulk every ~5 seconds (or when the queue fills up). This is the best mode for web servers and long-running services because it minimizes the performance impact of tracing on your application.
Call tracer.flush() or tracer.force_flush() at the end of your process or notebook cell to drain any remaining spans from the queue before the process exits.

Immediate Sync Export

Use disable_batch=True when the runtime may freeze or terminate immediately after the handler returns, such as AWS Lambda, Google Cloud Functions, or one-off CLI scripts. In these environments, a background thread may not get a chance to flush before the process is frozen.
With disable_batch=True, each span exports synchronously when it ends, so force_flush() is effectively a no-op for spans that have already completed. If your handler spawns child threads or async tasks, make sure all work finishes (and spans end) before the handler returns - otherwise those spans may be lost when the runtime freezes.

Flushing

Both modes support explicit flushing:
Use flush() / force_flush():
  • At the end of a Lambda handler, before returning the response
  • At the end of a Jupyter notebook cell
  • Before process exit in scripts
  • In atexit handlers or signal handlers for graceful shutdown

What are tracer initialization best practices?

Passing tracer=tracer makes the binding explicit and avoids relying on implicit tracer discovery.
Even with a global tracer, create sessions to isolate traces by request, user, or job.
Use the tracer placement that matches your runtime:
  • Scripts and notebooks: initialize once in the module that starts the run
  • Lambda and other serverless runtimes: lazy-init outside the handler, then create a session per invocation
  • Web servers: initialize once at startup, then create a session per request
  • evaluate(): let the SDK create and manage tracers for you
test_mode=True (or the HH_TEST_MODE=true environment variable) disables OTLP export and generates a local session ID instead of creating one in HoneyHive. Use it for local development and tests when you want tracer setup without exporting spans over OTLP.

Where should you go next?

Production Deployment

Error handling, environment config, and deployment checklist

Multi-Instance Tracing

Run multiple tracer instances for multi-tenant or A/B testing

Distributed Tracing

Propagate trace context across service boundaries

Experiments

Run evaluations with automatic per-datapoint tracing

Span Filtering

Drop noisy framework spans using prefix-based rules

Environment Variables

Full reference for SDK configuration via environment variables