Python
A logging.Handler you add alongside the existing ones, plus WSGI middleware for Django and Flask.
Before you start
- Where your InsightRecorder runs
- The base URL every call goes to —
http://localhost:8080when you are running it locally, your own hostname otherwise. - An API key
- Settings → API keys, scoped to
logs:writeandbug: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.
- 01
Install
HTTP is urllib.request, so it cannot conflict with the requests or httpx versions you pin.
pip install insight-recorder - 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() - 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"}) - 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") - 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 upNothing 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.
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.