flask-watchlog-apm 1.1.0
Flask instrumentation for Watchlog APM with JSON OTLP export
License: MIT
Requires Python: >=3.7

# flask_watchlog_apm

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

**flask_watchlog_apm** is a lightweight APM integration for Flask, built on OpenTelemetry. It provides:

- **Auto-instrumentation** for Flask routes and underlying HTTP calls  
- **Manual custom spans** via the OpenTelemetry API  
- **JSON-over-HTTP exporter** (OTLP) compatible with Watchlog Agent  
- **Environment detection** (local vs in-cluster Kubernetes)  
- **Configurable sampling**, error-only and slow-only span export  

---

## Installation

Install from PyPI:

```bash
pip install flask_watchlog_apm
```

Or directly from GitHub:

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

---

## Quick Start

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

```python
# main.py
from flask import Flask
from flask_watchlog_apm.instrument import instrument_app

app = Flask(__name__)

# 1) Initialize Watchlog APM
instrument_app(
    app,
    service_name="my-flask-service",   # your service name
    headers={"Authorization": "Bearer <token>"},
    sample_rate=0.5,                   # random sample rate (0.0–1.0, capped at 0.3)
    send_error_spans=True,             # always export error spans
    error_tps=10,                      # max 10 error spans per second
    slow_threshold_ms=100              # always export spans >100ms
)

# 2) Define your routes
@app.route("/")
def hello():
    return "Hello, Watchlog APM!"

# 3) Run your app
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=6000, debug=True)
```

What happens?

1. Flask endpoints and outbound HTTP calls (via `requests`) are auto-instrumented.  
2. Spans are batched and sent as JSON to your Watchlog Agent (local or in-cluster).  
3. Configurable filters—sampling, error-only, slow-only—are applied.

---

## Configuration Options

| Parameter           | Type    | Default                     | Description                                                      |
| ------------------- | ------- | --------------------------- | ---------------------------------------------------------------- |
| `service_name`      | `str`   | **required**                | Name of your Flask 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`                       | Maximum spans per batch                                          |
| `batch_delay_ms`    | `int`   | `5000`                      | Delay in milliseconds between batch exports                      |
| `sample_rate`       | `float` | `1.0`                       | Random sampling rate (0.0–1.0, internal cap at 0.3)             |
| `send_error_spans`  | `bool`  | `False`                     | If `True`, always export spans with non-OK 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)       |
| `export_timeout`    | `float` | `10.0`                      | HTTP request timeout (seconds) for exporter POSTs                |

---

## Manual Custom Spans

Use the OpenTelemetry API to create custom spans:

```python
from opentelemetry import trace

tracer = trace.get_tracer(__name__)

@app.route("/db")
def fetch_db():
    with tracer.start_as_current_span("db.query", attributes={"db.system":"postgresql"}):
        # your DB logic here
        return "db query done"
```

---

## 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 in order:

1. Existence of `/var/run/secrets/kubernetes.io/serviceaccount/token`  
2. Presence of `kubepods` in `/proc/1/cgroup`  
3. DNS lookup of `kubernetes.default.svc.cluster.local`

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

---

## Docker Setup

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

### Using otlp_endpoint Option (Recommended for Docker)

```python
# main.py
from flask import Flask
from flask_watchlog_apm.instrument import instrument_app

app = Flask(__name__)

# Initialize Watchlog APM with explicit agent URL for Docker
instrument_app(
    app,
    service_name="my-flask-service",
    otlp_endpoint="http://watchlog-agent:3774/apm",  # Use container name
    sample_rate=0.5,
    send_error_spans=True,
    error_tps=10,
    slow_threshold_ms=100
)

@app.route("/")
def hello():
    return "Hello, Watchlog APM!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=6000, debug=True)
```

**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

  flask-app:
    build: .
    container_name: flask-app
    ports:
      - "6000:6000"
    environment:
      - FLASK_ENV=production
    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 Flask app (make sure your code sets otlp_endpoint='http://watchlog-agent:3774/apm')
docker run -d \
  --name flask-app \
  --network app-network \
  -p 6000:6000 \
  my-flask-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](https://watchlog.io)