pi-logger
v2.0.3
Published
A telemetry logging extension for the pi agent harness.
Maintainers
Readme
pi-logger
A telemetry logging extension for pi. It intercepts lifecycle events and writes them to a JSONL file in a local .pi/logs/ directory for debugging and visualization.
Built for personal use to understand how an AI coding agent works internally — what it does, how long things take, where tokens go, and how the agent's decision-making flows from turn to turn.
What It Captures
Subscribes to 21 of 32 available pi extension events, organized by priority:
| Priority | Events | Purpose |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| P0 (core) | session_start/shutdown, agent_start/end/settled, turn_start/end, message_start/end, tool_execution_start/end, after_provider_response | Trace hierarchy, timing, token/cost metrics |
| P1 (strong) | input, before_agent_start, session_before_compact, session_compact, model_select, tool_call, tool_result | User intent, context utilization, audit trail |
| P2 (nice) | thinking_level_select, session_tree, user_bash | Config changes, navigation, user commands |
Events intentionally skipped: message_update and tool_execution_update (too high-frequency — token-by-token), before_provider_request (sensitive payload), context (too large), and transitional events already captured by start/shutdown pairs.
Output
Each session produces a JSONL file in .pi/logs/:
session-2025-01-15T10-30-00-000Z-abc12345.jsonlEvery line is a JSON object:
{
"ts": 1736935800000,
"model": "anthropic/claude-sonnet-4-20250514",
"cwd": "/home/user/project",
"event_type": "tool_execution_end",
"trace_id": "f1e2d3c4b5a69780",
"span_id": "a1b2c3d4e5f60789",
"parent_span_id": "9876543210fedcba",
"payload": {
"tool_call_id": "tc_001",
"tool_name": "bash",
"is_error": false,
"result_summary": "total 123\n-rw-r--r-- 1 user ..."
}
}All events carry trace_id, span_id, and parent_span_id for trace correlation — even non-span events like input, model_select, and agent_settled.
session_id and session_file are not stored per-event since each JSONL file already represents one session (encoded in the filename).
Span Hierarchy
Events that represent span boundaries include trace_id, span_id, and parent_span_id to build a trace tree:
session (root)
├── agent_run
│ ├── turn [0]
│ │ ├── message (assistant)
│ │ │ ├── tool [read]
│ │ │ ├── tool [edit]
│ │ │ └── tool [bash]
│ │ └── message (tool_result)
│ └── turn [1]
│ └── message (assistant)
└── agent_settledMessage Content
message_start and message_end events capture the LLM's actual output — text, thinking blocks, and tool call counts:
{
"event_type": "message_end",
"payload": {
"role": "assistant",
"usage": {
"input": 1558,
"output": 32,
"reasoning": 20,
"totalTokens": 1590
},
"content": {
"text": "Here's the fix for the bug in parser.js: ...",
"thinking": "The user asked about the parser. Let me check the error ...",
"tool_use_count": 2
}
}
}Text and thinking content are truncated to 2000 characters each. This lets you see what the agent actually said and thought without bloating the log files.
Metrics Snapshots
Every 5 turns, a metrics_snapshot event is emitted with running totals:
{
"event_type": "metrics_snapshot",
"payload": {
"agent_run_count": 3,
"turn_count": 17,
"avg_agent_run_duration_ms": 12400,
"avg_turn_duration_ms": 3200,
"tokens": {
"input": 45000,
"output": 12000,
"cache_read": 8000,
"cache_write": 2000,
"reasoning": 3500
},
"cost": { "total": 0.045 },
"tool_call_count": 23,
"tool_error_count": 1,
"tool_error_rate": 0.0435,
"tool_counts": { "read": 8, "edit": 6, "bash": 7, "write": 2 },
"compaction_count": 0,
"http_request_count": 17,
"http_error_count": 0,
"http_rate_limit_count": 0
}
}Token usage is normalized across providers — Anthropic (input_tokens), OpenAI (prompt_tokens), and lmstudio (input, output, cacheRead, cacheWrite, reasoning, totalTokens) are all mapped to a common shape.
Installation
Auto-discovery (recommended)
The extension is already in .pi/extensions/pi-logger/ — pi will auto-discover it when you run pi from this project directory.
For global use, copy or symlink:
mkdir -p ~/.pi/agent/extensions/pi-logger
cp .pi/extensions/pi-logger/*.js ~/.pi/agent/extensions/pi-logger/One-off test
pi -e .pi/extensions/pi-logger/index.jsConfiguration
| Environment Variable | Default | Description |
| -------------------- | ------------------------- | -------------------------------- |
| PI_OBS_LOG_DIR | .pi/logs/ (project dir) | Directory for JSONL output files |
Dashboard Ideas
The JSONL output is designed to feed three dashboard views:
Metrics View
- Total cost, token usage (input/output/cache)
- Average turn duration, average agent run duration
- Tool call count by tool, tool error rate
- Compaction count by reason (manual/threshold/overflow)
- HTTP error rate, rate-limit hit count
- Model usage distribution
- Input source distribution (interactive vs RPC vs extension)
Logs View
- User prompts with timestamps
- Tool calls with arguments and results
- LLM responses with token usage and cost
- Session events (start/shutdown/compaction)
- Security audit trail (bash commands, file writes)
- Error log (failed tool calls, HTTP errors)
Traces View
- Hierarchical span tree for each agent run
- Click into any span to see timing and details
- Filter by tool name, error status, duration thresholds
- Compare traces across turns to spot patterns
Architecture
pi-logger/
├── index.js # Extension entry — subscribes to 21 events, routes to modules
├── tracer.js # Span ID generation, trace correlation, span lifecycle
├── writer.js # Buffered JSONL writer with auto-flush and redaction
└── metrics.js # In-memory metric accumulators, periodic snapshots- Buffered writes: Events are batched (50 events or 5s interval) to minimize disk I/O.
- Redaction: Sensitive fields (api_key, authorization, etc.) are redacted. Strings over 2000 chars are truncated.
- Flush on shutdown: All buffered data is flushed on
session_shutdown. - Zero dependencies: Only uses
node:fsandnode:pathbuilt-ins. - Universal trace context: Every event carries
trace_id/span_id/parent_span_id, including non-span events likeinputandmodel_select. - Provider-agnostic metrics: Token usage is normalized across Anthropic, OpenAI, and lmstudio field shapes.
Limitations
- Per-tool duration is approximate (exact start timestamps are lost between
tool_execution_startandtool_execution_endevents). - Parallel tool execution:
tool_execution_endfires in completion order, not source order. Trace correlation viatool_call_idhandles this. - No built-in log rotation by file size (sessions rotate by default).
- No real-time dashboard — this is a data collector; build your own viewer on top of the JSONL files.
