# 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.
