---
name: parse_nats_monitor
kind: function
lang: go
domain: infra
version: "1.0.0"
purity: impure
signature: "func ParseNatsMonitor(node string, varz, connz, jsz []byte) ([]PromSample, error)"
description: "Convierte las respuestas JSON del endpoint de monitoring HTTP embebido de un nats-server (puerto 8222, loopback) en una serie de PromSample lista para empujar a VictoriaMetrics. Hermana de ParseUnibusHealth pero para las métricas server-level de NATS/JetStream: msgs/s, bytes, conexiones, slow consumers, memoria RSS, start epoch (proxy de reinicios), streams/messages/bytes/memory/storage de JetStream, y por stream nats_stream_messages/bytes, nats_jetstream_raft_leader y kv_bucket_msgs para los buckets KV_. Adjunta labels node e instance a cada serie. varz es el core (error si no parsea); connz y jsz son best-effort (se omiten sin abortar). La consume el unibus_exporter de fleet_monitoring como scraper local por nodo."
tags: [prometheus, metrics, nats, jetstream, monitoring, varz, connz, jsz, kv, raft, fleet-metrics, infra]
uses_functions: []
uses_types: ["PromSample_go_infra"]
returns: []
returns_optional: true
error_type: "error_go_core"
imports: ["encoding/json", "fmt", "strings", "time"]
params:
  - name: node
    desc: "nombre lógico del nodo (p.ej. \"magnus\"); se adjunta como labels node e instance a CADA serie y se compara con cluster.leader de cada stream para nats_jetstream_raft_leader"
  - name: varz
    desc: "cuerpo JSON crudo de GET http://127.0.0.1:8222/varz; core de la función (in_msgs, out_msgs, in_bytes, out_bytes, connections, slow_consumers, subscriptions, mem, start). Si no parsea, la función devuelve error"
  - name: connz
    desc: "cuerpo JSON crudo de GET http://127.0.0.1:8222/connz; best-effort (num_connections). Si vacío o inválido, nats_connections cae a varz.connections sin abortar"
  - name: jsz
    desc: "cuerpo JSON crudo de GET http://127.0.0.1:8222/jsz?streams=1; best-effort (streams, messages, bytes, memory, storage y account_details[].stream_detail[]). Si vacío o inválido, se omiten sus series sin abortar. Necesita ?streams=1 para traer stream_detail"
output: "slice de PromSample con labels base {node,instance}: nats_msgs_in/out_total, nats_bytes_in/out_total, nats_connections, nats_slow_consumers, nats_mem_bytes, nats_subscriptions, nats_server_start_seconds (omitida si start no parsea), nats_jetstream_streams/messages/bytes/memory_bytes/storage_bytes; y por stream nats_stream_messages{stream}, nats_stream_bytes{stream}, nats_jetstream_raft_leader{stream} (1 si cluster.leader==node) y, para streams KV_, kv_bucket_msgs{bucket} con el prefijo KV_ recortado. Error solo si varz no es JSON válido."
tested: true
test_file_path: "functions/infra/parse_nats_monitor_test.go"
tests:
  - "TestParseNatsMonitorGolden"
  - "TestParseNatsMonitorEmptyJsz"
  - "TestParseNatsMonitorInvalidConnz"
  - "TestParseNatsMonitorInvalidVarz"
---

# parse_nats_monitor

Función de transformación (clasificada `impure` porque devuelve `error` al fallar el
unmarshal del core; no hace I/O ni red por sí misma) que traduce las métricas
server-level de un **nats-server** a series Prometheus. Es la hermana de
`parse_unibus_health_go_infra`: aquella lee el `/healthz` de `membershipd` (posture),
esta lee el monitoring embebido de NATS (puerto 8222) para las métricas profundas que
`/healthz` no expone: msgs/s, conexiones, RAFT leader por stream, memoria, KV buckets.

Pertenece al grupo de capacidad `fleet-metrics`: se compone con
`format_prom_exposition_go_infra` (serializar) y `push_prom_remote_go_infra` (empujar a
VictoriaMetrics). La consume el `unibus_exporter` de `fleet_monitoring` en modo scraper
local por nodo, que hace los tres GET y le pasa los cuerpos crudos.

## Ejemplo

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"time"

	"fn-registry/functions/infra"
)

func get(url string) []byte {
	resp, err := http.Get(url)
	if err != nil {
		return nil // best-effort: connz/jsz pueden faltar
	}
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	return b
}

func main() {
	base := "http://127.0.0.1:8222"
	varz := get(base + "/varz")
	connz := get(base + "/connz")
	jsz := get(base + "/jsz?streams=1")

	samples, err := infra.ParseNatsMonitor("magnus", varz, connz, jsz)
	if err != nil {
		panic(err) // varz es el core: sin él no hay métricas
	}
	fmt.Print(infra.FormatPromExposition(samples, time.Now().UnixMilli()))
	// nats_msgs_in_total{instance="magnus",node="magnus"} 17 ...
	// kv_bucket_msgs{bucket="UNIBUS_users",instance="magnus",node="magnus"} 2 ...
	// nats_jetstream_raft_leader{instance="magnus",node="magnus",stream="KV_UNIBUS_users"} 1 ...
}
```

## Cuando usarla

Úsala dentro de un exporter que monitoriza un nats-server con el monitoring HTTP
embebido activado (`http: 127.0.0.1:8222` en la config de NATS): tras hacer
`GET /varz`, `GET /connz` y `GET /jsz?streams=1` contra loopback, pasa los tres cuerpos
crudos a esta función para obtener todas las series server-level del nodo. Llámala como
scraper local por nodo (cada nodo expone su 8222 solo en loopback), no centralizado.

## Gotchas

- **Impura por contrato**: solo devuelve `error` si `varz` no es JSON válido (es el core).
  `connz` y `jsz` son **best-effort**: si vienen vacíos o no parsean, sus series se omiten
  sin abortar. Esto hace al scraper resistente a que un endpoint falle de forma puntual.
- **Monitoring loopback-only sin auth**: el puerto 8222 de NATS no tiene autenticación; por
  eso debe bindearse a `127.0.0.1` y scrapearse localmente en cada nodo, nunca exponerse a
  la red. El push agregado a VictoriaMetrics lo hace el exporter, no esta función.
- **`/jsz` necesita `?streams=1`** para traer `account_details[].stream_detail[]`. Sin ese
  parámetro el cuerpo trae los totales pero no el detalle por stream, y entonces no salen
  `nats_stream_*`, `nats_jetstream_raft_leader` ni `kv_bucket_msgs`.
- **`nats_connections`**: prefiere `connz.num_connections`; si `connz` no parsea, cae a
  `varz.connections` para no perder la serie.
- **RAFT leader en standalone**: en un nats-server sin clúster, el objeto `cluster` puede
  faltar o `leader` venir vacío; en ese caso `nats_jetstream_raft_leader` sale 0 salvo que
  `cluster.leader == node`. Es esperado: en standalone no hay quorum RAFT real.
- **`kv_bucket_msgs`** solo se emite para streams cuyo nombre empieza por `KV_`, recortando
  el prefijo (stream `KV_UNIBUS_users` → bucket `UNIBUS_users`).
- **`nats_server_start_seconds`** es el epoch Unix del campo `start` (RFC3339): sirve como
  proxy de reinicios (un cambio de valor = el server reinició). Si el campo no parsea como
  fecha válida, la serie se omite en lugar de abortar.
