Environment and deployment

Environment variables, separating environments, CI, serverless, worker pools, and self-hosting the backend.

Environment variables

The SDK reads no configuration file. These are conventions your code passes to init().

VariablePurpose
CAPSERA_API_KEYYour cap- key.
CAPSERA_ENDPOINTBackend URL, for self-hosting.
capsera.init(
    api_key=os.environ["CAPSERA_API_KEY"],
    endpoint=os.environ.get("CAPSERA_ENDPOINT", "https://api.capsera.ai"),
)

The guard pattern, which Capsera's auto-instrumentation writes into repositories:

if os.environ.get("CAPSERA_API_KEY"):
    capsera.init(api_key=os.environ["CAPSERA_API_KEY"])

Without a key the SDK stays inactive, so the same code runs unchanged on a machine with no Capsera credentials. The cost is that a missing key is silent, which is why Verify it works exists.

Separating environments

Use a separate API key per environment and pass a matching env label:

capsera.init(api_key=os.environ["CAPSERA_API_KEY"], env="production")

The key determines which workspace receives events. env labels them within it. Sharing one key across development and production puts load-test traffic in the same reports as customer spend.

Long-running services

Call init() at startup. The background worker ships every 500 ms and the daemon thread does not delay shutdown.

capsera.init(api_key=os.environ["CAPSERA_API_KEY"], env=settings.env)
app = FastAPI()

Add set_session() in middleware for per-request grouping. See Tags and sessions.

Short-lived processes

Scripts, cron jobs, CI steps, and one-shot workers must drain before exit, because the daemon thread ends with the process:

try:
    main()
finally:
    capsera.shutdown()

This is the second most common cause of an empty dashboard, after a missing key.

Serverless

Initialise outside the handler so it runs once per container rather than once per invocation:

import os

import capsera

capsera.init(api_key=os.environ["CAPSERA_API_KEY"], env="production")


def handler(event, context):
    result = do_work(event)
    capsera.flush()
    return result

Flush before returning. A serverless runtime can freeze the execution environment immediately after the handler returns, and a frozen thread ships nothing. Use flush() rather than shutdown(), so a warm container still has a worker for the next invocation.

Celery and worker pools

Initialise once per worker process, not per task. With prefork, use the worker-process init hook:

from celery.signals import worker_process_init

@worker_process_init.connect
def start_capsera(**_):
    capsera.init(api_key=os.environ["CAPSERA_API_KEY"], env="production")

Initialising in the parent before forking gives each child a copy of a thread that does not exist in it. The queue fills and nothing ships.

CI

Set the key only if you want test-suite spend recorded. Often you do, because a test suite making real provider calls is a real cost that tends to grow unnoticed.

env:
  CAPSERA_API_KEY: ${{ secrets.CAPSERA_API_KEY }}

Label it and drain at the end:

capsera.init(api_key=os.environ["CAPSERA_API_KEY"], env="ci", agent_name="test-suite")

To report nothing from CI, leave the variable unset. With the guard pattern the SDK is then inactive with no code change.

Self-hosting

Point endpoint at your own deployment:

capsera.init(
    api_key=os.environ["CAPSERA_API_KEY"],
    endpoint="https://capsera.internal.example.com",
)

Everything described in Privacy then stays inside your infrastructure.