Contrato de monitorización
Dos tipos de heartbeat. Esta página describe la monitorización de una extensión de Chrome registrada: tu agente envía su informe a POST /api/v1/monitoring/report. Los entornos de un proyecto web usan en el panel un endpoint aparte, POST /api/v1/heartbeat/report, con otro token. El informe de entorno mínimo es {"overall":"ok"}; también se acepta un payload de métricas personalizadas v2. No cambies un endpoint por el otro al configurar un agente. La sección 8b describe el heartbeat de un entorno.
La monitorización pone tu extensión bajo vigilancia constante: un semáforo en vivo en su tarjeta de la lista, una pestaña base monitoring en Detalle y un mensaje de estado de Telegram aparte con alertas de cambio de estado. Combina dos componentes que se activan de forma independiente: un heartbeat push (un agente en el host de tu servicio informa según un calendario declarado; el propio silencio es una señal) y pings de disponibilidad (el panel comprueba la disponibilidad de tus URL públicas).
1. Cómo funciona (dos componentes, un semáforo)
- Heartbeat push: un script cron en tu host (p. ej., un
monitor.shadaptado) envía por POST un informe de métricas v2 al panel con el intervalo que declaraste. Cada informe debe incluir el veredicto reservadooverall. Los informes perdidos escalan el semáforo (interruptor de hombre muerto): si caen el host, la red o cron, lo ves precisamente porque las llamadas se detienen. - Pings de disponibilidad: el propio panel consulta periódicamente las URL públicas que configuras (sitio, api, docs) y solo comprueba la disponibilidad, nunca el contenido. No hace falta ningún agente, basta con una URL.
Una extensión está "en monitorización" cuando la monitorización está activada y hay al menos un componente configurado. En caso contrario, su tarjeta muestra un punto gris neutro.
1b. Heartbeats y la bolsa de slots
La monitorización se configura a nivel de extensión como dos listas planas independientes que comparten una bolsa de slots: heartbeats (productores push, antes llamados "stands") y
pings de disponibilidad (comprobaciones pull). Una extensión puede tener varios heartbeats, por ejemplo un host principal y un worker de pago. Cada heartbeat envía informes con su propio token bearer y declara su propia presentación (pestañas, secciones y líneas de Telegram) mediante su label. Los pings de disponibilidad son una lista plana aparte que pertenece a la extensión y no cuelga de ningún heartbeat. El panel lo combina todo en un único semáforo agregado (el peor estado), una única vista Detalle y un único mensaje resumen de Telegram.
Los heartbeats y los pings de una extensión comparten una única bolsa de slots: un heartbeat ocupa un slot, un ping activo ocupa un slot, y su suma no debe superar el max_slots del plan (Prueba 3 · Studio 10 · Fleet 25). Superar la bolsa devuelve 409 slot_limit; tras bajar a un plan inferior, el excedente se suspende (primero los pings, al final los heartbeats). La respuesta incluye slots_used y max_slots para que la interfaz pueda mostrar el presupuesto.
2. El semáforo
El agregado es el peor estado de todos los componentes de heartbeat activados y del semáforo de cada ping activo, donde las gravedades se ordenan como ok < warn < crit. El componente de un heartbeat es max(reported, watchdog): reported es la peor gravedad entre la métrica overall y los umbrales superados (6.2) del último informe aceptado por el panel; watchdog es un umbral mínimo de frescura calculado con el reloj de recepción del propio panel (el desfase del reloj del agente no importa).
| Estado | Color | Cuándo |
|---|---|---|
off | ⚪ gris | Monitorización no activada o ningún componente configurado. |
paused | ⚪ gris | Activada pero congelada (bloqueo por plan o pausa manual). Los informes se siguen almacenando. |
green | 🟢 | Todos los componentes ok. |
yellow | 🟡 | Peor componente warn. |
red | 🔴 | Peor componente crit. |
La gravedad canónica es ok | warn | crit. Los alias habituales se normalizan al recibirse: warning pasa a ser warn; error y critical pasan a ser crit.
3. Watchdog (heartbeats perdidos)
Declaras el intervalo de informe esperado (de 60 s a 24 h) al activar el heartbeat. Después, el watchdog impone un umbral mínimo al semáforo según la frescura, con grace = max(60s, 25% of the interval) para absorber la variación de cron y de la red:
| Antigüedad del último informe aceptado | Umbral del watchdog |
|---|---|
| hasta intervalo + margen | ok (verde) |
| 1 informe perdido (hasta 2 × intervalo + margen) | warn (amarillo) |
| 2 o más informes perdidos | crit (rojo) |
Un informe rechazado (401 / 413 / 422 / 429) nunca renueva el watchdog, así que un agente averiado escala de forma honesta; el último motivo de rechazo se ve en la configuración de monitorización. La recuperación es instantánea: el primer informe aceptado recalcula el semáforo de inmediato.
4. Pings de disponibilidad
Un ping es una comprobación de disponibilidad, con nombre, de una URL https pública. El panel realiza GET con un timeout de 10 s, sigue como máximo 5 redirecciones, no lee el cuerpo más allá de un límite mínimo y considera un estado final de clase 2xx como éxito (204 y 206 también cuentan). Un 3xx final (bucle de redirecciones o límite alcanzado), 4xx, 5xx, timeout o error de DNS/TLS/red es un fallo. Un fallo se confirma con un reintento tras unos 15 s antes de contar, lo que absorbe cortes de red puntuales.
| Fallos consecutivos confirmados | Semáforo del ping |
|---|---|
| 0 | ok |
| 1 | warn |
| 2 o más | crit |
Los resultados de los pings aparecen como métricas propias del panel ping:<name> en la sección
pings (Uptime) de la pestaña base
monitoring y como la línea reservada de Telegram pings: 🟢 api 210ms · 🟢 site 90ms · 🔴 docs (timeout). El número de pings por extensión y el intervalo mínimo dependen del plan:
| Plan | Pings de disponibilidad por extensión | Intervalo mínimo de ping |
|---|---|---|
| Prueba | 3 | 5 minutos |
| Indie | 3 | 5 minutos |
| Studio | 10 | 1 minuto |
| Fleet | 25 | 1 minuto |
La disponibilidad de la monitorización y el número de slots dependen de tu plan; el intervalo mínimo de heartbeat es de 60s.
5. Poner una extensión en monitorización
En los Ajustes, bloque de monitorización de la extensión:
- Activa la monitorización, si tu plan la incluye.
- Heartbeat: actívalo y declara el intervalo de informe; el panel emite un token de monitorización (bearer), que se muestra una sola vez (puedes rotarlo en cualquier momento; el token anterior recibe 401 de inmediato). Configura el token y la URL del endpoint en el entorno de tu agente.
- Pings: añade URL públicas con nombre (p. ej.,
site,api,docs) con un intervalo por ping, hasta el límite del plan. - Telegram: activa la entrega de la monitorización y, opcionalmente, define un grupo/chat aparte.
6. El formato del informe
El informe es el mismo payload v2 basado en metadatos que el del
contrato de métricas personalizadas (pestañas, secciones, líneas de Telegram, métricas), enviado a POST /api/v1/monitoring/report con Authorization: Bearer <monitoring token>. La monitorización añade ids reservados:
| Id reservado | Estado | Significado |
|---|---|---|
overall | obligatorio | Métrica de tipo status: el veredicto del tick, gravedad ok | warn | crit. Alimenta el agregado. Un informe sin un overall válido se rechaza (422). |
issues | recomendado | Métrica de tipo list: los problemas actuales, items en el nivel superior de la métrica. Hasta 5 elementos se citan en los textos de alerta. |
ping:* | prohibido | Espacio de nombres de ids de métrica para las métricas de ping propias del panel; enviarlo se rechaza (422). |
pings | prohibido | Id de la línea de Telegram de los pings del panel; enviarla se rechaza (422). |
monitoring | pestaña propiedad del panel | Id de la pestaña base. Las métricas pueden apuntar a ella (dashboard.tab: "monitoring") sin declararla en tabs; si se declara, la declaración se ignora. |
base | pestaña propiedad del panel | El id de la pestaña Basic, reservado del mismo modo: una declaración se ignora. |
Límites: payload de hasta 64 KB, hasta 200 métricas, hasta 2 informes por minuto por fuente (el exceso recibe 429 y no cuenta como heartbeat).
Un cuerpo de más de 64 KB recibe 413 payload_too_large y nunca se recorta ni se lee como un informe más corto. Un informe que incumple las reglas anteriores recibe 422 invalid_report con todos los problemas en una lista. Ambos motivos se guardan como el último error del heartbeat:
HTTP/1.1 413 Payload Too Large
{ "code": "payload_too_large", "message": "report is over 64 KB; send fewer metrics or split them across heartbeats",
"limit_bytes": 65536, "size_bytes": 71680 }
HTTP/1.1 422 Unprocessable Entity
{ "code": "invalid_report", "message": "report rejected",
"errors": [ "metric \"overall\" must have kind \"status\" (got \"text\")",
"metric id \"ping:x\" uses the reserved \"ping:\" namespace" ] }6.1. Informe de ejemplo
{
"schema_version": "2.0",
"generated_at": "2026-07-12T10:19:58Z",
"tabs": [ { "id": "stand", "title": "Stand", "order": 5, "icon": "server" } ],
"sections": [ { "tab": "stand", "id": "docker", "title": "Docker", "order": 0 },
{ "tab": "monitoring", "id": "host", "title": "Host", "order": 10 } ],
"telegram": { "header": "{sev} {name} monitoring",
"lines": [ { "id": "host", "order": 0, "prefix": "🖥 " },
{ "id": "docker", "order": 1, "prefix": "📦 " } ] },
"metrics": [
{ "id": "overall", "kind": "status", "state": "ok", "severity": "ok" },
{ "id": "issues", "kind": "list",
"items": [ { "text": "RAM 91% (7.1/7.8G)", "severity": "warn" } ],
"dashboard": { "show": true, "tab": "monitoring", "section": "host", "order": 0,
"render": "list", "name": "Issues" },
"telegram": { "show": false } },
{ "id": "ram", "kind": "percent", "label": "RAM", "value": 91, "severity": "warn",
"dashboard": { "show": true, "tab": "monitoring", "section": "host", "order": 1,
"render": "kpi" },
"telegram": { "show": true, "line": "host", "pos": 10,
"format": "RAM {value}%", "sep": " · " } }
]
}Respuesta correcta:
{ "accepted": true, "effective": "green", "next_expected_by": "2026-07-12T10:26:13Z" }6.2. Umbrales en las métricas del informe
Cualquier métrica numérica de un informe, salvo overall, puede llevar thresholds (el bloque del contrato de métricas personalizadas, sección 5.4). El panel calcula la severidad de esa métrica al recibirla y la luz del heartbeat es lo peor entre overall y los umbrales superados: una métrica por encima de crit pone el heartbeat en rojo aunque overall sea ok. recover se compara con el informe aceptado anterior del mismo heartbeat. Un bloque mal formado recibe 422 invalid_report con el motivo en la lista. Un informe sin umbrales se comporta como antes.
{ "id": "queue", "kind": "number", "label": "Mail queue", "value": 41,
"thresholds": { "direction": "above", "warn": 20, "crit": 100, "unit": "msgs", "recover": 15 },
"dashboard": { "show": true, "tab": "monitoring", "section": "host", "order": 2, "render": "kpi" },
"telegram": { "show": true, "line": "host", "pos": 20, "format": "queue {value}" } }El panel la muestra como 41 (threshold 20/100 msgs) en una tarjeta amarilla, y el heartbeat queda en amarillo.
7. Integración de un script de monitorización (estilo monitor.sh)
Si ya ejecutas un script cron de comprobación de estado, añade un POST de mejor esfuerzo al final de cada tick. Un fallo de entrega no debe romper el tick: el watchdog detectará de todos modos un silencio prolongado.
# monitor.env
EXTOPS_REPORT_URL="https://extops.dev/api/v1/monitoring/report"
EXTOPS_REPORT_TOKEN="<monitoring token from Settings>"
# end of the tick: build the payload with jq from the values you already computed,
# then push it best-effort (never let delivery break the tick itself)
payload="$(jq -n --arg sev "$SEVERITY" '{
schema_version: "2.0",
generated_at: (now | todate),
metrics: [ { id: "overall", kind: "status", state: $sev, severity: $sev } ]
}')"
code="$(curl -sS -m 10 -o /tmp/extops-report.json -w '%{http_code}' -X POST "$EXTOPS_REPORT_URL" \
-H "Authorization: Bearer $EXTOPS_REPORT_TOKEN" \
-H "Content-Type: application/json" \
-d "$payload" || echo 000)"
# 413 payload_too_large: over 64 KB; 422 invalid_report: .errors lists every problem
case "$code" in 413|422) logger -t monitor "panel: $code $(jq -c . /tmp/extops-report.json)" ;; esac
Asigna las gravedades de tu script al conjunto canónico (critical se acepta y se normaliza a crit) y pasa tu lista de incidencias actual a la métrica issues. Establece el intervalo declarado igual al periodo de cron.
8. Telegram: mensaje de estado y alertas separados
- Los datos de monitorización llegan a Telegram como un mensaje de estado editable aparte, distinto del mensaje de estado de las métricas personalizadas: encabezado + la línea reservada
pings+ las líneas que declare tu informe. - Opcionalmente, envía la monitorización a un grupo/chat aparte; si no se define, se usa el chat del canal de la extensión. El transporte del bot se comparte con el canal de la extensión.
- Alertas de cambio de estado se disparan en cada cambio de color del agregado, en ambos sentidos, incluida la recuperación a verde (con la duración del incidente). Mientras el color es estable, hay silencio; los cambios de configuración (pausar, editar pings) no generan alertas.
8b. Heartbeats de proyectos web
Un proyecto web tiene sus propios heartbeats. Cada uno pertenece a un entorno (por ejemplo Production) y tiene su propio token hb_. Las reglas del informe son las de la sección 6; abajo está lo que cambia.
POST /api/v1/heartbeat/report
Authorization: Bearer hb_<64 hex>
Content-Type: application/json
{"overall":"ok"}- Cuerpo: un informe v2 con la métrica obligatoria
overallde tipo status, o el cuerpo mínimo{"overall":"ok"}conok,warnocrit. El panel convierte el cuerpo mínimo en un informe v2 y guarda el cuerpo normalizado. - Límites: como máximo 64 KB por informe, como máximo 4 informes por minuto por token y como máximo 200 métricas. Un informe con más de 200 métricas recibe 422, igual que en una extensión.
- Respuestas:
200 {"accepted":true,"light":"green"},401para un token desconocido,413 payload_too_large,422 invalid_report,429conRetry-After: 60. Los motivos de 413 y 422 aparecen como Last error en Setup, Monitoring. - Covers: un heartbeat cubre cero o más superficies de su entorno. Su semáforo colorea cada superficie cubierta; un heartbeat que no cubre ninguna colorea el entorno. Covers se define en el panel, no en el informe.
- Claro: verde hasta interval + grace tras el último informe aceptado, amarillo hasta 2 × interval + grace y rojo después, con
grace = max(60s, 25% of the interval). El intervalo va de 60s a 24h. - Waiting for first report: un heartbeat nuevo sigue gris hasta su primer informe aceptado. Tras 24 horas sin informe pasa a amarillo y envía una notificación; nunca está en rojo antes de su primer informe.
- Umbrales: como en la sección 6.2. La luz de la respuesta 200 y del panel es lo peor entre la frescura,
overally los umbrales superados, y a través de Covers llega a las superficies cubiertas. - Pestañas: las métricas con
tab:"monitoring"aparecen en la primera pestaña de la sección Monitoring del proyecto, ybasetambién está reservado. Las pestañas que declara obtienen pestañas propias.
8c. Quién se conecta a tus servicios y cómo
Un proyecto web puede añadir comprobaciones de puertos a un Service cuya dirección es un host, por ejemplo un servidor de correo. Estas comprobaciones las hace el propio Ext Ops Panel. Esta sección dice quién se conecta, qué se envía, con qué frecuencia y cómo puede excluirse un host. El contrato del agente de arriba no cambia.
- Desde dónde: una dirección,
89.117.144.124, con el nombreprobe.extops.dev. El nombre resuelve a esta dirección y la dirección resuelve de vuelta al nombre. No hay otro punto de observación. - Solo hosts verificados: una comprobación solo se ejecuta en un host bajo un dominio que el dueño del proyecto verificó con un registro DNS TXT o un archivo well-known. Si la prueba desaparece, las comprobaciones se pausan.
- Puertos: TCP 25, 110, 143, 465, 587, 993, 995; TLS 443, 465, 993, 995; SMTP 25 y 587; SMTPS 465; IMAP 143; IMAPS 993. Nunca se comprueba ningún otro puerto.
- TCP y TLS: conectar y leer el saludo que envía el servidor, o completar una negociación TLS y leer el certificado. No se envía nada.
- SMTP: esperar el saludo
220, enviarEHLO probe.extops.dev, enviarSTARTTLSsi el servidor lo ofrece y completar la negociación, y luego enviarQUIT. SMTPS en el puerto 465 hace lo mismo dentro de TLS, sin STARTTLS. - IMAP: esperar
* OK, enviara1 CAPABILITY, enviarSTARTTLSen el puerto 143 si se ofrece, enviarID ("name" "ExtOpsPanel-Probe" "support-url" "https://extops.dev/docs/monitoring#probes")solo si el servidor anuncia ID, y luego enviarLOGOUT. IMAPS en el puerto 993 hace lo mismo dentro de TLS. - Nunca se envía:
AUTH,LOGIN,AUTHENTICATE,MAIL FROM,RCPT TO,DATA,VRFY,EXPN,SELECT. La sonda nunca inicia sesión, nunca envía correo y nunca abre un buzón. Cada línea pasa una lista fija de comandos permitidos antes de escribirse, y una prueba falla si cualquier otro comando llega a un servidor. - Con qué frecuencia: una comprobación de correo se ejecuta como mucho cada 5 minutos en cualquier plan, con una dispersión del 10% en ambos sentidos. Un host recibe como mucho una conexión a la vez y como mucho 40 conexiones por hora, para todos los proyectos juntos. Por encima de eso, el host queda tranquilo hasta la hora siguiente.
- Cuando un servidor dice que no: un SMTP
421detiene todas las comprobaciones de ese host durante una hora. Un SMTP554o un IMAP* BYEse registra como rechazo y solo se vuelve a intentar en el siguiente intervalo. - Límites de una comprobación: 5 segundos para conectar, 10 segundos para todo el diálogo, como mucho 4 KB leídos.
- Qué se guarda: el resultado, el código de respuesta, la latencia, la primera línea del saludo recortada a 120 caracteres y sin direcciones de correo, una lista corta de capacidades como STARTTLS e IMAP4rev1 y las fechas del certificado. No el saludo completo, ni las respuestas a EHLO o ID, ni ninguna dirección.
- Cómo excluirse: si gestionas un host y no quieres estas comprobaciones, escribe a
support@extops.dev con el nombre del host. Excluimos el host o todo su dominio, y después nada se conecta a él. También puedes responder
421o bloquear89.117.144.124. El dueño del proyecto puede pausar o borrar una comprobación en cualquier momento en Setup, Monitoring.
9. El contrato legible por máquina
La descripción formal del endpoint de informes, el esquema del payload, los ids reservados y la semántica del semáforo está en el archivo OpenAPI 3.1. Lleva la misma versión de contrato (2.1.0).