SDKs / Python

Python

A logging.Handler you add alongside the existing ones, plus WSGI middleware for Django and Flask.

Requires Python 3.9+Package insight-recorderSource on GitHub →

Before you start

Where your InsightRecorder runs
The base URL every call goes to — http://localhost:8080 when you are running it locally, your own hostname otherwise.
An API key
Settings → API keys, scoped to logs:write and bug:write. The token (crk_…) is shown exactly once — it is hashed at rest — and is revocable. Do not use a login JWT: it expires within the hour and is not meant for machines.
  1. 01

    Install

    HTTP is urllib.request, so it cannot conflict with the requests or httpx versions you pin.

    pip install insight-recorder
  2. 02

    Create the client and shipper

    Once, where the app is composed — app.py, wsgi.py, a Django AppConfig.ready(), a FastAPI lifespan.

    from insight_recorder import Client
    
    client = Client(os.environ["INSIGHT_BASE_URL"], os.environ["INSIGHT_API_KEY"])
    shipper = client.shipper()
  3. 03

    Bridge logging

    Alongside the existing handlers, never replacing them. Anything in extra= becomes a searchable attribute.

    from insight_recorder import InsightHandler
    
    logging.getLogger().addHandler(InsightHandler(shipper))
    logging.error("checkout failed", extra={"trace_id": trace_id, "order_id": "1234"})
  4. 04

    Report unhandled exceptions

    For WSGI (Django, Flask, Pyramid). ASGI apps call client.report_bug from the framework's exception handler instead.

    from insight_recorder import InsightMiddleware
    
    app.wsgi_app = InsightMiddleware(app.wsgi_app, client, shipper=shipper, service="checkout-api")
  5. 05

    Verify it is working

    Log one line through the standard logging module, then raise from a route. The handler ships whatever your existing logging already produces.

    logging.error("insightrecorder smoke test", extra={"trace_id": "smoke-1"})
    
    @app.route("/_boom")
    def boom():
        raise RuntimeError("insightrecorder smoke test")

    Then look for the line at /app/logs and the report at /app/bugs.

    If nothing shows up

    Nothing arrived? Check shipper.dropped — a rising count means the queue is undersized. Delivery errors are silent unless you pass on_error. And shipper.close() has to run on shutdown; `with client.shipper() as shipper:` does it for you.

Before you ship

  • shipper.close() must run on shutdown — a lifespan teardown, a signal handler, or atexit.
  • The shipper batches on a daemon thread; send() never blocks and drops when the queue is full.
PROMPT Install the Python SDK with an AI agent
Raw .md

Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/python directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.

# Install and wire the InsightRecorder Python SDK

Paste this into a coding agent (Claude Code, Cursor, …) running inside your
Python service's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.

---

You are working in a Python service. Install and wire the InsightRecorder SDK so it
ships its logs to InsightRecorder and reports unhandled exceptions.

**Context you need:**

- InsightRecorder collects application logs and bug reports. `insight-recorder` is the
  supported way for a Python service to talk to it.
- Requires **Python 3.9+** and has **zero runtime dependencies** — HTTP is
  `urllib.request` — so it cannot conflict with the `requests`/`httpx` versions
  this project pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
  the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
  `bug:write` to report errors). **Do not** use a login JWT: those expire after
  an hour and are not meant for machines.

  Add the matching **read** scopes for anything the SDK reads back — `bug:read` for `list_bugs/get_bug`, `logs:read` for `list_logs`. A write-only key is the common mistake: shipping works, the first read returns 403.

- **Wire trace correlation if the application uses OpenTelemetry.** A log line
  and the request that wrote it are one investigation, and that join is the
  reason to ship logs here rather than anywhere else — see the "Trace
  correlation" section of the README for what this language needs (several do it
  automatically; the rest take one hook). Without it, every log call has to carry
  `trace_id` by hand, which nobody sustains past the first week.

**Steps:**

1. Add `insight-recorder` using whatever this project already uses —
   `pyproject.toml` dependencies, `requirements.txt`, Poetry, uv, or pipenv.
   Match the existing file and pinning style.

2. Read configuration from the environment, never hard-code it:
   `INSIGHT_BASE_URL` and `INSIGHT_API_KEY`. Wire them through the project's
   existing settings mechanism (Django `settings.py`, Pydantic `BaseSettings`,
   plain `os.environ`). If either is empty, the service must start normally with
   shipping disabled — telemetry is never a startup dependency.

3. Create the client and shipper **once**, where the application is composed
   (`app.py`, `wsgi.py`, a Django `AppConfig.ready()`, a FastAPI lifespan):

   ```python
   from insight_recorder import Client, InsightHandler

   client = Client(os.environ["INSIGHT_BASE_URL"], os.environ["INSIGHT_API_KEY"])
   shipper = client.shipper()
   ```

4. Bridge logging by adding the handler **alongside** the existing ones, never
   replacing them:

   ```python
   logging.getLogger().addHandler(InsightHandler(shipper))
   ```

   If the project configures logging through `dictConfig` (Django does), add it
   there as a handler entry instead of calling `addHandler` imperatively.
   Anything passed via `extra=` becomes a searchable attribute; `trace_id` and
   `span_id` are lifted into the record's correlation fields.

5. Report unhandled exceptions. For a WSGI app (Django, Flask, Pyramid):

   ```python
   app.wsgi_app = InsightMiddleware(app.wsgi_app, client, shipper=shipper,
                                    service="<service name>")
   ```

   For an ASGI framework (FastAPI, Starlette), call
   `client.report_bug({...})` from that framework's exception handler instead —
   the payload shape is in the package README.

6. Flush on shutdown: call `shipper.close()` from the project's existing
   shutdown path (a FastAPI lifespan's teardown, a signal handler, or `atexit`).
   Without it the final batch is lost.

**Rules — do not violate these:**

- Do **not** write a logging handler that makes an HTTP call per record. The SDK
  batches on a daemon thread, caps each request at `batch_size`, retries with
  backoff, and respects the server's quota (429 + `Retry-After`).
- Do **not** block on `shipper.send(...)` — it is non-blocking on purpose and
  drops when its queue is full.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
  reports: they routinely carry credentials and personal data.

**Verify before you finish:**

1. The project's test command passes and the service starts.
2. With both env vars set, exercise an endpoint and confirm the log lines appear
   in InsightRecorder at `/app/logs`.
3. Raise a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the service still starts with both variables **unset**.
5. Report what you changed, and state explicitly where `shipper.close()` runs.

If anything is ambiguous — which web framework, how logging is configured, how
shutdown is handled — inspect the repository and follow what is already there.
Do not restructure the service to fit the SDK.