Metadata-Version: 2.4
Name: viclix-agent
Version: 0.4.2
Summary: Registro de tráfico (IPs, usuarios, sesiones, endpoints) para apps FastAPI, con visor incluido, listo para Viclix
License: MIT
Keywords: fastapi,asgi,middleware,logging,observability,viclix
Classifier: Framework :: FastAPI
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Logging
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100; extra == "fastapi"
Provides-Extra: dev
Requires-Dist: fastapi>=0.100; extra == "dev"
Requires-Dist: httpx>=0.24; extra == "dev"
Requires-Dist: pytest>=7; extra == "dev"

# viclix-agent

Registro de tráfico para apps **FastAPI**, listo para visualizar en **Viclix**.

Instala un middleware ASGI que registra **cada request** (IP, usuario, endpoint,
hora, duración, status…) en un archivo **JSONL dedicado**. La escritura ocurre en
un **hilo de fondo** con el archivo siempre abierto, así que el camino del request
nunca hace I/O de disco. Después, esos logs alimentan la visualización animada
de Viclix: quién entró, desde qué IP, a qué páginas y en qué orden.

## Instalación

```bash
pip install viclix-agent
```

## Uso mínimo

```python
from fastapi import FastAPI
from viclix_agent import MiddlewareLogger

app = FastAPI()
app.add_middleware(MiddlewareLogger)
```

Eso ya crea la carpeta `.viclix-agent/middleware/` y escribe ahí
`traffic-YYYY-MM-DD.jsonl`.

## Configuración

```python
app.add_middleware(
    MiddlewareLogger,
    log_path=".viclix-agent/middleware/traffic.jsonl",  # ruta base (se le añade la fecha)
    trust_proxy=True,                       # IP real desde X-Forwarded-For (PythonAnywhere)
    rotation="daily",                       # "daily" o None
    include_query=False,                    # incluir query string (ojo con datos sensibles)
    ignore_paths=["/static", "/health"],    # prefijos que no se registran
)
```

| Parámetro       | Default                  | Descripción                                             |
|-----------------|--------------------------|---------------------------------------------------------|
| `log_path`      | `.viclix-agent/middleware/traffic.jsonl` | Ruta base del log.                     |
| `trust_proxy`   | `True`                   | Lee la IP de `X-Forwarded-For` / `X-Real-IP`.          |
| `user_resolver` | lee `request.state.user` | `Callable[[scope], id]` para extraer el usuario.       |
| `rotation`      | `"daily"`                | Rotación diaria del archivo, o `None`.                 |
| `include_query` | `False`                  | Incluye el query string en el evento.                  |
| `ignore_paths`  | `[]`                     | Prefijos de path a ignorar.                            |
| `track_sessions`| `True`                   | Cookie de sesión anónima (`viclix_sid`) por visitante. |
| `session_cookie`| `"viclix_sid"`           | Nombre de la cookie de sesión.                         |
| `session_ttl_days` | `365`                 | Duración de la cookie de sesión.                       |

### Sesiones (trazar el flujo de un visitante)

Con `track_sessions=True` (por defecto), cada visitante recibe una cookie
anónima `viclix_sid` y cada evento lleva un campo `session`. Así puedes seguir la
secuencia completa de páginas de un mismo visitante **aunque no haya login** —
por ejemplo, para ver qué flujo llevó a un usuario a un error 4xx/5xx:

```
GET /            -> 200   sess=44ef64b0
GET /checkout    -> 200   sess=44ef64b0
GET /boom        -> 500   sess=44ef64b0   ← el flujo que terminó en error
```

### Identificar al usuario

Por defecto se lee `request.state.user` (lo que deje tu capa de auth). Si guardas
un objeto, se prueban `username`, `email`, `id`, `pk`. Para lógica propia:

```python
def resolver(scope):
    state = scope.get("state") or {}
    user = state.get("user")
    return getattr(user, "email", None)

app.add_middleware(MiddlewareLogger, user_resolver=resolver)
```

## Rotación y retención (tamaño del log)

Por defecto los logs rotan **cada hora**, los archivos cerrados se **comprimen a
`.gz`** y se **borran tras 24 h**:

```python
app.add_middleware(
    MiddlewareLogger,
    rotation="hourly",       # "hourly" (default) | "daily" | None
    retention_hours=24,      # borra archivos más viejos que esto (None = sin límite)
    max_total_mb=200,        # tope duro de tamaño: borra los más viejos (None = sin tope)
    compress=True,           # gzip de los archivos ya rotados
)
```

- `retention_hours` acota por **tiempo**; `max_total_mb` es la red de seguridad
  por **tamaño** (un pico de tráfico o un bot puede llenar el disco dentro del
  plazo de retención).
- La limpieza y la compresión corren en el **hilo de fondo** al rotar — nunca en
  el camino del request — y solo tocan archivos del propio log.
- El visor lee `.jsonl` y `.jsonl.gz` de forma transparente.

**El mayor ahorro suele ser no registrar assets estáticos:**

```python
app.add_middleware(MiddlewareLogger, ignore_paths=["/static", "/assets", "/favicon.ico"])
```

Las background tasks aceptan los mismos parámetros:

```python
track_background_tasks(name="checkout", rotation="hourly", retention_hours=24)
```

## Varias apps (mismo venv / servidor)

Si tienes **varias apps FastAPI** en el mismo directorio/venv, dale a cada una un
`name` distinto y cada una escribirá en **su propio archivo**
(`.viclix-agent/middleware/<name>.jsonl`), en vez de compartir uno solo:

```python
app.add_middleware(MiddlewareLogger, name="checkout")   # → checkout.jsonl
app.add_middleware(MiddlewareLogger, name="auth")       # → auth.jsonl
track_background_tasks(name="checkout")                 # tasks también separadas
```

Además, cada evento lleva un campo `app`. En el visor:

```bash
viclix-agent                  # agrega TODAS las apps (avisa cuáles hay)
viclix-agent --app checkout   # solo esa app
```

Sin `name`, todas comparten `traffic.jsonl` (comportamiento anterior).

## Visor (CLI)

El paquete instala el comando `viclix-agent`. Al ejecutarlo en tu terminal:

```bash
viclix-agent
```

1. Lee los logs de `.viclix-agent/middleware/` (todos los días agregados). Si no
   existe esa carpeta, busca recursivamente (hasta 3 niveles) como respaldo.
2. Levanta un servidor local y abre el navegador con una visualización interactiva.
3. Muestra estadísticas, línea de tiempo por sesión/IP (con **animación** del
   tráfico), endpoints e IPs más activas, y una tabla filtrable por IP, path,
   usuario o clase de status.

### Mundo vivo (experimental)

En `/experimental` (enlace en el dashboard) hay una representación isométrica
donde **cada IP es una personita** que sale de la "entrada" y camina hacia el
endpoint que solicitó. Los endpoints se agrupan por clase (primer segmento del
path) en **distritos** — `/products`, `/products/42` → distrito *products* — y el
mapa **empieza en 0 y crece** a medida que se descubren IPs y endpoints. El color
del pulso indica la clase de status del último request (verde/amarillo/naranja/rojo).
Controles: reproducir, velocidad, scrub temporal, arrastrar para mover, rueda para
zoom, *Fit* para reencuadrar. El motor de personajes es el de POKER (`sprites.js`).

Esta vista es autocontenida y pública: es la base de lo que luego se mostrará en
Viclix.

Opciones:

```bash
viclix-agent [LOG]           # ruta a un .jsonl específico (opcional)
  --dir .                    # directorio raíz de búsqueda
  --depth 3                  # profundidad máxima de búsqueda
  --port 8787                # puerto del servidor local
  --all-days                 # agregar todos los archivos diarios de la misma base
  --no-browser               # no abrir el navegador automáticamente
```

## Formato del log (JSONL)

Una línea JSON por request:

```json
{"v":1,"ts":"2026-07-24T16:50:55.349029+00:00","ip":"1.2.3.4","method":"GET","path":"/dashboard","status":200,"duration_ms":3.06,"user":"jp@sizth.com","ua":"Mozilla/5.0","referer":null}
```

| Campo         | Descripción                                    |
|---------------|------------------------------------------------|
| `v`           | Versión del esquema.                           |
| `ts`          | Timestamp ISO-8601 en UTC.                     |
| `ip`          | IP del cliente.                                |
| `method`      | Método HTTP.                                   |
| `path`        | Ruta solicitada.                               |
| `status`      | Código de respuesta.                           |
| `duration_ms` | Duración del request en ms.                    |
| `user`        | Identificador de usuario (o `null`).           |
| `session`     | ID de sesión anónima (cookie `viclix_sid`).    |
| `ua`          | User-Agent.                                    |
| `referer`     | Referer.                                       |
| `query`       | Query string (solo si `include_query=True`).   |

## Notas de rendimiento

- Los eventos se encolan en memoria y los escribe un hilo daemon; el request no
  espera al disco.
- Si la cola se satura (`queue_maxsize`, 10k por defecto), los eventos nuevos se
  **descartan** en vez de bloquear la app. El writer lleva contadores `written`
  y `dropped`.
- El archivo se hace flush cada segundo y se cierra limpiamente al terminar el
  proceso (`atexit`).
