# Python + Streamlit: redacted ERROR logs to BugTape in 5 minutes

Streamlit apps fail in the page, not always in a tidy ops pipeline. The error shows in the app. The same failure can also hit the logging path. You still need something an agent can pick up later over MCP, with a human reviewing any pull request.

This cookbook follows the public [Python docs](/docs/python/): install the package, call `init`, let ERROR logs and Streamlit page errors report with messages redacted by pattern, then confirm an issue in the console.

BugTape is for capture, then agent-native closure via MCP, then pull-request review, then a check that the fix holds in the next release. It is not a pitch to replace your metrics or a full observability stack.

Package: [bugtape on PyPI](https://pypi.org/project/bugtape/) (the wheel named in the Python docs is 0.1.1).

## Checklist (5 minutes)

1. Create a free project and copy a Python capture key from Setup → Python (`bt_live_…` or `bt_test_…`). [Register](/console/register).
2. `pip install bugtape` (Python 3.9 or later; no package dependencies, per the docs and PyPI).
3. Export `BUGTAPE_KEY`.
4. Call `bugtape.init(service=..., release=...)` at the top of the Streamlit page.
5. Trigger a test ERROR or a page exception.
6. Open **Issues** in the BugTape console and confirm the report.
7. Optional: connect MCP so an agent can read the issue ([MCP install](/docs/mcp-install/)). A human still reviews the pull request.

## Install

From [PyPI](https://pypi.org/project/bugtape/) and the [Python docs](/docs/python/):

```bash
pip install bugtape
```

If your network cannot reach PyPI, the docs also document a reviewed wheel hosted by BugTape:

```bash
pip install https://app.bugtape.ai/sdk/python/bugtape-0.1.1-py3-none-any.whl
```

> The wheel in this post is `bugtape-0.1.1-py3-none-any.whl`, the file named in the Python docs. If that doc bumps the version, update this post in the same change.

## Start (any Python process)

```python
import bugtape

bugtape.init(service="reports", release="2026.09.28")
```

The capture key is read from the `BUGTAPE_KEY` environment variable. Without a key, `init` logs one warning and reporting stays off.

From then on BugTape reports (per the docs):

- uncaught exceptions in the main thread and in threads
- log records at `ERROR` and above, from any logger
- anything you pass to `bugtape.capture_exception()` or `bugtape.capture_message()`
- uncaught exceptions in Streamlit pages, when Streamlit is imported

```python
try:
    run_export()
except Exception:
    bugtape.capture_exception()  # reports the exception being handled
    raise
```

## Streamlit

```python
import streamlit as st
import bugtape

bugtape.init(service="analytics")

st.title("Revenue")
```

Notes from the docs that matter in production:

- Streamlit reruns the page on interaction. Calling `init` at the top with the same settings keeps the running client. That is fine.
- Streamlit catches page errors and shows them in the app, so they never reach Python’s normal error hook. BugTape wraps the function Streamlit uses for those errors. The app still shows the error. BugTape files one report with the page name and the Streamlit session.
- Streamlit also logs the same error. BugTape recognises it and does not report it twice.
- If a future Streamlit release changes that hook, `init(streamlit=True)` logs a warning. You can wrap page code yourself:

```python
from bugtape.integrations.streamlit import capture

with capture():
    render_page()
```

## What leaves your server (redaction)

Per the [Python docs](/docs/python/).

**Sent:** exception type and a redacted message; file, function and line per frame; `service`, `release` and `environment`; log templates such as `export failed for %s`; Datadog trace and span ids when `ddtrace` is running.

**Never sent:** frame locals; source code lines; arguments passed to a log call; request bodies; other environment variables beyond the documented settings, Datadog keys, and the commit keys used for release.

Redaction replaces quoted values, numbers and amounts, SQL, emails, credentialed URLs, cloud paths, ids, and common `token=` / `password=` style pairs. Pattern-based redaction has limits (for example a bare name in an f-string). Mitigations in the docs: log with `%s` arguments, `extra_redactions`, `before_send`, or `send_messages=False`.

```python
bugtape.init(
    service="reports",
    extra_redactions=[(r"\bACME-\d+\b", "[account]")],
    before_send=lambda report: None if "healthcheck" in report["title"] else report,
)
```

## Release (so fix verification works)

Every report should carry a release so BugTape can tell a fixed issue from a regression. Pass `release=`, or set `BUGTAPE_RELEASE` or `DD_VERSION`, or rely on the CI commit environment variables listed in the docs (`GITHUB_SHA` and the rest of `COMMIT_ENV_KEYS`). Without a release the SDK records `release_missing`.

## Check it

```bash
BUGTAPE_KEY=bt_test_… python -c "import bugtape; bugtape.init(service='check'); bugtape.capture_message('BugTape Python check'); bugtape.flush()"
```

Then open Issues in the console.

For short scripts, call `bugtape.flush()` before exit so queued reports send.

## Optional: agent loop after the error lands

Once issues appear:

1. Connect MCP ([install guide](/docs/mcp-install/), or the [MCP install post](/blog/install-bugtape-mcp-claude-cursor-codex/)).
2. The agent uses `list_recommended_critical`, then `get_repro_context`, then `ack_bug` with `prUrl`.
3. You review the pull request.
4. Mark the issue fixed and ship a release. BugTape alerts you if the same failure returns on a newer release.

## Summary

| Step | Action |
|---|---|
| Install | `pip install bugtape` |
| Configure | `BUGTAPE_KEY` plus `bugtape.init(service=..., release=...)` |
| Streamlit | `init` at the top of the page; page errors and ERROR logs |
| Privacy | Messages redacted by pattern; locals never read |
| Next | MCP, then a human-reviewed pull request |

Full reference: [Python docs](/docs/python/). Where data lives and what is masked: [Trust](/trust/).
