Metadata-Version: 2.4
Name: fastapi-apm-watchlog
Version: 1.1.0
Summary: FastAPI instrumentation for Watchlog APM with JSON OTLP export
Home-page: https://github.com/Watchlog-monitoring/fastapi_apm_watchlog
Author: Mohammadreza
Author-email: mohammadnajm75@gmail.com
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Framework :: FastAPI
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: opentelemetry-api>=1.9.0
Requires-Dist: opentelemetry-sdk>=1.9.0
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.9.0
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.34b0
Requires-Dist: opentelemetry-instrumentation-requests>=0.34b0
Requires-Dist: dnspython>=2.0.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# fastapi\_watchlog\_apm

🔗 **Website**: [https://watchlog.io](https://watchlog.io)

**fastapi\_watchlog\_apm** is a lightweight APM integration for FastAPI, built on OpenTelemetry. It provides:

* **Auto‑instrumentation** for FastAPI endpoints and underlying HTTP calls
* **Manual custom spans** via OpenTelemetry API
* **OTLP exporters** over HTTP (protobuf) for compact transport
* **Environment detection** (local vs in‑cluster Kubernetes)
* **Configurable sampling**, error‑only and slow‑only span export
* **Metrics collection** via OTLP

---

## Installation

Install from PyPI:

```bash
pip install fastapi_apm_watchlog
```

Or directly from GitHub:

```bash
pip install git+https://github.com/Watchlog-monitoring/fastapi_apm_watchlog.git
```

---

## Quick Start

Initialize the APM **before** mounting any routes:

```python
# main.py
from fastapi import FastAPI
from fastapi_apm_watchlog import instrument

app = FastAPI()

# 1) Initialize Watchlog APM
instrument(
    app,
    service_name='my-fastapi-app',  # your service name
    error_tps=5,                    # max 5 error spans/sec
    send_error_spans=True,          # always send error spans
    slow_threshold_ms=300,          # always send spans >300ms
    sample_rate=1.0                 # random sample rate (0.0–1.0, capped at 0.3)
)

# 2) Define your routes
@app.get('/')
async def read_root():
    return {'message': 'Hello Watchlog APM + FastAPI'}

# 3) Run your app
# uvicorn main:app --reload
```

**What happens?**

1. FastAPI and HTTP calls are auto‑instrumented for tracing.
2. Spans and metrics are exported to the Watchlog agent (local or Kubernetes).
3. Custom filter rules (error‑only, slow‑only, sampling) apply.

---

## Configuration Options

| Parameter            | Type    | Default                     | Description                                                    |
| -------------------- | ------- | --------------------------- | -------------------------------------------------------------- |
| `service_name`       | `str`   | **required**                | Name of your FastAPI service                                   |
| `otlp_endpoint`      | `str`   | `http://localhost:3774/apm` | Base OTLP URL (overrides auto-detection if different from default) |
| `headers`            | `dict`  | `{}`                        | Additional HTTP headers for OTLP requests                      |
| `batch_max_size`     | `int`   | `200`                       | Max spans per batch                                            |
| `batch_delay_ms`     | `int`   | `5000`                      | Delay (ms) between batch exports                               |
| `metric_interval_ms` | `int`   | `5000`                      | Interval (ms) for metric export                                |
| `sample_rate`        | `float` | `1.0`                       | Random sampling rate (0.0–1.0, capped at **0.3**)              |
| `send_error_spans`   | `bool`  | `False`                     | If `True`, always export spans with non-`UNSET` status         |
| `error_tps`          | `int`   | `None`                      | Max error spans to export per second (`None` = unlimited)      |
| `slow_threshold_ms`  | `int`   | `0`                         | If >0, always export spans slower than this threshold (ms)     |

---

## Manual Custom Spans

Leverage OpenTelemetry API for custom instrumentation:

```python
from opentelemetry import trace

@app.get('/db')
async def fetch_db():
    tracer = trace.get_tracer('watchlog-apm', '1.0.0')
    span = tracer.start_span('db.query', attributes={'db.system':'postgresql'})
    try:
        # Execute your DB logic
        result = await run_query()
        return result
    except Exception as e:
        span.record_exception(e)
        raise
    finally:
        span.end()
```

---

## Environment Detection

The package automatically detects the runtime environment:

* **Local** (non‑K8s): sends to `http://localhost:3774/apm`
* **Kubernetes** (in‑cluster): sends to `http://watchlog-python-agent.monitoring.svc.cluster.local:3774/apm`

Detection checks:

1. Kubernetes ServiceAccount token file
2. `/proc/1/cgroup` for `kubepods`
3. DNS lookup of `kubernetes.default.svc.cluster.local`

**Manual Override:** You can override the endpoint by passing `otlp_endpoint` option in `instrument()` function

---

## Docker Setup

When running your FastAPI app in Docker, you need to configure the correct agent endpoint.

### Using otlp_endpoint Option (Recommended for Docker)

```python
# main.py
from fastapi import FastAPI
from fastapi_apm_watchlog import instrument

app = FastAPI()

# Initialize Watchlog APM with explicit agent URL for Docker
instrument(
    app,
    service_name='my-fastapi-app',
    otlp_endpoint='http://watchlog-agent:3774/apm',  # Use container name
    error_tps=5,
    send_error_spans=True,
    slow_threshold_ms=300,
    sample_rate=1.0
)

@app.get('/')
async def read_root():
    return {'message': 'Hello Watchlog APM + FastAPI'}

# Run: uvicorn main:app --reload
```

**Docker Compose Example:**
```yaml
version: '3.8'

services:
  watchlog-agent:
    image: watchlog/agent:latest
    container_name: watchlog-agent
    ports:
      - "3774:3774"
    environment:
      - WATCHLOG_APIKEY=your-api-key
      - WATCHLOG_SERVER=https://log.watchlog.ir
    networks:
      - app-network

  fastapi-app:
    build: .
    container_name: fastapi-app
    ports:
      - "8000:8000"
    environment:
      - PYTHONUNBUFFERED=1
    depends_on:
      - watchlog-agent
    networks:
      - app-network

networks:
  app-network:
    driver: bridge
```

**Docker Run Example:**
```bash
# 1. Create network
docker network create app-network

# 2. Run Watchlog Agent
docker run -d \
  --name watchlog-agent \
  --network app-network \
  -p 3774:3774 \
  -e WATCHLOG_APIKEY="your-api-key" \
  -e WATCHLOG_SERVER="https://log.watchlog.ir" \
  watchlog/agent:latest

# 3. Run FastAPI app (make sure your code sets otlp_endpoint='http://watchlog-agent:3774/apm')
docker run -d \
  --name fastapi-app \
  --network app-network \
  -p 8000:8000 \
  my-fastapi-app
```

**Important Notes:**
- When using Docker, use the container name as the hostname (e.g., `watchlog-agent`)
- Both containers must be on the same Docker network
- The agent must be running before your app starts
- Set the `otlp_endpoint` option in your code to point to the agent container
- If `otlp_endpoint` is not provided (or set to default), auto-detection will be used

---

## License

MIT © Mohammadreza

Built for Watchlog.io# fastapi_apm_watchlog
