Telemetry · Logs

Logs — audit-log entries, dual-written

Audit entries are dual-written: OTEL Logs export to <endpoint>/v1/logs AND a local JSONL file at collector.logs.path (default ~/.nio/audit.jsonl). The JSONL line is the entry verbatim; the OTEL LogRecord uses body = JSON.stringify(entry) plus a flat attribute set for indexing.

Entry types

Discriminated by the event field. Five shapes — four named entry types plus the catch-all "hook event" record.

event: "guard" Guard decision per PreToolUse / PostToolUse
FieldTypeNotes
timestampstringISO-8601
platformstringclaude-code / hermes / openclaw
session_idstring?host session id
cwdstring?working dir
tool_namestringhost tool name
action_typestring?exec_command / write_file / network_request / read_file
tool_input_summarystringredacted ≤200-char summary of tool input
decisionstringallow / deny / ask
risk_levelstringlow / medium / high / critical
max_finding_severitystringhighest finding severity
risk_scorenumber0–1 final score
risk_tagsstring[]rule IDs hit (deduped)
phase_stoppednumber | nullwhich Phase 0–6 produced the decision
scoresRecord<string, number>per-phase score (runtime, static, behavioural, llm, external, final)
phasesAuditPhaseMap?per-phase {score, finding_count, duration_ms}
top_findingsAuditFindingSummary[]up to 5: {rule_id, severity, category, title, confidence}
explanationstring?human-readable reason
initiating_skillstring?which skill scope the action originated from
event_type"pre" | "post"?which hook side fired
event: "session_scan" Skill scan (on-demand or session-start)
FieldTypeNotes
timestampstringISO-8601
platformstringhost
session_idstring?host session id
skill_namestringscanned skill / dir
risk_levelstringaggregated severity
risk_tagsstring[]rule IDs hit
finding_countnumber?total findings
event: "lifecycle" Subagent / agent / session lifecycle
FieldTypeNotes
timestampstringISO-8601
platformstringhost
session_idstring?host session id
lifecycle_typestringsubagent_spawning / subagent_ended / agent_end / session_start / session_end
detailsRecord<string, unknown>?platform-specific (e.g. OpenClaw: {subagent_id, run_id})
event: "config_error" Config load failure
FieldTypeNotes
timestampstringISO-8601
config_pathstringpath that failed to load
error_messagestringparser / IO error
event: <hook event> Collector hook record (one entry per dispatched hook event)

Discriminator is the canonical hook event name itself: UserPromptSubmit · PreToolUse · PostToolUse · TaskCreated · TaskCompleted · Stop · SubagentStop · SessionStart · SessionEnd.

FieldTypeNotes
timestampstringISO-8601
platformstringhost
session_idstring?host session id
cwdstring | nullworking dir
transcript_pathstring?Claude Code-only — path to session transcript JSONL
tool_namestring?for PreToolUse / PostToolUse
tool_use_idstring?for PreToolUse / PostToolUse
tool_summarystring?for PreToolUse / PostToolUse
task_idstring?for TaskCreated / TaskCompleted
task_summarystring?for TaskCreated

OTEL LogRecord projection

The flat attribute set used for OTEL Logs indexing. Same key names as the matching trace span attributes wherever a concept overlaps (tool name, conversation id, guard decision, …) — same query keys work across logs and traces.

  • body = JSON-stringified entry (full content of the JSONL line)
  • severityNumber / severityText derived from risk_level: low→INFO, medium→WARN, high→ERROR, critical→FATAL; INFO when no risk_level
AttributeDescriptionCaptured atPlatforms
gen_ai.tool.nameHost tool name; same key as the tool-span attributePreToolUse · PostToolUse · guard decisionall
gen_ai.tool.call.idHost tool-call id; same key as the tool-span attributePreToolUse · PostToolUseall
gen_ai.conversation.idHost session id; same key as the turn-span attributeevery audit entry with a sessionall
session.idMirror of gen_ai.conversation.id for OTel base-spec consumersevery audit entry with a sessionall
nio.guard.decisionGuard verdict — allow / deny / askguard decisionall
nio.guard.risk_levelGuard risk level — low / medium / high / criticalguard decisionall
nio.guard.risk_scoreGuard risk score, 0–1guard decisionall
nio.guard.risk_tagsComma-joined rule IDs that firedguard decisionall
nio.tool_summaryOne-line summary derived from tool inputPreToolUse · PostToolUseall
nio.task_idTask id from the dispatch eventTaskCreated · TaskCompletedClaude Code + OpenClaw
nio.task_summaryDerived from task inputTaskCreatedClaude Code + OpenClaw
nio.platformSource platform — claude-code / hermes / openclawevery audit entryall
nio.cwdWorking dir at hook fireevery audit entry with cwdall
nio.eventDiscriminator — hook event name vs guard / lifecycle / scan / config_errorevery audit entryall
nio.event_typepre / post for guard entriesguard decisionall
nio.action_typeexec_command / write_file / network_request / read_fileguard decisionall
nio.max_finding_severityHighest finding severity surfaced this runguard decisionall
nio.phase_stoppedWhich Phase 0–6 produced the decisionguard decisionall
nio.explanationHuman-readable reason for the verdictguard decisionall
nio.transcript_pathClaude Code-only — path to session transcript JSONLhook events with transcriptClaude Code only
nio.phases.{name}.scorePer-phase score (Phase 0–6)guard decisionall
nio.phases.{name}.finding_countPer-phase finding countguard decisionall
nio.phases.{name}.duration_msPer-phase wall-clock cost (ms)guard decisionall

Local JSONL path: collector.logs.path (default ~/.nio/audit.jsonl). Rotation kicks in at collector.logs.max_size_mb (default 100 MB) — the live file is renamed to <path>.1.