@eiaifactory/monitor 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -182,7 +182,7 @@ rechazan uno por uno con `lote_excedido`) y **hasta 64 KB** (si no, `413` y se r
|
|
|
182
182
|
| `user_agent` | string | no | 512 | |
|
|
183
183
|
| `contexto` | objeto plano de escalares | no | 2 KB | Claves sensibles → `[redactado]` |
|
|
184
184
|
| `repeticiones` | entero ≥ 1 | no | — | Colapso local. Default 1 |
|
|
185
|
-
| `huella_manual` | string | no | 128 | Reemplaza la agrupación calculada (`cron
|
|
185
|
+
| `huella_manual` | string | no | 128 | Reemplaza la agrupación calculada (`cron:<función>`, como hace `app.run_job`) |
|
|
186
186
|
|
|
187
187
|
Los topes son en caracteres. Un texto más largo se recorta con la marca `…[cortado]`.
|
|
188
188
|
|
|
@@ -198,8 +198,11 @@ Los topes son en caracteres. Un texto más largo se recorta con la marca `…[co
|
|
|
198
198
|
| `405` | — | Método que no es `POST` ni `OPTIONS` |
|
|
199
199
|
|
|
200
200
|
Cómo se agrupa (lo hace finance, no el emisor): mismo origen, tipo, mensaje (sin ids, hashes ni
|
|
201
|
-
números) y primer frame del stack
|
|
202
|
-
|
|
201
|
+
números) y primer frame del stack son el mismo problema. Del frame se sacan el hash del chunk de
|
|
202
|
+
Vite (también los que traen `-` o `_`), la línea y la columna, y en un chunk hasheado también el
|
|
203
|
+
nombre de función, que el minificador cambia en cada build: el mismo error no se parte en un
|
|
204
|
+
problema por deploy. Sin stack, cuentan sólo origen, tipo y mensaje. `huella_manual` reemplaza ese
|
|
205
|
+
cálculo.
|
|
203
206
|
|
|
204
207
|
### Con `curl`
|
|
205
208
|
|
|
@@ -213,48 +216,83 @@ curl -X POST "https://qoyskdjcjyjxcnhruhni.supabase.co/functions/v1/monitor-inge
|
|
|
213
216
|
### Desde un cron de la base (`net.http_post`)
|
|
214
217
|
|
|
215
218
|
El patrón es envolver cada job en una función que ataja el error y avisa, con la clave en el vault
|
|
216
|
-
del cliente
|
|
219
|
+
del cliente. Es la migración que corre en producción en Mercado Fratelli
|
|
220
|
+
(`20260929130000_monitor_run_job.sql`). Sólo cambian el ejemplo del cron, la referencia al plan de
|
|
221
|
+
Fratelli en el `comment` y un comentario sobre `PT4xx`. Tres decisiones que no son obvias:
|
|
222
|
+
|
|
223
|
+
- **No re-lanza la excepción.** `net.http_post` encola el pedido en una tabla y un worker lo manda
|
|
224
|
+
después: si el job re-lanzara, la transacción se revertiría y el aviso con ella. Deja el error en
|
|
225
|
+
los logs de Postgres con `raise warning`. Consecuencia: en `cron.job_run_details` el job figura
|
|
226
|
+
`succeeded` aunque haya fallado. La falla se ve en el Monitor.
|
|
227
|
+
- **Sin `monitor_clave` en el vault no manda nada.** La clave se carga a mano por ambiente, nunca en
|
|
228
|
+
la migración, que es la misma en dev y en prod. Así la migración se puede aplicar antes de que
|
|
229
|
+
exista la clave.
|
|
230
|
+
- **Sólo pg_cron la llama**: `revoke` para `public`, `anon` y `authenticated`. Es `security definer`.
|
|
217
231
|
|
|
218
232
|
```sql
|
|
233
|
+
create extension if not exists pg_net with schema extensions;
|
|
234
|
+
|
|
219
235
|
create or replace function app.run_job(p_fn text)
|
|
220
236
|
returns void
|
|
221
237
|
language plpgsql
|
|
222
238
|
security definer
|
|
239
|
+
-- Vacío: todo va calificado. Las funciones de los crons fijan su propio `search_path`.
|
|
223
240
|
set search_path = ''
|
|
224
241
|
as $$
|
|
225
242
|
declare
|
|
226
243
|
v_estado text;
|
|
227
244
|
v_mensaje text;
|
|
245
|
+
v_clave text;
|
|
228
246
|
begin
|
|
247
|
+
-- El cast a `regproc` valida que la función exista y la escribe bien citada. Adentro del
|
|
248
|
+
-- bloque a propósito: un nombre mal escrito en el cron también es un cron que falla.
|
|
229
249
|
execute format('select %s()', p_fn::regproc);
|
|
230
250
|
exception when others then
|
|
231
251
|
get stacked diagnostics v_estado = returned_sqlstate, v_mensaje = message_text;
|
|
252
|
+
|
|
232
253
|
-- PT4xx es un rechazo de negocio, no un incidente.
|
|
233
254
|
if v_estado not like 'PT4%' then
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
'
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
255
|
+
select decrypted_secret into v_clave
|
|
256
|
+
from vault.decrypted_secrets
|
|
257
|
+
where name = 'monitor_clave';
|
|
258
|
+
|
|
259
|
+
if v_clave is not null then
|
|
260
|
+
perform net.http_post(
|
|
261
|
+
url := 'https://qoyskdjcjyjxcnhruhni.supabase.co/functions/v1/monitor-ingesta',
|
|
262
|
+
headers := jsonb_build_object(
|
|
263
|
+
'Authorization', 'Bearer ' || v_clave,
|
|
264
|
+
'Content-Type', 'application/json'
|
|
265
|
+
),
|
|
266
|
+
body := jsonb_build_object('eventos', jsonb_build_array(jsonb_build_object(
|
|
267
|
+
'id_externo', gen_random_uuid(),
|
|
268
|
+
'origen', 'db',
|
|
269
|
+
'mensaje', v_mensaje,
|
|
270
|
+
'codigo', v_estado,
|
|
271
|
+
'ubicacion', 'cron ' || p_fn,
|
|
272
|
+
-- Un problema por cron, sea cual sea el mensaje de cada falla.
|
|
273
|
+
'huella_manual', 'cron:' || p_fn
|
|
274
|
+
)))
|
|
275
|
+
);
|
|
276
|
+
end if;
|
|
249
277
|
end if;
|
|
250
|
-
|
|
251
|
-
|
|
278
|
+
|
|
279
|
+
raise warning 'app.run_job(%): % (SQLSTATE %)', p_fn, v_mensaje, v_estado;
|
|
252
280
|
end;
|
|
253
281
|
$$;
|
|
254
282
|
|
|
255
|
-
|
|
283
|
+
comment on function app.run_job(text) is
|
|
284
|
+
'Corre la función de un cron y, si falla con algo que no es PT4xx, avisa al Monitor de finance (net.http_post con monitor_clave del vault). No re-lanza: el aviso se revertiría con la transacción.';
|
|
285
|
+
|
|
286
|
+
-- Sólo pg_cron (corre como postgres) la llama: ni un usuario ni la API.
|
|
287
|
+
revoke all on function app.run_job(text) from public, anon, authenticated;
|
|
288
|
+
|
|
289
|
+
-- Cada cron pasa por el wrapper. `cron.schedule` con un nombre que ya existe actualiza ese job.
|
|
290
|
+
select cron.schedule('<nombre-del-job>', '0 3 * * *', $$select app.run_job('app.<funcion_del_cron>');$$);
|
|
256
291
|
```
|
|
257
292
|
|
|
293
|
+
El esquema `app` tiene que existir y no estar expuesto por la API. La clave va al vault de cada
|
|
294
|
+
ambiente: `select vault.create_secret('<clave>', 'monitor_clave');`.
|
|
295
|
+
|
|
258
296
|
## Qué NO hace
|
|
259
297
|
|
|
260
298
|
- **Sin sourcemaps**: el stack llega minificado; se agrupa igual por frame normalizado.
|
package/package.json
CHANGED