ADR 0005 — Observabilidad desde el núcleo
- Estado: aceptada
- Fecha: 2026-09-25
Contexto
Con cientos o miles de tenants, "algo falló" no sirve: hay que saber en qué tenant, en qué operación y por qué, y poder ver los errores de un tenant concreto. Esto no puede esperar a la fase de OpenTelemetry: tiene que ser parte del núcleo desde el inicio.
Decisión
Logs estructurados con campos fijos. Toda línea que escribe el paquete usa los mismos nombres:
campo contenido tenantIdid del tenant o null(central). Siempre presente.operationtenants.create,domains.add,http.request,events.listener,tenancy.resolve,tenancy.runForEach.item...outcomesuccessoerrordurationMsduración de la operación o de la petición codecódigo estable del error ( TENANCY_*,ECONNREFUSED, nombre de la clase)errorIdid del error en el ErrorTracker(para cruzar log y panel)requestId,method,path,host,statusCodeen peticiones HTTP errel error completo con stack Niveles: escrituras correctas en
info, lecturas endebug, errores del cliente (4xx) enwarn, errores del servidor (5xx) enerror.tenantIdautomático: el logger del paquete (ContextualLogger) toma el tenant deAsyncLocalStorage.tenancy.observability.loggerestá disponible para la aplicación.Un error se asigna al tenant del que habla: si un error del paquete trae
details.tenantId(tenant suspendido, no encontrado...), se registra bajo ese tenant aunque la petición no haya entrado a su contexto.Registro de errores por tenant (
ErrorTracker): puerto con implementación en memoria (ring buffer por tenant).tenancy.observability.errors({ tenantId })ysummary()los exponen para el CLI, el panel admin y health checks.Ningún error se pierde en silencio: listeners
async, reverts de bootstrappers, errores de handlers HTTP y fallos derunForEachpasan por el mismoObserver.El logger es compatible con pino (
logger: pino()).
Consecuencias
- Filtrar por tenant es un
grep '"tenantId":"bolivar"'o una consulta en Loki/Datadog/ELK. - En varias instancias, el
ErrorTrackeren memoria es por proceso; en fases siguientes se agrega un tracker persistente (base central / Redis) detrás del mismo puerto. - Los spans de OpenTelemetry (Fase 8) usarán los mismos nombres de
operation.