Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

ログと観測性

ZeroClaw が発行するすべてのイベントは、1 つのクレート zeroclaw-log を経由します。このクレートは、ディスク上の JSONL スキーマ、ダッシュボードが読み取るプロセス内ブロードキャストストリーム、オプションの型付き Observer ブリッジ、ネイティブの OTLP ログエクスポートブリッジ、そしてサブシステムが呼び出すマクロ(record!、scope!、spawn!)を所有します。

このページでは、オペレーターが必要とする情報を扱います。設定、ログの保存場所、イベントの構造、そしてそれらをクエリする方法です。

デスクトップデーモンのキャプチャ

デスクトップ版のスーパーバイザーは、デーモンの標準出力と標準エラー出力を、サイズに上限のある1つのファイル <config-dir>/logs/zeroclaw-desktop-daemon.log に記録します。<config-dir> は正規の設定解決の優先順位に従って決まります。具体的には、ZEROCLAW_CONFIG_DIR、次に ZEROCLAW_DATA_DIR、次に非推奨の ZEROCLAW_WORKSPACE、最後に Homebrew/デフォルトの解決の順です。ファイルの上限は 8 MiB です。出力が上限を超えると、最も古いバイトは圧縮処理によって削除され、最新の末尾部分が保持されます。

設定([observability])

デフォルト: log_persistence = "rolling", log_persistence_max_entries = 200, log_tool_io = "redacted", log_tool_io_truncate_bytes = 40960, log_llm_request_payload = "off"。新規インストールでは ~/.zeroclaw/data/state/runtime-trace.jsonl に 200 イベントのローリング JSONL が生成され、ダッシュボードの Logs ページは追加の設定なしで動作します。

log_persistence = "none" は永続化を完全に無効にしますが、ダッシュボードの SSE や ネイティブ OTLP エクスポートで使われるブロードキャストストリームは制御しません。オプションの型付き Observer ブリッジも永続化とは独立していますが、明示的にバインドされた場合にのみ正規のログイベントを受け取ります。現在の本番ブートストラップではこのバインディングは設定されていません。

永続化はベストエフォートであり、トランザクション的な監査保証ではありません。Observer ブリッジ、ネイティブ OTLP キュー、ブロードキャスト配信は、イベントが有界のバックグラウンドライタキューに渡される前に実行されます。キューが満杯になったり、ワーカーの書き込みに失敗したりすると、イベントが JSONL に書き込まれないことがあります。定期的な同期は現在のアクティブファイルを対象とします。UTC の新しい日の最初の追記の前に行われる日次ローテーションと、しきい値を超えた追記の後に行われるサイズベースのローテーションでは、アクティブファイルを同期せずにリネームすることがあるため、同期の間隔では、ローテーション直後のアーカイブの耐久性は保証されません。配信経路ごとの個別の保証については、ログ記録のアーキテクチャを参照してください。

アーカイブのローテーション (log_persistence = "rotating")

rotating は、アクティブファイルを切り詰めるのではなく、ローテーションすることですべてのイベントを保持します。サイズ、日次の境界、またはエントリ数のいずれかのトリガーで、アクティブファイルはタイムスタンプ付きのアーカイブにリネームされます。古いアーカイブは、各ローテーションの後に件数と経過時間で削除されます。

キーデフォルト効果
log_persistence_max_bytes0追記によってアクティブファイルがこのバイト数以上になった時点で、一度ローテーションを行います。0 を指定するとサイズによるローテーションが無効になります。
log_persistence_rotate_dailytrue新しいUTC日の最初のイベントの前に、最終書き込みがそれより前の日に行われたファイルをアーカイブします。
log_persistence_max_entries_per_segment0セグメントの空でない行数がこの上限に達すると、ローテーションを行います。定常状態では、各アーカイブにはちょうどこの数のエントリが含まれます。既存のログで初めて有効化された場合、そのファイルは丸ごとアーカイブされます(一度だけ上限を超える遷移が発生します)。定常状態は次のローテーション時に再開されます。0 を指定すると、エントリ数によるローテーションが無効になります。
log_persistence_retention_max_files7最大でこの数のアーカイブを保持します。ローテーション後、上限を超えた最も古いものが削除されます。0 はすべてを保持します。
log_persistence_retention_max_age_days0ローテーション後にこの日数より古いアーカイブを削除します。0 は経過日数ベースのクリーンアップを無効にします。

アーカイブはアクティブなファイルと同じディレクトリに置かれ、元の拡張子を保持します。拡張子の直前には、ソート可能なUTCタイムスタンプが挿入されます。例えば、runtime-trace.jsonl は runtime-trace.0000000001-20260624-031500.jsonl にローテーションされます。シーケンスプレフィックス(0000000001)はローテーション時に書き込まれ、リーダー側での並び順を決定します。ベストエフォートのサイドカーが再起動をまたいで高水位マークを保持します。サイドカーを書き込めない状態で保持ポリシーによってすべての番号付きアーカイブが削除され、その後デーモンが再起動すると、シーケンス番号が再利用される可能性があります。その結果、未処理のカーソルが新しい履歴に紐付いてしまうことがあります。シーケンス番号の導入前に書き込まれたアーカイブは従来の形式(runtime-trace.20260624-031500.jsonl)を維持し、すべての番号付きアーカイブより前に並びます。

ダッシュボードと GET /api/logs は、現在のファイルと保持されているすべてのアーカイブを 1 つの論理的なイベントストリームとして読み取るようになりました。アーカイブは古いものから順にマージされ、イベントは新しいものから順に返されます。API はセグメントを考慮したカーソル(next_segment_cursor)を、既存のバイトオフセットカーソル(next_cursor_line_offset)と並べて公開しています。セグメントの境界をまたいでページングするには、後続のリクエストで ?until_segment_cursor= を指定してください。従来のバイトオフセットカーソルも引き続き有効で、現在のファイル内のオフセットとして解釈されます。

日次ローテーションはUTCカレンダーを基準とするため、その境界は他のタイムゾーンのローカル深夜と一致しない場合があります。これらのキーは log_persistence = "rotating" でない限り無視され、none、rolling、および full モードは変更されません。

ネイティブ OTLP ログ (observability-otel)

オプトインの observability-otel 機能は、公式 OpenTelemetry SDK を通じてトレース、メトリクス、ログをエクスポートします。ログは OTLP/HTTP と protobuf エンコーディングを使用し、<otel_endpoint>/v1/logs へバッチ送信されます。トレースとメトリクスは標準の /v1/traces および /v1/metrics エンドポイントを引き続き使用します。この機能は SDK および HTTP クライアントの規模が大きいため、デフォルトビルドには含まれないままです。

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

# ローカル/オフラインでの確認も同時に引き続き利用できます。
log_persistence = "rolling"

# 任意のベンダーまたはコレクターのヘッダーです。
[observability.otel_headers]
Authorization = "Bearer <collector-token>"

--features observability-otel を指定してビルドします。エンドポイントには、OpenTelemetry Collector、Grafana Alloy、または HTTP/protobuf 経由で OTLP ログを受け付けるベンダーのエンドポイントを指定できます。ヘッダーの値はシークレットの設定項目であり、エクスポートされるレコードには決して含まれません。

Each canonical LogEvent maps into the stable OpenTelemetry Logs Data Model:

Wait — I should translate, not copy. Correct output:

各正規のLogEventは、安定版のOpenTelemetry Logs Data Modelにマッピングされます:

ZeroClaw fieldOpenTelemetry の送信先
@timestampLogRecord の Timestamp;エクスポート時刻は ObservedTimestamp になります
severity_number, severity_textLogRecord の正規化された重大度フィールド
messageLogRecord Body
設定済みのサービス名とクレートのバージョンリソース service.name, service.version
有効な16バイトのトレースIDと8バイトのスパンIDLogRecord の TraceId、SpanId
event.*ECS互換のLogRecord属性
_file, _linecode.file.path, code.line.number
モデル/プロバイダー/トークンフィールドgen_ai.* セマンティック属性
ツール識別情報tool.name
zeroclaw.* 属性namespaced LogRecord 属性(zeroclaw.sop_run_id を含む)

OTel のトレース/スパン識別子として有効でないアプリケーションの相関文字列は、トレースコンテキストを捏造せずに zeroclaw.trace_id / zeroclaw.span_id 属性として検索可能な状態で保持されます。ネストされた JSON 値は、OTel の AnyValue のマップとリストに対応付けられます。ブロードキャスト専用の一時的な属性(ペアリングコードを含む)は、エクスポーターに渡されることは決してありません。

このエクスポーターは追加型です。JSONL、ダッシュボードの SSE、/api/logs、CLI、ZeroCode は既存のスキーマを維持し、コレクターが利用できない場合でも引き続き動作します。エクスポーターの置き換えまたはシャットダウン時には、SDK のバッチキューを強制フラッシュします。ランタイムでのイベント出力自体はキューに追加するだけで、呼び出し元でネットワーク I/O は行いません。

GenAI スパン属性 (observability-otel)

llm.response スパンは、OTel GenAI メッセージコンテンツ属性 gen_ai.input.messages、gen_ai.output.messages、gen_ai.system_instructions(JSON 文字列エンコード)を保持しており、これらは Langfuse/Tempo の Input/Output/System ペインに反映されます。

プライバシーとコスト。 キャプチャされたコンテンツはベストエフォートでサニタイズされます: インライン画像データは省略され、既知の認証情報の形式(key=value、bearer、および sk-/ghp_/xoxb--style のプレフィックス)はリダクションされます。これはすべてのシークレットまたは PII の削除を保証するものではありません。会話に機密情報が含まれる可能性がある場合は、アクセス制御されたトレースバックエンドを優先してください。キャプチャのコストは O(prompt size) エージェントループのイテレーションごと です(成長する履歴が各ラウンドで再スキャンされます)。またフルテキストは span ごとのペイロードに比例して増加します。バイト単位のバックエンドでは、属性を削除するのではなく、エクスポーター側で切り捨てを適用してください。

OTel コンテンツキャプチャ

OTel コンテンツキャプチャは、ログベースのキャプチャ (log_tool_io, log_llm_request_payload) とは独立しています。OpenTelemetry スパン属性として出力されるコンテンツを制御します。

生成AIコンテンツ

gen_ai.system_instructions、gen_ai.input.messages、および gen_ai.output.messages を OTel スパン上で制御します。

[observability]
otel_genai_content = "off"            # off | redacted | full
otel_genai_content_max_chars = 1000  # フィールドごとの切り詰め上限
  • off(デフォルト): コンテンツ属性なし、メタデータのみ。
  • redacted: コンテンツはリークスキャンされ、フィールドごとに max_chars で切り詰められます。
  • full: コンテンツはリークスキャンされますが、切り詰められません。

ツール I/O

OTel スパン上の gen_ai.tool.arguments、input.value、gen_ai.tool.result、および output.value を制御します。

[observability]
otel_tool_io = "off"                  # off | redacted | full
otel_tool_io_max_chars = 1000        # フィールドごとの切り詰め上限
  • off(デフォルト): コンテンツ属性なし、ツール名 + 結果のみ。
  • redacted: コンテンツはリークスキャンされ、フィールドごとに max_chars で切り詰められます。
  • full: コンテンツはリークスキャンされますが、切り詰められません。

動作に関する注意事項

  • *_max_chars = 0 を設定すると、そのポリシーでは off と同じ意味になります。
  • コンテンツは常に切り詰めの前にスクラブされます(認証情報パターン + シークレットパターン)。
  • 切り詰めはツール引数の JSON 構造を保持します(末端の文字列が切り詰められます)。
  • 切り詰められたフィールドには …[truncated {n} of {total} chars] マーカーが付きます。このマーカーはメタデータであり、max_chars にはカウントされません。保持されるコンテンツは正確に max_chars 文字で、その上にマーカーが付加されます。
  • デフォルト off は、以前の動作からのプライバシー優先の変更です(機能ゲート付きだが、有効時は常にオン)。
  • コンテンツポリシーは、プロセスではなくオブザーバー/設定インスタンスにバインドされます。プロセスグローバルな OTel コンテンツポリシーは存在しません。各 OtelObserver は構築時に ObservabilityConfig からイミュータブルなコンテンツ設定を導出し、OTel エクスポート境界でそれを参照します。同一プロセス内の複数のオブザーバーはそれぞれ独立したポリシーを保持します。後から作成されたオブザーバーが、先に作成されたオブザーバーのプライバシー設定を上書きしたり無効化したりすることはできません(後勝ちも、オブザーバー間のドリフトも発生しません)。

ターン入れ子メモリおよびRAGスパン(observability-otel)

memory.recall、memory.store、rag.retrieve の各スパンは、操作が属性付きのエージェントターン内で実行されると常に gen_ai.agent.invoke ターンスパンの下にネストされます。これにより、1回の完全なターン(メモリのリコール、自動保存によるストア、LLM呼び出し、ツール呼び出し)が Langfuse/Tempo で1つのトレースとして表示されます。これら3つのイベントは、LLMイベントやツールイベントと同じ channel / agent_alias / turn_id の3つ組を持ち、zeroclaw.channel、gen_ai.agent.name、zeroclaw.turn_id のスパン属性として公開されます。

相関付けられたターンの外側でのメモリ操作は、引き続きルートスパンを生成します: gateway REST メモリストアと、process_message hardware-RAG 取得です。これはターンのブラケットが開く前に実行されるため、一致する zeroclaw.turn_id 属性を持つルートスパンのままになります (そのスパンの完全なネストは #8844 で追跡されています)。ライブターンに一致しなくなった turn_id も、親を推測するのではなくルートスパンにフォールバックします。

LLM リクエストペイロードのキャプチャ (log_llm_request_payload)

log_llm_request_payload は、llm_request イベントが messages_count に加えて送信されるプロンプトと会話を記録するかどうかを制御します。これはデフォルトで無効であり、プライバシーに関わる部分です。有効にすると、ZeroClaw はターンごとにシステムプロンプト全体と会話履歴全体を保存します。

値キャプチャされる内容
off(デフォルト)メッセージの内容は記録されません。イベントには引き続き messages_count と、常時有効なプレフィックスのフィンガープリント(以下で説明)が含まれます(system_chars、tools_count、および存在する場合は system_sha256、tools_sha256)。これらは内容ではなくダイジェストですが、トレースの読み手は等しいかどうかを比較できます。
redacted完全なメッセージ履歴(role + content)。raw_response およびツール I/O に使用されるものと同じ scrub_credentials パスでクレデンシャルスキャンされ、その後 log_tool_io_truncate_bytes で切り詰められます。切り詰めは request_messages_truncated と request_messages_original_bytes でフラグ付けされます。
fullredacted と同じ認証情報の除去を行いますが、切り詰めは行いません(リプレイの忠実性を確保し、raw_response を反映します)。

すべての llm_request イベントには、ペイロードポリシーに関係なく、ランタイムのリクエスト入力のプレフィックスフィンガープリントも含まれます。system_chars と tools_count は常に存在します。system_sha256 は、先頭の system メッセージが連続する範囲について、それらの内容を JSON 配列としてシリアライズしたうえで SHA-256 を計算し、その先頭 16 桁の16進数文字です。これは最初のメッセージが system ロールの場合にのみ存在します。tools_sha256 は、ネイティブのツール仕様を JSON 配列としてシリアライズして同様に計算したもので、ツール仕様が添付されている場合にのみ存在します。そのため、並べ替えられたツールセットは異なるフィンガープリントになります。これらは、ランタイムがプロバイダーアダプターに渡した内容を表し、論理リクエストごとに1行で記録されます。プロバイダー側の変換処理(スキーマのクリーニング、cache-control の配置、OAuth のシステムプレフィックス、互換性のあるワイヤーでのシステムメッセージの統合、Anthropic アダプターが破棄する2つ目の先頭システムメッセージ、アダプター内部でのリトライ)は、ハッシュ化されるバイト列の対象外です。変化は診断のヒントとして扱い、キャッシュの判定とはみなさないでください。system_sha256 が変化した場合は、先頭のシステムメッセージのいずれかが変わったか、挿入または削除されたことを意味します。例えば、取り込まれたワークスペースファイルが変更された場合、呼び出し前のフックがメッセージを編集または追加した場合、ツールのフレーミングがネイティブプロトコルとテキストプロトコルの間で切り替わった場合などです。tools_sha256 が変化した場合は、ランタイムのツール仕様のシリアライズが変わったことを意味します。これには、ワイヤーでは送信されないメタデータフィールドの変更も含まれます。テキストプロトコルモードでは、ツールの指示はシステムテキスト内に含まれるため、tools_count は 0 になり、tools_sha256 は存在しません。その場合、ツールセットの変更は system_sha256 に反映されます。実際のキャッシュヒットまたはミスは、プロバイダーが報告するキャッシュ使用量で確認してください。フィンガープリントはカウントと、ソルトなしで切り詰めたハッシュであり、内容そのものではありません。入力が変わったかどうかは分かりますし、既知の候補テキストが一致するかを検証することもできます。しかし、メッセージ本文は一切保存しません。トレースを読める人は誰でも、既知の候補プロンプトやツールセットが一致するかを検証できます。したがって、トレースファイルは、その横に置かれたオプトインのペイロードキャプチャと同じアクセス制御で管理してください。

redacted と full はどちらも常に資格情報のスクラビングを実行します。両者の唯一の違いは切り捨てです。キャプチャは 2 つ目の上限を導入するのではなく、既存の log_tool_io_truncate_bytes の上限を再利用します。キャプチャを即座に無効にするには、log_llm_request_payload = "off" に設定するかそのままにしてください。再デプロイは不要です。

ディスク上のフォーマット

JSONL: 1行に1イベント、UTF-8、Unix 上では 0o600 パーミッション。ホットパスはノンブロッキングです: record_event はシリアライズされたイベントを有界チャネル経由で専用のバックグラウンドスレッド(zeroclaw-log-writer)に渡し、即座に返します。ワーカーは定期的な間隔で sync_all を呼び出します: 100回の書き込みごと、またはウォールクロック時間で1秒ごと(いずれか先に到達した方)、加えて通常シャットダウン時にチャネルが閉じる際の最終的な sync_all。これにより、イベントごとの耐久性(以前の同期的な動作)を有界な書き込みレイテンシと引き換えにします: プロセスがクラッシュすると、最大で1同期間隔分の保留中の書き込みが失われる可能性があります。ワーカーが遅延した場合、record_event は非同期ランタイムをブロックするのではなく、tracing::warn! でイベントをドロップします。ワーカーはプロセスごとのシングルトンです。init_from_config 経由で永続化を無効化して再有効化すると、古いワーカーが破棄され(チャネルのクローズにより最終同期とスレッド終了がトリガーされる)、新しいワーカーが生成されます。

Line形状は zeroclaw_log::event::LogEvent をミラーします。トップレベルのキー:

キータイプ注釈
idUUID v4 文字列永続的なイベント ID。
@timestampRFC 3339 + ミリ秒、UTC辞書順でソート可能です。リーダーはこれを基準にソートします。
severity_numberu8OTel: 1 TRACE、5 DEBUG、9 INFO、13 WARN、17 ERROR。
severity_textstringseverity_number のバケットラベル。
event.categorystringagent、channel、cron、memory、tool、provider、session、system、または internal。
event.actionstring安定した識別子 (llm_request、channel_message_inbound、…)。
event.outcomestring | 省略success、failure、unknown(unknown の場合は省略)。
service.namestringConstant "zeroclaw".
service.versionstring実行中のデーモンの Crate バージョン。
trace_id16進数文字列 | 省略ターンごとの相関。1つのエージェントターン = 1つの trace_id。
span_id16進数文字列 | 省略ターン内のサブスパン。
zeroclaw.*フラットな文字列マップエイリアスにバインドされた属性(以下を参照)。
messagestring | 省略人間が読み取り可能な行本文。
attributesobject | omittedアクションごとの自由形式のペイロード。
schema_versionu8現在は 2。v1 の行は起動時にインプレースで移行されます。

zeroclaw.* 属性

信頼できる情報源となるRustの定義は、crates/zeroclaw-log/src/event.rs内のATTRIBUTION_FIELDSとCOMPOSITE_PREFIXESです。/api/logsのレスポンスは正規のリストをattribution_keysとして保持しているため、ハードコードする代わりにこれを取得してください。

プレーンフィールド(ATTRIBUTION_FIELDS)はそれぞれ単一の文字列を保持します。複合プレフィックスには3つのキーが付与されます: <prefix>、<prefix>_type、<prefix>_alias(例: channel = "discord.glados"、channel_type = "discord"、channel_alias = "glados")。フィルターは粗いマッチングと精密なマッチングのいずれにも対応できます。

トレース呼び出しが複合プレフィックスフィールドを(. を含まない)裸の型に設定した場合、_type スロットのみが設定されます。これにより、すでに完全な <type>.<alias> 複合を保持しているスパン内での tracing::*!(model_provider = name, …) 呼び出しが、リーフ→ルートのマージ時にそれを上書きしてしまうことを防ぎます。

クエリの実行

ダッシュボードのLogsページがメインの操作画面です。その内部では以下のように動作します。

GET /api/logs

トップレベルのフィルター(クエリパラメーター): since_ts、until_ts、until_line_offset、until_segment_cursor、action、category、outcome、severity_min、trace_id、q(message と attributes を対象とした部分一致検索)、hide_internal(event.category = "internal" のイベントを除外)、limit。従来の until_id フィールドは、タイムスタンプ/ID によるカーソルの互換性のため引き続き利用できます。

他のすべての ?<key>=<value> は属性ごとの等価フィルターとして扱われ、ゲートウェイは is_attribution_field に対してキーを検証し、不明なものは 400 で拒否します。レスポンスには attribution_keys: string[] が含まれるため、呼び出し元が推測する必要はありません。

例:

sh

# デーモン起動以降のすべてのWARN+イベント。
curl "$ZEROCLAW_GATEWAY/api/logs?severity_min=13"

# 特定のエージェントのイベント:
curl "$ZEROCLAW_GATEWAY/api/logs?agent_alias=glados"

# 1つのボットのDiscordトラフィック:
curl "$ZEROCLAW_GATEWAY/api/logs?channel=discord.glados"

# 単一のエージェントターン:
curl "$ZEROCLAW_GATEWAY/api/logs?trace_id=<value-from-a-prior-event>"

# SOP実行1回分に紐づく、保持されているすべてのイベント(各ステップのターンを横断):
curl "$ZEROCLAW_GATEWAY/api/logs?sop_run_id=<run-id>"

ログのページネーションは、セグメントを意識したカーソルを使って後方へ移動します。at_end が false の間:

  1. next_segment_cursor を優先して使用してください。変更せずに until_segment_cursor として渡し返します。これは不透明なトークンとして扱ってください。その内部構造は、ページが終端したセグメントによって異なり、リリース間で変わる可能性があります。クライアントは返された文字列を構築・解析せず、一切変更せずにそのまま返送する必要があります。ページ内の最も古いイベントがローテーション済みのアーカイブにある場合に前進できる唯一のカーソルです。
  2. セグメントカーソルが存在しない場合は、until_line_offset として返された next_cursor_line_offset にフォールバックします。これはアクティブファイル内の単純なバイトオフセットであり、最も古いイベントがアーカイブ内にある場合は null になります。
  3. 従来の next_cursor: [timestamp, id] | null レスポンスは互換性のために引き続き提供されます。until_ts と until_id としてこの値を渡す方法は、辞書順の ID によるタイブレークが同じタイムスタンプのイベントを黙ってスキップする可能性があるため、非推奨です。

フィルターを変更した後は、最新のページから再開してください。at_end: true は、その走査で古いページの取得を停止する合図として扱ってください。

at_end は、デーモンが実際に読み取れたセグメントの範囲に限定されます。保持されているセグメントを開けない場合、またはファイルの途中でデコードが停止した場合(例:不正な UTF-8)、そのことがログに記録され、読み取れなかった部分はマージされたビューから除外され、レスポンスには incomplete: true が設定されます。ページは引き続き返されますが、その場合の at_end は「古いイベントが存在しない」ではなく、「読み取れたセグメント内に古いイベントがない」を意味します。このような走査は、完全ではなく部分的なものとして提示してください。単一イベントの検索も同様です。見つからなかった場合に not found と報告するのではなく、デーモンはイベントが見つからなかったことと、保持履歴の一部が読み取れなかったためイベントが存在する可能性があることを報告します。セグメントカーソルの解決についても同じ規則が適用されます。カーソルが指すアンカーを、そのセグメントを開けない、またはデコードできないために検証できなかった場合、履歴の末尾のページには同じく incomplete: true が付与されます。古いデーモンはこのフィールドを省略します。フィールドが存在しない場合は false として扱ってください。

until_line_offset は現在のアクティブファイル内の位置を示します。アーカイブのローテーション、起動時のマイグレーション、設定されたパスの変更によって、それが参照するバイト列やアクティブファイルが置き換えられた場合は、それらの境界の後で最新のページから再開してください。

until_segment_cursor は、ページの終端位置に応じて異なる方法で、それらの境界をまたいでも堅牢に動作します:

  • アーカイブで終わるページは、アーカイブ自身の識別子によって参照されます。この識別子はアーカイブの書き込み時に確定し、別の内容に再割り当てされることはありません。そのため、その後のローテーションによって無効になることはありません。保持期間の経過によってそのアーカイブが削除されている場合、リーダーは無関係な位置から黙って再開するのではなく、履歴が終了したことを報告します。
  • アクティブファイル内で終わるページには、オフセットとともにアンカーイベントIDが保存されます。アクティブファイルのパスは安定していますが、ローテーションのたびに内容が置き換わるためです。再開時にリーダーはカーソル境界のイベントがアンカーと一致するかを確認します。一致しない場合は、保持されているセグメントからそのイベントを検索し、現在の位置から再開します。これにより、ローテーションをまたいでもイベントの重複や欠落なくページングが継続されます。アンカーされたイベントが完全に失われている場合、リーダーは履歴が終了したことを報告します。

古いデーモンが発行したカーソルは、セグメントをアンカーなしのファイル名で指定しており、引き続き受け付けられます。この形式では、ローテーション済みアーカイブを指しているのか、アクティブなファイルを指しているのかを判別できません。そのため、リーダーはまずアーカイブを検索します。アーカイブでは名前が再割り当てされることがないためです。そのうえで、見つからない場合はアクティブなファイルにフォールバックします。

The log response includes persistence_enabled: boolean, which distinguishes “no matching events” from a daemon where runtime-trace persistence is disabled. The /api/status response includes daemon_started_at: string (RFC 3339), so a dashboard can default to “since daemon start” without an extra round-trip.

外部ログビューア

JSONLスキーマはOTelログとECSのハイブリッドです。@timestamp、severity_number + severity_text、event.{category,action,outcome}、service.{name,version}、attributesに加え、zeroclaw.*ベンダー名前空間が含まれます。ほとんどのログビューアは、ほぼ変換なしでこれを取り込めます。以下の例では、<install>をインストールディレクトリの絶対パス(通常は~/.zeroclawを展開したもの)に置き換えてください。

Grafana Loki

Promtail のラベルは agent_alias、channel、severity_text を抽出し、Grafana でフィルター可能にします:

scrape_configs:
  - ジョブ名: ゼロクロー
    static_configs:
      - ターゲット: [localhost]
        ラベル:
          ジョブ: ゼロクロー
          __path__: <install>/data/state/runtime-trace.jsonl
    pipeline_stages:
      - json:
          式:
            エージェント: zeroclaw.agent_alias
            チャンネル: zeroclaw.channel
            level: severity_text
      - ラベル:
          エージェント:
          チャンネル:
          level:
      - タイムスタンプ:
          source: @timestamp
          フォーマット: RFC3339

OpenTelemetry Collector

With an observability-otel build, prefer the native OTLP receiver; no parsing or field transform is needed:

レシーバー:
  otlp:
    プロトコル:
      http:
        エンドポイント: 127.0.0.1:4318

observability.otel_endpoint をそのリスナーに向けてください。既存の filelog レシーバーは、デフォルトビルドの場合、保持されている JSONL のバックフィルを行う場合、または個別のファイル転送ツールを実行する場合に、引き続き有用なフォールバックとなります:

レシーバー:
  filelog/zeroclaw:
    include: [<install>/data/state/runtime-trace.jsonl]
    演算子:
      - 型: json_parser
        タイムスタンプ:
          parse_from: attributes["@timestamp"]
          layout: '%Y-%m-%dT%H:%M:%S.%LZ'
        重要度:
          parse_from: attributes.severity_number

Kibana / Elastic

取り込みはそのまま機能します。厳格な ECS パイプラインでは、severity_text の代わりに log.level が必要です。severity_text を log.level に(さらに severity_number を log.syslog.severity.code に)リネームする Filebeat 取り込みパイプラインによって、このギャップを埋められます。@timestamp と event.{category,action,outcome} は既に正規の位置に配置されています。

Vector / Fluent Bit

どちらも JSON パーサーステージで JSONL を tail します。任意のバックエンドへ送信する前のスキーマ変換は不要です。

ターミナル形式

デーモンの stderr フォーマッタは、各行の先頭に、最も近い外側のエイリアスにバインドされた識別子を付加します:

  • エージェントコンテキスト → [<agent_alias>]
  • channel専用のコンテキスト(channelリスナー、まだagentなし)→ [<channel_composite>](例:[discord.glados])
  • otherwise → [system]

スパンチェーンは次のとおりです: channel_listener{channel=discord.glados}: …。スパンフィールドはインラインで表示されます。

スキーマ移行

起動時、log_persistence が有効でファイルが存在する場合、ライターは最初の追記の前に、すべての schema-1 行をインプレースマイグレーションによって schema-2 へストリーミング処理します。これは純粋なストリーミング処理であり、ファイルサイズに関係なく1行分のメモリ割り当てに制限されます。マイグレーション後のファイルはアトミックにリネームされて配置されます。すでに v2 のファイルはそのまま変更されません。

移行に失敗した場合、デーモンは warn をログに記録し、v2 の追記書き込みを継続します。古い v1 の行は、v1 をまだ解釈できるツールでは引き続き読み取り可能ですが、v2 リーダーのデシリアライザーは通過しません。

internalとは何ですか?

event.category = "internal" は、オペレーターがデフォルトではダッシュボードに表示する必要のない運用ノイズ用のバケットです。ハートビートのティック、アイドル状態のブロードキャスト、ロスのある同期リトライなどが該当します。ダッシュボードの「内部を非表示」トグル(デフォルトで有効)がこれらをフィルタリングします。

フォレンジック調査でその存在が重要であるものの、通常は存在しないことが正常な状態である高頻度イベントがある場合に使用します。本物のエラーに対する量の調整役として使用しないでください。

関連ファイル

  • crates/zeroclaw-log/src/event.rs: 正規の LogEvent 形式。
  • crates/zeroclaw-log/src/layer.rs: すべての tracing::* 呼び出しをキャプチャし、パイプラインに渡す tracing-subscriber Layer。
  • crates/zeroclaw-log/src/macro.rs: record!、scope!、spawn!。
  • crates/zeroclaw-log/src/writer.rs:追記、ローリングトリム、およびアーカイブローテーション。
  • crates/zeroclaw-log/src/reader.rs: /api/logs リーダー。
  • crates/zeroclaw-log/src/config.rs: StoragePolicy, ToolIoPolicy, ResolvedPolicy。
  • crates/zeroclaw-log/src/migrate.rs:schema-1 → schema-2 のストリーミングマイグレーション。
  • crates/zeroclaw-log/src/observer_bridge.rs: Prometheus / OTel コンシューマー向けの型付き Observer プロジェクション。
  • crates/zeroclaw-log/src/export_bridge.rs: ネイティブ OTLP ログエクスポーターが使用する、ノンブロッキングな正規イベントブリッジです。
  • crates/zeroclaw-runtime/src/observability/otel_logs.rs: OTel Logs Data Model へのマッピングと OTLP/HTTP protobuf エクスポーター。
  • crates/zeroclaw-gateway/src/api_logs.rs: HTTP アダプター。

このページの文章を信頼する前に、ソースを確認してください。