Add automatic Logfire export for captured sessions - #28
Merged
Conversation
Mirrors every thirdeye session into Pydantic Logfire live, as OTel traces, with no separate sync step. Export is dispatched from the same Claude Code / Codex hooks that already capture events, but the actual Logfire call runs in a detached background process (thirdeye.otel_worker) so a slow or unreachable endpoint adds no latency to the tool call that triggered it. - thirdeye logfire enable/disable/status (CLI) and a settings page + button in the web UI, both persisting the gateway key/project to config.yaml - tool_call/tool_result pairs merge into one span with a real duration; everything else becomes a timeline marker under one trace per session - Homebrew formula now bundles the ui and logfire extras' resources Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each individual model call within a turn now gets its own "chat" span nested under that turn's assistant_message, carrying real per-call gen_ai.usage.* counts plus gen_ai.input.messages / gen_ai.output.messages with the actual conversation content (text, tool calls/results, and reasoning/thinking blocks) — matching the format Logfire's own GenAI instrumentations use, so traces render with a proper chat view instead of just token totals. Codex keeps local usage tracking only for now: its rollout reports far more usage entries than matching content frames, so there's no clean per-call content to attach yet. Also fixes Logfire's default scrubber redacting anything containing the word "session" (very common in a coding agent's own captured content) via a scrubbing callback that exempts only that pattern, leaving real secret patterns (password, api_key, etc.) redacted as before. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tool_call/tool_resultpairs (matched on Claude'stool_use_id/ Codex'scall_id) merge into one span with a real duration; everything else becomes a timeline marker, all under one trace per session.Store.append_event— the single call site every Claude Code / Codex hook already goes through — but the actual Logfire call (configure + a network flush) runs in a detached, unwaited-for background process (thirdeye.otel_worker), so enabling this adds no latency to tool calls.thirdeye logfire enable/disable/statusCLI commands and a settings page + Enable/Disable button in the web UI, both persisting the gateway key and project to~/.thirdeye/config.yamlindefinitely.uiandlogfireextras' resources, sobrew installgets everything without a separatepip install.Notable implementation details
otel.jsonsidecar so later hook subprocesses (which can't share in-memory state) parent correctly under it.logfireSDK: Logfire's default scrubber redacts any attribute whose key matches/session/, which was blanking the session identifier (fixed by using the OTel GenAIgen_ai.conversation.idkey instead); and Logfire's background token-check thread writes warnings straight to stderr on a bad/unreachable token even after the call that triggered it returns, which needed a permanent process-global suppression rather than a scoped one (this runs inside hook subprocesses whose stdout/stderr Claude Code may read as part of a hook decision, so it must stay silent).thirdeye logfire status/thirdeye --helpagainst it, not just transcribed from a lockfile.Test plan
pytest— 1668 passedruff check/ruff format --checkcleanthirdeye-claude-*hook binaries with Logfire enabled (bad token): stdout/stderr silent, hook returns in ~100ms,otel.jsonand job-file cleanup both confirmed asyncTestClientand a live app instance (enable persists + masks token, disable keeps token, badge updates)brew install --build-from-sourcepass (not runnable in this environment) — worth doing before merge, along with the still-placeholderurl/sha256on the top-levelthrdisource tarball pending an actual PyPI release🤖 Generated with Claude Code