Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Registros y observabilidad

Cada evento que emite ZeroClaw pasa por un único crate: zeroclaw-log. El crate gestiona el esquema JSONL en disco, el flujo de difusión dentro del proceso que lee el panel de control, el puente opcional al Observer tipado (Prometheus / OTel) y las macros (record!, scope!, spawn!) que utilizan los subsistemas.

Esta página cubre lo que un operador necesita: la configuración, dónde se encuentra el registro, la estructura de los eventos y cómo consultarlos.

Configuración ([observability])

Valores predeterminados: log_persistence = "rolling", log_persistence_max_entries = 200, log_tool_io = "redacted", log_tool_io_truncate_bytes = 40960, log_llm_request_payload = "off". Una instalación nueva genera un JSONL circular de 200 eventos en ~/.zeroclaw/data/state/runtime-trace.jsonl, y la página de Logs del dashboard funciona sin configuración adicional.

log_persistence = "none" deshabilita por completo la persistencia, pero no bloquea el flujo de difusión utilizado por el SSE del panel. El puente Observer tipado opcional también es independiente de la persistencia, pero recibe eventos de registro canónicos solo cuando se vincula explícitamente; el arranque actual de producción no instala ese vínculo.

La persistencia se realiza con el mejor esfuerzo, no constituye una garantía de auditoría transaccional. El puente Observer, cuando está enlazado, y la entrega mediante difusión tienen lugar antes de que el evento se ofrezca a una cola limitada de escritura en segundo plano. Una cola llena o un fallo de escritura del trabajador puede hacer que un evento no llegue a JSONL. La sincronización periódica cubre el archivo activo actual; la rotación diaria antes de la primera adición de un nuevo día UTC y la rotación por tamaño después de una adición que supera el umbral pueden cambiar el nombre del archivo activo sin sincronizarlo primero, por lo que la cadencia no limita la durabilidad de un archivo recién rotado. Consulta Arquitectura de registro para conocer los contratos de entrega independientes.

Rotación de archivos (log_persistence = "rotating")

rotating no aplica ningún recorte por cantidad de entradas a los eventos aceptados por el escritor en segundo plano, al igual que full, pero ZeroClaw administra el archivo activo: lo rota a un archivo histórico con marca de tiempo al alcanzar un límite de tamaño o diario, y los archivos históricos antiguos se depuran según su cantidad y antigüedad. Esto difiere de rolling, que elimina las entradas antiguas del archivo activo; los eventos rotados se conservan en archivos históricos para diagnósticos posteriores.

ClavePredeterminadoEfecto
log_persistence_max_bytes0Gira una vez que una adición deje el archivo activo en o por encima de esta cantidad de bytes. 0 deshabilita la rotación por tamaño.
log_persistence_rotate_dailytrueAntes del primer evento de un nuevo día UTC, archiva un archivo cuya última escritura ocurrió en un día anterior.
log_persistence_retention_max_files7Conserve como máximo esta cantidad de archivos; tras una rotación, se eliminan los más antiguos que superen el límite. 0 conserva todos.
log_persistence_retention_max_age_days0Elimina los archivos archivados anteriores a este número de días después de una rotación. 0 deshabilita la limpieza basada en la antigüedad.

Los archivos archivados se sitúan junto al archivo activo y conservan su extensión, con una marca temporal UTC ordenable insertada antes de esa extensión. Por ejemplo, runtime-trace.jsonl rota a runtime-trace.20260624-031500.jsonl. El panel y el extremo /api/logs leen solo el archivo activo, por lo que los archivos archivados son un registro en disco para inspección sin conexión, no una superficie de consulta en vivo.

La rotación diaria se basa en el calendario UTC, por lo que su límite puede no coincidir con la medianoche local en otras zonas horarias. Estas claves se ignoran a menos que log_persistence = "rotating", y los modos none, rolling y full no cambian.

Atributos de span de GenAI (observability-otel)

llm.response los spans llevan los atributos de contenido de mensajes GenAI de OTel gen_ai.input.messages, gen_ai.output.messages y gen_ai.system_instructions (codificados como cadena JSON), que rellenan los paneles de Entrada/Salida/Sistema en Langfuse/Tempo.

Privacidad y coste. El contenido capturado se sanea con esfuerzo razonable: los datos de imágenes en línea se omiten y las formas conocidas de credenciales (key=value, bearer y prefijos de estilo sk-/ghp_/xoxb--style) se redactan. Esto NO garantiza la eliminación de todos los secretos ni de la PII. Prefiera un backend de trazas con control de acceso si las conversaciones pueden ser sensibles. El coste de captura es O(tamaño_del_prompt) por iteración del bucle del agente (el historial en crecimiento se vuelve a analizar en cada ronda), y el texto completo crece proporcionalmente al payload por span. En backends por byte, aplique truncamiento del lado del exportador en lugar de descartar los atributos.

Captura de contenido de OTel

La captura de contenido de OTel es independiente de la captura basada en logs (log_tool_io, log_llm_request_payload). Controla qué contenido se emite como atributos de span de OpenTelemetry.

Contenido de GenAI

Controla gen_ai.system_instructions, gen_ai.input.messages y gen_ai.output.messages en spans de OTel.

[observability]
otel_genai_content = "off"            # off | redacted | full
otel_genai_content_max_chars = 1000  # límite de truncamiento por campo
  • off (predeterminado): Sin atributos de contenido, solo metadatos.
  • redacted: El contenido se analiza para detectar filtraciones y se trunca en max_chars por campo.
  • full: El contenido se analiza en busca de filtraciones, pero no se trunca.

Entrada/salida de herramientas

Controla gen_ai.tool.arguments, input.value, gen_ai.tool.result y output.value en spans de OTel.

[observability]
otel_tool_io = "off"                  # off | redacted | full
otel_tool_io_max_chars = 1000        # per-field truncation limit
  • off (predeterminado): Sin atributos de contenido, solo nombre de la herramienta + resultado.
  • redacted: El contenido se analiza para detectar filtraciones y se trunca en max_chars por campo.
  • full: El contenido se analiza en busca de filtraciones, pero no se trunca.

Notas de comportamiento

  • Establecer *_max_chars = 0 es equivalente a off para esa política.
  • El contenido siempre se limpia (patrones de credenciales + patrones de secretos) antes de truncarse.
  • La truncación preserva la estructura de JSON para los argumentos de herramientas (las cadenas hoja se truncan).
  • Los campos truncados reciben un marcador …[truncated {n} of {total} chars]. El marcador es metadatos y no cuenta para max_chars: el contenido conservado tiene exactamente max_chars caracteres, con el marcador añadido encima.
  • El valor predeterminado off es un cambio orientado primero a la privacidad respecto al comportamiento anterior (habilitado mediante feature gate, pero siempre activo cuando se habilitaba).
  • La política de contenido está vinculada a la instancia observer/config, no al proceso. No existe una política de contenido OTel global al proceso: cada OtelObserver deriva una configuración de contenido inmutable de ObservabilityConfig en la construcción y la consulta en el límite de exportación OTel. Varios observadores en el mismo proceso mantienen políticas independientes: un observador posterior no puede sobrescribir ni silenciar la configuración de privacidad de uno anterior (sin last-writer-wins, sin deriva entre observadores).

Spans de memoria anidada y RAG por turnos (observability-otel)

Los spans de memory.recall, memory.store y rag.retrieve se anidan bajo el span de turno gen_ai.agent.invoke siempre que la operación se ejecuta dentro de un turno de agente atribuido, de modo que un turno completo (recuperación de memoria, almacenamiento de autoguardado, llamadas al LLM, llamadas a herramientas) se representa como una sola traza en Langfuse/Tempo. Los tres eventos incluyen la misma tripleta channel / agent_alias / turn_id que los eventos de LLM y de herramientas, expuesta como los atributos de span zeroclaw.channel, gen_ai.agent.name y zeroclaw.turn_id.

Las operaciones de memoria fuera de un turno correlacionado siguen produciendo spans raíz: el almacén de memoria REST de la puerta de enlace y la recuperación de hardware-RAG de process_message, que se ejecuta antes de que se abra el delimitador del turno y, por tanto, permanece como un span raíz que contiene el atributo coincidente zeroclaw.turn_id (el anidamiento completo de ese span se sigue en #8844). Un turn_id que ya no coincide con un turno activo también se degrada a un span raíz en lugar de adivinar un elemento principal.

Captura de la carga útil de la solicitud LLM (log_llm_request_payload)

log_llm_request_payload controla si el evento llm_request registra el prompt saliente y la conversación además de su messages_count. Está desactivado de forma predeterminada y es una superficie sensible para la privacidad: cuando está habilitado, ZeroClaw persiste el prompt del sistema completo junto con todo el historial de la conversación en cada turno.

Valor¿Qué se captura?
off (predeterminado)Solo messages_count. No se registra el contenido del mensaje; comportamiento existente.
redactedHistorial completo de mensajes (rol + contenido), examinado en busca de credenciales con el mismo paso scrub_credentials usado para raw_response y la E/S de herramientas, y luego truncado en log_tool_io_truncate_bytes. El truncamiento se indica con request_messages_truncated y request_messages_original_bytes.
fullEl mismo saneamiento de credenciales que redacted, pero sin truncar (fidelidad de reproducción, reflejando raw_response).

Tanto redacted como full siempre ejecutan el saneamiento de credenciales; la única diferencia entre ellos es la truncación. La captura reutiliza el límite existente log_tool_io_truncate_bytes en lugar de introducir un segundo límite. Establece o deja log_llm_request_payload = "off" para desactivar la captura al instante, sin redeploy.

Formato en disco

JSONL: un evento por línea, UTF-8, permisos 0o600 en Unix. La ruta crítica no bloquea: record_event entrega el evento serializado a un hilo de fondo dedicado (zeroclaw-log-writer) a través de un canal acotado y devuelve inmediatamente. El worker llama a sync_all con una cadencia periódica: cada 100 escrituras o cada 1 segundo de tiempo de pared, lo que ocurra primero, además de un sync_all final cuando el canal se cierra en un apagado normal. Esto intercambia durabilidad por evento (el comportamiento síncrono anterior) por latencia de escritura acotada: un fallo del proceso puede perder hasta un intervalo de sincronización de escrituras pendientes. Si el worker se retrasa, record_event descarta el evento con un tracing::warn! en lugar de bloquear el runtime asíncrono. Los workers son singletons por proceso; deshabilitar y volver a habilitar la persistencia mediante init_from_config descarta el worker anterior (el cierre del canal provoca su sincronización final y la salida del hilo) y crea uno nuevo.

La forma de la línea refleja zeroclaw_log::event::LogEvent. Claves de nivel superior:

ClaveTipoNotas
idCadena UUID v4Id de evento persistente.
@timestampRFC 3339 + ms, UTCOrdenable lexicográficamente; el lector ordena según este campo.
severity_numberu8OTel: 1 TRACE, 5 DEBUG, 9 INFO, 13 WARN, 17 ERROR.
severity_textcadenaEtiqueta de bucket para severity_number.
event.categorycadenaagent, channel, cron, memory, tool, provider, session, system o internal.
event.actioncadenaIdentificador estable (llm_request, channel_message_inbound, …).
event.outcomestring | omittedsuccess, failure, unknown (se omite cuando es unknown).
service.namecadenaConstant "zeroclaw".
service.versioncadenaVersión del crate del daemon en ejecución.
trace_idcadena hexadecimal | omitidoCorrelación por turno. Un turno de agente = un trace_id.
span_idcadena hexadecimal | omitidoSubintervalo dentro de un turno.
zeroclaw.*mapa de cadenas planoAtribución vinculada a alias (ver más abajo).
messagestring | omittedCuerpo de línea legible por humanos.
attributesobject | omittedCarga útil por acción de formato libre.
schema_versionu8Actualmente 2. Las filas v1 se migran in situ al iniciar.

Atribución zeroclaw.*

La fuente de verdad en Rust es ATTRIBUTION_FIELDS + COMPOSITE_PREFIXES en crates/zeroclaw-log/src/event.rs. La respuesta de /api/logs incluye la lista canónica como attribution_keys; obtenla en lugar de codificarla manualmente.

Los campos simples (ATTRIBUTION_FIELDS) contienen una sola cadena cada uno. Los prefijos compuestos obtienen tres claves: <prefix>, <prefix>_type, <prefix>_alias (p. ej. channel = "discord.glados", channel_type = "discord", channel_alias = "glados"). Los filtros pueden coincidir de forma general o precisa.

Cuando una llamada de tracing establece un campo de prefijo compuesto con un tipo simple (sin .), solo se rellena la ranura _type, de modo que una llamada tracing::*!(model_provider = name, …) dentro de un span que ya contiene el compuesto completo <type>.<alias> no lo sobrescriba durante la fusión de hoja a raíz.

Consulta

La página de Logs del panel es la superficie principal. Debajo:

GET /api/logs

Filtros de nivel superior (parámetros de consulta): since_ts, until_ts, until_line_offset, action, category, outcome, severity_min, trace_id, q (subcadena en message + attributes), hide_internal (elimina event.category = "internal"), limit. El campo heredado until_id sigue disponible para compatibilidad con cursores de marca de tiempo/ID.

Cada otro ?<key>=<value> se trata como un filtro de igualdad por atribución; el gateway valida la clave contra is_attribution_field y rechaza las desconocidas con 400. La respuesta incluye attribution_keys: string[], de modo que quienes hacen las llamadas no tienen que adivinar.

Ejemplos:

sh

# Todos los eventos WARN+ desde que se inició el daemon.
curl "$ZEROCLAW_GATEWAY/api/logs?severity_min=13"

# Eventos de un agente específico:
curl "$ZEROCLAW_GATEWAY/api/logs?agent_alias=glados"

# Tráfico de Discord para un bot:
curl "$ZEROCLAW_GATEWAY/api/logs?channel=discord.glados"

# Un único turno del agente:
curl "$ZEROCLAW_GATEWAY/api/logs?trace_id=<value-from-a-prior-event>"

La paginación de registros recorre hacia atrás con un cursor de desplazamiento en bytes. Mientras at_end sea false, vuelva a pasar un next_cursor_line_offset no nulo como until_line_offset con los mismos filtros que no son de cursor para cargar eventos más antiguos sin volver a leer bytes más recientes. Reinicie desde la página más reciente después de cambiar los filtros. Trate at_end: true como la señal para dejar de solicitar páginas más antiguas para ese recorrido de paginación. La respuesta heredada next_cursor: [timestamp, id] | null se mantiene por compatibilidad; usar su par de timestamp/ID como until_ts y until_id para la paginación está obsoleto porque el desempate lexicográfico de ID puede omitir silenciosamente eventos con la misma marca de tiempo.

until_line_offset es una posición del archivo activo actual, no un punto de control persistente de eventos. Las adiciones simples lo conservan, pero el recorte rotativo, la rotación de archivos, la migración durante el inicio y un cambio en la ruta configurada reemplazan los bytes o el archivo activo al que hace referencia. Reinicie desde la página más reciente después de esos límites en lugar de reutilizar un desplazamiento anterior. /api/logs solo lee el archivo activo; inspeccione directamente los archivos archivados con marca de tiempo cuando se necesite el historial rotado más antiguo.

La respuesta de /api/status incluye daemon_started_at: string (RFC 3339), de modo que un panel puede usar de forma predeterminada “desde el inicio del daemon” sin necesidad de una solicitud adicional.

Visores de registros externos

El esquema JSONL es un híbrido de OTel-logs + ECS: @timestamp, severity_number + severity_text, event.{category,action,outcome}, service.{name,version}, attributes, además del espacio de nombres de proveedor zeroclaw.*. La mayoría de los visores de registros lo ingieren con poca o ninguna transformación. Reemplaza <install> con la ruta absoluta a tu directorio de instalación en los ejemplos siguientes (normalmente ~/.zeroclaw expandido).

Grafana Loki

Promtail eleva las etiquetas agent_alias, channel y severity_text para que se puedan filtrar en Grafana:

scrape_configs:
  - nombre_del_trabajo: zeroclaw
    static_configs:
      - objetivos: [localhost]
        etiquetas:
          trabajo: zeroclaw
          __path__: <install>/data/state/runtime-trace.jsonl
    pipeline_stages:
      - json:
          expresiones:
            agente: zeroclaw.agent_alias
            canal: zeroclaw.channel
            nivel: severity_text
      - etiquetas:
          agente:
          canal:
          nivel:
      - marca de tiempo:
          fuente: '@timestamp'
          formato: RFC3339

OpenTelemetry Collector

El receptor filelog asigna el esquema directamente. Exporta a cualquier destino de OTel posteriormente (Tempo, Honeycomb, Datadog, etc.):

receptores:
  filelog/zeroclaw:
    include: [<install>/data/state/runtime-trace.jsonl]
    operadores:
      - tipo: json_parser
        marca de tiempo:
          parse_from: attributes["@timestamp"]
          layout: '%Y-%m-%dT%H:%M:%S.%LZ'
        gravedad:
          parse_from: attributes.severity_number

Kibana / Elastic

La ingesta funciona tal cual. Las canalizaciones de ECS estrictas esperan log.level en lugar de severity_text. Una canalización de ingesta de Filebeat que renombra severity_text a log.level (y severity_number a log.syslog.severity.code) cubre la diferencia. @timestamp y event.{category,action,outcome} ya están en sus posiciones canónicas.

Vector / Fluent Bit

Ambos hacen tail de JSONL con una etapa de análisis JSON; no se necesitan transformaciones de esquema antes de enviarlos a cualquier backend.

Formato de terminal

El formateador de stderr del daemon antepone a cada línea la identidad más cercana vinculada al alias que la contiene:

  • agente context → [<agent_alias>]
  • contexto solo de canal (listener de canal, sin agente aún) → [<channel_composite>] (p. ej. [discord.glados])
  • otherwise → [system]

La cadena de spans es la siguiente: channel_listener{channel=discord.glados}: …. Los campos del span se muestran en línea.

Migración de esquema

Al iniciar, si log_persistence está habilitado y el archivo existe, el escritor procesa en streaming todas las filas con esquema 1 mediante una migración in situ a esquema 2 antes de la primera escritura. Streaming puro, limitado por la asignación de memoria de una sola línea independientemente del tamaño del archivo. El archivo migrado se renombra atómicamente a su ubicación final. Los archivos que ya están en v2 se dejan intactos.

Si la migración falla, el daemon registra un warn y continúa escribiendo anexos v2; las filas v1 antiguas siguen siendo legibles por las herramientas que aún entienden v1, pero no pasarán el deserializador del lector v2.

¿Qué es internal?

event.category = "internal" es el grupo para el ruido de operaciones que un operador no necesita en el panel de forma predeterminada: marcas de heartbeat, difusiones inactivas, reintentos de sincronización con pérdidas y similares. El interruptor “Hide internal” del panel (activado de forma predeterminada) los filtra.

Úsalo cuando tengas un evento de alta frecuencia cuya presencia sea relevante para el análisis forense pero cuya ausencia sea el estado normal. No lo uses como regulador de volumen para errores genuinos.

Archivos de interés

  • crates/zeroclaw-log/src/event.rs: la estructura canónica de LogEvent.
  • crates/zeroclaw-log/src/layer.rs: la Layer de tracing-subscriber que captura cada llamada a tracing::* y alimenta el pipeline.
  • crates/zeroclaw-log/src/macro.rs: record!, scope!, spawn!.
  • crates/zeroclaw-log/src/writer.rs: añadir, recorte cíclico y rotación de archivos archivados.
  • crates/zeroclaw-log/src/reader.rs: lector de /api/logs.
  • crates/zeroclaw-log/src/config.rs: StoragePolicy, ToolIoPolicy, ResolvedPolicy.
  • crates/zeroclaw-log/src/migrate.rs: migración por streaming de schema-1 → schema-2.
  • crates/zeroclaw-log/src/observer_bridge.rs: proyección tipada de Observer para consumidores de Prometheus / OTel.
  • crates/zeroclaw-gateway/src/api_logs.rs: el adaptador HTTP.

Verifica la fuente antes de confiar en el texto de esta página.