# overcast-sdk (Python) v2

Overcast SRE monitoring SDK for Python.  One install, one line — captures everything.

**Zero external dependencies** — uses only Python stdlib.

## Install

```bash
pip install overcast-sdk
```

## Quick Start

```python
import overcast_sdk

overcast_sdk.init(api_key="oc_...", service_name="my-api")
```

That's it.  The SDK automatically captures:

- All `logging.*` output (DEBUG through CRITICAL)
- All `print()` / stdout / stderr output
- Uncaught exceptions (`sys.excepthook`)
- Thread exceptions (`threading.excepthook`)
- Outgoing HTTP requests (`requests`, `httpx`)
- Stack traces with full context

## What's New in v2

| Feature | Description |
|---|---|
| **Breadcrumbs** | Trail of events leading up to an error |
| **Scoped context** | Per-request tags, user info, extra data via `contextvars` |
| **`before_send` hook** | Filter or modify events before they leave the process |
| **`send_default_pii`** | Opt-in PII (default: off — IPs and user agents stripped) |
| **Gzip compression** | Payloads are gzip-compressed before sending |
| **Rate-limit handling** | Respects server `429` / `Retry-After` responses |
| **Health monitoring** | Adaptive downsampling when the transport is unhealthy |
| **Background worker** | Single persistent daemon thread (no thread-per-flush) |
| **Safe serialization** | Cycle detection, depth/breadth limits, safe repr fallbacks |
| **Modular architecture** | Pluggable integration framework with auto-discovery |

## Scoped Context

```python
import overcast_sdk

# Tags appear on every event in the current request
overcast_sdk.set_tag("region", "us-east-1")
overcast_sdk.set_tag("tenant", "acme-corp")

# User context — who was affected?
overcast_sdk.set_user({"id": "usr_123", "email": "alice@acme.com"})

# Extra data — arbitrary key/value
overcast_sdk.set_extra("order_id", 456)

# Manual breadcrumbs
overcast_sdk.add_breadcrumb({
    "type": "user",
    "category": "checkout",
    "message": "User clicked 'Place Order'",
})
```

## `before_send` Hook

```python
def my_filter(event):
    # Drop noisy health-check logs
    if "/healthz" in event.get("message", ""):
        return None  # drop
    # Modify events
    event.setdefault("tags", {})["team"] = "platform"
    return event

overcast_sdk.init(
    api_key="oc_...",
    service_name="my-api",
    before_send=my_filter,
)
```

## Framework Middleware

### Flask

```python
from flask import Flask
from overcast_sdk import middleware_flask

app = Flask(__name__)
middleware_flask(app)
```

### FastAPI / Starlette (ASGI)

```python
from fastapi import FastAPI
from overcast_sdk import middleware_asgi

app = FastAPI()
app = middleware_asgi(app)
```

### Django

```python
# settings.py
MIDDLEWARE = [
    "overcast_sdk.middleware_django",
    ...
]
```

## Manual Logging

```python
overcast_sdk.error("Payment failed", order_id=123)
overcast_sdk.warn("Slow query detected", query_ms=4200)
overcast_sdk.info("User signed up", user_id="usr_456")
overcast_sdk.debug("Cache hit", key="session:abc")

try:
    risky_operation()
except Exception as exc:
    overcast_sdk.capture_exception(exc)
```

## Encryption

```python
overcast_sdk.init(
    api_key="oc_...",
    service_name="my-api",
    encrypt_payload=True,
    encryption_key="your-64-char-hex-key-here",  # 32 bytes in hex
)
```

## All Options

| Option | Default | Description |
|---|---|---|
| `api_key` | *required* | Your Overcast API key |
| `service_name` | `"python-app"` | Service name in the dashboard |
| `environment` | `$OVERCAST_ENVIRONMENT` / `$PYTHON_ENV` / `"production"` | Environment label |
| `base_url` | `"https://platform.overcastsre.com"` | API base URL |
| `capture_logging` | `True` | Capture `logging.*` calls |
| `capture_stdout` | `True` | Capture `print()` / stdout |
| `capture_stderr` | `True` | Capture stderr |
| `capture_exceptions` | `True` | Capture uncaught exceptions |
| `capture_threads` | `True` | Capture thread exceptions |
| `capture_http` | `True` | Capture outgoing HTTP requests |
| `capture_signals` | `True` | Capture SIGTERM / SIGINT |
| `slow_request_threshold` | `5.0` | Seconds before a request is "slow" |
| `batch_size` | `50` | Logs per batch |
| `flush_interval` | `5.0` | Flush interval in seconds |
| `log_sampling_rate` | `1.0` | 0.0–1.0 sampling for non-error logs |
| `send_default_pii` | `False` | Include IPs and user agents |
| `sanitize` | `True` | Redact PII (passwords, tokens, etc.) |
| `encrypt_payload` | `False` | AES-256-GCM encrypt payloads |
| `encryption_key` | `""` | 32-byte hex key |
| `enable_compression` | `True` | Gzip-compress payloads |
| `before_send` | `None` | `fn(event) -> event \| None` |
| `before_breadcrumb` | `None` | `fn(crumb, hint) -> crumb \| None` |
| `debug` | `False` | Print SDK debug output to stderr |

## Architecture

```
overcast_sdk/
├── __init__.py              # Public exports
├── api.py                   # Top-level functions (init, error, set_tag, …)
├── client.py                # OvercastClient — owns transport, scrubber, scope
├── consts.py                # Constants, limits, default config
├── scope.py                 # Scope + breadcrumbs (contextvars isolation)
├── transport.py             # BackgroundWorker, batching, compression, rate-limiting
├── serializer.py            # Cycle-safe serialization with depth/breadth limits
├── scrubber.py              # PII scrubbing (key deny-list + regex patterns)
├── utils.py                 # Internal helpers (capture_internal, safe_str, …)
└── integrations/
    ├── __init__.py          # Integration ABC, DidNotEnable, auto-discovery
    ├── _logging.py          # logging module capture
    ├── _stdlib.py           # stdout, stderr, excepthook, signals
    ├── _flask.py            # Flask middleware
    ├── _django.py           # Django middleware
    ├── _asgi.py             # ASGI / FastAPI middleware
    ├── _requests.py         # requests library capture
    └── _httpx.py            # httpx library capture
```
