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 en proceso que lee el panel, el puente tipado opcional Observer, el puente nativo de exportación de logs OTLP, y las macros (record!, scope!, spawn!) que llaman 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.

Captura del daemon de escritorio

El supervisor de Desktop captura la salida estándar (stdout) y el error estándar (stderr) del daemon en un único archivo de tamaño limitado: <config-dir>/logs/zeroclaw-desktop-daemon.log. <config-dir> sigue la precedencia de resolución canónica de la configuración: ZEROCLAW_CONFIG_DIR, luego ZEROCLAW_DATA_DIR, después el ZEROCLAW_WORKSPACE obsoleto y, por último, la resolución de Homebrew o la predeterminada. El archivo tiene un límite de 8 MiB; cuando la salida supera este límite, los bytes más antiguos se compactan y se conserva la cola más reciente.

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" desactiva la persistencia por completo, pero no controla el flujo de difusión usado por el SSE del panel ni por la exportación OTLP nativa. El puente tipado opcional Observer también es independiente de la persistencia, pero solo recibe eventos de log canónicos cuando se vincula explícitamente; el arranque de producción actual no instala ese vínculo.

La persistencia se basa en el mejor esfuerzo y no ofrece una garantía de auditoría transaccional. El puente de Observer, la cola OTLP nativa y la entrega por difusión ocurren antes de que el evento se ofrezca a una cola acotada del escritor en segundo plano. Una cola llena o un fallo de escritura del worker puede dejar un evento fuera del JSONL. La sincronización periódica cubre el archivo activo actual. La rotación diaria, antes de la primera escritura de un nuevo día UTC, y la rotación por tamaño, después de una escritura que supera el umbral, pueden renombrar el archivo activo sin sincronizarlo antes, por lo que la cadencia no limita la durabilidad de un archivo recién rotado. Consulte Arquitectura de logging para conocer los contratos de entrega independientes.

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

rotating conserva todos los eventos rotando el archivo activo en lugar de recortarlo. El archivo activo se renombra a un archivo de archivo con marca de tiempo cuando se activa un disparador por tamaño, por cambio de día o por número de entradas. Los archivos antiguos se eliminan según su cantidad y antigüedad después de cada rotación.

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_max_entries_per_segment0Rota cuando el número de líneas no vacías del segmento alcance este límite. En estado estable, cada archivo contiene exactamente esta cantidad de entradas. Al activarlo por primera vez en un registro existente, el archivo se archiva completo (una transición por encima del límite); el estado estable se reanuda en la siguiente rotación. 0 desactiva la rotación por número de entradas.
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 rotados se ubican junto al archivo activo y conservan su extensión, con una marca de tiempo UTC ordenable insertada antes de dicha extensión. Por ejemplo, runtime-trace.jsonl se rota a runtime-trace.0000000001-20260624-031500.jsonl. El prefijo de secuencia (0000000001) se escribe en el momento de la rotación y determina el orden en el lado del lector. Un archivo sidecar de mejor esfuerzo conserva la marca de máximo histórico (high-water mark) entre reinicios. Si ese archivo sidecar no puede escribirse, la política de retención elimina todos los archivos rotados numerados y el daemon se reinicia, una secuencia puede reutilizarse; en consecuencia, un cursor pendiente puede quedar vinculado a un historial más reciente. Los archivos escritos antes de que existiera la numeración de secuencia conservan su formato anterior (runtime-trace.20260624-031500.jsonl) y se ordenan antes que todos los archivos numerados.

El panel y GET /api/logs ahora leen el archivo activo y todos los archivos archivados conservados como un único flujo lógico de eventos, combinándolos empezando por el archivo archivado más antiguo y devolviendo los eventos del más reciente al más antiguo. La API expone un cursor consciente de segmentos (next_segment_cursor) junto con el cursor de desplazamiento en bytes existente (next_cursor_line_offset); pasa ?until_segment_cursor= en las solicitudes posteriores para paginar a través de los límites de segmento. Los cursores de desplazamiento en bytes anteriores siguen siendo válidos y se interpretan como un desplazamiento dentro del archivo activo.

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.

Registros OTLP nativos (observability-otel)

La función opcional observability-otel exporta trazas, métricas y registros a través del SDK oficial de OpenTelemetry. Los registros usan OTLP/HTTP con codificación protobuf y se envían por lotes mediante POST a <otel_endpoint>/v1/logs; las trazas y las métricas mantienen sus endpoints estándar /v1/traces y /v1/metrics. La función sigue excluida de las compilaciones predeterminadas debido al mayor tamaño que aportan su SDK y su cliente HTTP.

[observability]
backend = "otel"
otel_endpoint = "http://localhost:4318"
otel_service_name = "zeroclaw"

# La inspección local/sin conexión sigue disponible al mismo tiempo.
log_persistence = "rolling"

# Encabezados opcionales del proveedor o del colector.
[observability.otel_headers]
Authorization = "Bearer <collector-token>"

Compila con --features observability-otel. El endpoint puede ser un OpenTelemetry Collector, Grafana Alloy o cualquier endpoint de un proveedor que acepte logs OTLP sobre HTTP/protobuf. Los valores de los encabezados son campos de configuración secretos y nunca aparecen en los registros exportados.

Cada LogEvent canónico se asigna al modelo de datos estable de OpenTelemetry Logs:

Campo de ZeroClawDestino de OpenTelemetry
@timestampLogRecord Timestamp; el tiempo de exportación pasa a ser ObservedTimestamp
severity_number, severity_textLogRecord, campos de severidad normalizados
messageLogRecord Body
configuración del nombre del servicio + versión del crateRecurso service.name, service.version
Identificadores de traza de 16 bytes y de span de 8 bytes válidosLogRecord TraceId, SpanId
event.*Atributos de LogRecord compatibles con ECS
_file, _linecode.file.path, code.line.number
campos de modelo/proveedor/tokengen_ai.* atributos semánticos
identidad de la herramientatool.name
Atribución zeroclaw.*atributos de LogRecord con espacio de nombres, incluido zeroclaw.sop_run_id

Las cadenas de correlación de la aplicación que no son identificadores válidos de traza o span de OTel siguen siendo buscables como atributos zeroclaw.trace_id / zeroclaw.span_id en lugar de convertirse en un contexto de traza inventado. Los valores JSON anidados se asignan a mapas y listas de AnyValue de OTel. Los atributos efímeros de solo difusión, incluidos los códigos de emparejamiento, nunca se presentan al exportador.

El exportador es aditivo. JSONL, SSE del panel, /api/logs, CLI y ZeroCode mantienen su esquema existente y siguen funcionando si el collector no está disponible. El reemplazo o el cierre del exportador fuerza el vaciado de la cola de lotes del SDK; la emisión en tiempo de ejecución solo encola y no realiza operaciones de E/S de red en el llamador.

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)No se registra contenido de mensajes. El evento sigue incluyendo messages_count junto con las huellas digitales del prefijo, siempre activas, descritas a continuación (system_chars, tools_count y, cuando estén presentes, system_sha256 y tools_sha256). Son resúmenes criptográficos, no contenido, pero un lector de trazas aún puede comprobar si son iguales.
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).

Cada evento llm_request también incluye huellas de prefijo de las entradas de la solicitud del runtime, independientemente de la política de carga útil: system_chars y tools_count siempre están presentes; system_sha256 (los primeros 16 caracteres hexadecimales de un SHA-256 calculado sobre los mensajes system iniciales contiguos, serializados como un array JSON de sus contenidos, presente solo cuando el primer mensaje tiene el rol system) y tools_sha256 (lo mismo, pero sobre las especificaciones de herramientas nativas serializadas como un array JSON, presente solo cuando se adjuntan especificaciones de herramientas, de modo que un conjunto de herramientas reordenado produce una huella distinta). Describen lo que el runtime entregó al adaptador del proveedor, una fila por solicitud lógica; las transformaciones del lado del proveedor (limpieza de esquemas, ubicación de cache-control, un prefijo de sistema de OAuth, fusión de mensajes de sistema en protocolos compatibles, un segundo mensaje de sistema inicial que el adaptador de Anthropic descarta, reintentos dentro del adaptador) quedan fuera de los bytes hasheados. Interprete un cambio como una pista de diagnóstico, no como un veredicto de caché: un system_sha256 modificado indica que cambió uno de los mensajes de sistema iniciales, o que se insertó o eliminó uno, por ejemplo tras cambiar un archivo del workspace incluido, cuando un hook previo a la llamada editó o añadió uno, o cuando el formato de las herramientas cambió entre el protocolo nativo y el de texto; un tools_sha256 modificado indica que cambió la serialización de las especificaciones de herramientas del runtime, lo que incluye campos de metadatos que no viajan por el protocolo. En el modo de protocolo de texto, las instrucciones de las herramientas están dentro del texto del sistema, por lo que tools_count es 0 y tools_sha256 está ausente, mientras que un cambio en el conjunto de herramientas aparece en system_sha256. Confirme un acierto o fallo real de caché a partir del uso de caché que informa el proveedor. Las huellas son recuentos y hashes truncados sin sal, no contenido: revelan si una entrada cambió y permiten comprobar la igualdad de un texto candidato conocido, pero no almacenan ningún texto de mensajes. Cualquiera que pueda leer la traza puede comprobar la igualdad de un prompt o conjunto de herramientas candidato conocido, así que trate los archivos de traza con los mismos controles de acceso que la captura de carga útil opcional que los acompaña.

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, until_segment_cursor, action, category, outcome, severity_min, trace_id, q (subcadena en message + attributes), hide_internal (excluye event.category = "internal"), limit. El campo heredado until_id sigue disponible para la compatibilidad del cursor 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>"

# Cada evento retenido atribuido a una ejecución de SOP (a lo largo de sus turnos de paso):
curl "$ZEROCLAW_GATEWAY/api/logs?sop_run_id=<run-id>"

Log pagination walks backward with a segment-aware cursor. While at_end is false:

  1. Prefiera next_segment_cursor, devuelto sin cambios como until_segment_cursor. Trátelo como un token opaco: su estructura interna depende del segmento en el que terminó la página y puede cambiar entre versiones. Los clientes deben reenviar la cadena devuelta textualmente, sin construir ni analizar una. Este es el único cursor que puede avanzar una vez que el evento más antiguo de una página está en un archivo rotado.
  2. Recurre a next_cursor_line_offset pasado como until_line_offset cuando falta el cursor del segmento. Es un desplazamiento de bytes simple dentro del archivo activo y se resuelve como null cuando el evento más antiguo está en un archivo archivado.
  3. La respuesta heredada next_cursor: [timestamp, id] | null se mantiene por compatibilidad; pasarla de nuevo como until_ts + until_id está obsoleto, porque el desempate lexicográfico por ID puede omitir silenciosamente eventos con el mismo timestamp.

Reinicia desde la página más reciente después de cambiar los filtros. Trata at_end: true como la señal para dejar de solicitar páginas más antiguas en ese recorrido.

at_end se limita a los segmentos que el daemon pudo leer realmente. Cuando un segmento retenido no puede abrirse o deja de decodificarse a mitad del archivo (por ejemplo, por UTF-8 no válido), se registra en el log, la parte no leída se omite de la vista combinada y la respuesta establece incomplete: true. La página se devuelve igualmente, pero entonces at_end significa «no hay eventos más antiguos entre los segmentos que pudieron leerse», y no «no existen eventos más antiguos». Presente ese recorrido como parcial y no como completo. Lo mismo aplica a la búsqueda de un evento individual: en lugar de informar de una falta como not found, el daemon indica que el evento no se encontró y que parte del historial retenido era ilegible, por lo que podría seguir existiendo. Un cursor de segmento no resoluble sigue la misma regla: cuando no se pudo validar el ancla a la que apunta porque su segmento no se abría ni se decodificaba, la página de fin de historial también incluye incomplete: true. Los daemons antiguos omiten el campo; trate su ausencia como false.

until_line_offset es una posición en el archivo activo actual. La rotación de archivos, la migración al iniciar y un cambio de ruta configurada reemplazan los bytes o el archivo activo al que hace referencia; reinicia desde la página más reciente después de esos límites.

until_segment_cursor es resistente a esos límites, mediante distintos mecanismos según dónde terminó la página:

  • Una página que termina en un archivo se identifica mediante la identidad propia del archivo, que se fija cuando el archivo se escribe y nunca se reasigna a contenido distinto. Por tanto, las rotaciones posteriores no pueden invalidarla. Si la retención ya ha eliminado ese archivo, el lector informa de que el historial ha terminado en lugar de reanudar silenciosamente en una posición no relacionada.
  • Una página que termina en el archivo activo lleva un identificador de evento de anclaje junto con el desplazamiento, porque la ruta del archivo activo es estable mientras su contenido se reemplaza en cada rotación. Al reanudar, el lector comprueba que el evento en el límite del cursor siga coincidiendo con el anclaje. Si no coincide, busca ese evento en los segmentos conservados y reanuda desde donde se encuentre ahora, de modo que la paginación atraviese una rotación sin duplicar ni omitir eventos. Si el evento anclado ha desaparecido por completo, el lector informa de que el historial ha terminado.

Un cursor emitido por un daemon anterior, que identificaba un segmento solo por su nombre de archivo y sin ancla, sigue siendo aceptado. Esa forma no permite saber si se refiere a un archivo rotado o al archivo activo, así que el lector primero consulta los archivos archivados, donde un nombre nunca se reasigna, y recurre al archivo activo si no lo encuentra.

El registro de la respuesta incluye persistence_enabled: boolean, que distingue entre “no hay eventos coincidentes” y un daemon en el que la persistencia de trazas de ejecución está deshabilitada. La respuesta de /api/status incluye daemon_started_at: string (RFC 3339), por lo que un panel puede usar “desde el inicio del daemon” como valor predeterminado sin 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

Con una compilación de observability-otel, prefiera el receptor OTLP nativo; no se necesita ningún análisis ni transformación de campos:

receptores:
  otlp:
    protocolos:
      http:
        endpoint: 127.0.0.1:4318

Apunta observability.otel_endpoint a ese listener. El receptor filelog existente sigue siendo un respaldo útil para las compilaciones predeterminadas, para cargar retroactivamente los JSONL retenidos o para ejecutar un enviador de archivos independiente:

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-log/src/export_bridge.rs: puente de eventos canónicos sin bloqueo utilizado por el exportador nativo de registros OTLP.
  • crates/zeroclaw-runtime/src/observability/otel_logs.rs: Mapeo del modelo de datos de logs de OTel y exportador OTLP/HTTP con protobuf.
  • crates/zeroclaw-gateway/src/api_logs.rs: el adaptador HTTP.

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