Originally created by: Akarsh-Hegde
End-to-end observability across all three Meridian runtimes (Rust daemon, Python agents, Node MCP/UI), backed by a local OpenObserve instance and stitched together via W3C trace context.
Filter by session_id in OpenObserve and you see the full journey of a screenpipe frame: ETL block close → Python rule firing → stage2/stage3 LLM decision → Jira sync — all in a single trace tree.
src/observability.rs): layered tracing-subscriber (stderr pretty + ~/.meridian/logs/*.jsonl daily-rotated + OTLP/HTTP exporter). #[tracing::instrument] on run_etl, close_block, extract_block_context, upsert_open_block, cleanup_incomplete_runs, categorize, settle_*, three classifier backends, jira provider. close_block persists the active span's traceparent into app_sessions.traceparent so downstream Python agents inherit the trace.services/agents/observability.py): one setup(agent_name) call wires OTel TracerProvider, OTLPSpanExporter, LoggingInstrumentor, and python-json-logger. Spans on tagger.run_once, tagger.session, tagger.rules.fire, tagger_daemon.tick, stage2.match, stage3.decide, jira_keeper.sync, jira.api.update, embeddings.encode_batch. _tag_session reads traceparent from the DB row and continues the Rust trace.packages/meridian-mcp/): pino → stderr (stdout reserved for JSON-RPC; verified clean), OTel SDK exports spans, every tool dispatch wrapped in mcp.tool.<name>.ui/): instrumentation.ts register hook + lib/observability.ts. Each /api/* route handler wrapped in api.<name> span, console.error replaced with logger.error.010_traceparent.sql: adds traceparent TEXT to app_sessions, agent_runs, ticket_links.# Start OpenObserve (one-off install — single binary at ~/.openobserve/, launchd-managed)
launchctl load ~/Library/LaunchAgents/ai.openobserve.plist
# Run the daemon with OTLP enabled
export MERIDIAN_OO_AUTH=$(cat ~/.openobserve/.auth_token)
./target/release/meridian
# Browse logs + traces
open http://localhost:5080
Useful queries:
SELECT service_name, operation_name FROM "default" WHERE session_id = '<id>' — full cross-process journeyWHERE trace_id = '<id>' — single trace treecargo build --bin meridian cleancargo clippy --bin meridian -- -D warnings cleancargo test — all suites passcd packages/meridian-mcp && npm run build cleancd ui && npm run build cleanapp_sessions.traceparent populated with valid W3C string (00-{trace}-{span}-01)extract_parent_context unit-verified: child span inherits Rust trace_id 755c4549160e7ae61eb865531bca6dee exactlysession_id returns spans from both meridian-rust and meridian-taggerreference_parent_trace_id link in the UI) wasn't observed in the live smoke because both sessions with traceparent populated so far are too short (5-7s) and get filtered as trivial-overhead before reaching _tag_session. The extraction code path is unit-verified; it will fire naturally as soon as a longer session goes through.ImportError in services/agents/jira_keeper.py:20 (importing JIRA_STATE_FILE, CURRENT_CONTEXT_FILE, JIRA_DIR from agents.config) is not introduced by this PR — confirmed present on main before this branch. Worth a separate fix.github.rs and linear.rs provider stubs are uninstrumented because they have no outbound calls yet.🤖 Generated with Claude Code
Tickets: #198
Tickets: #213
Tickets: #25
Tickets: #258
Tickets: #273
Tickets: #288
Tickets: #298
Tickets: #310
Tickets: #345
Tickets: #346
Tickets: #369
Tickets: #372
Tickets: #393
Tickets: #450
Tickets: #458
Tickets: #473
Tickets: #475
Tickets: #508
Ticket changed by: Akarsh-Hegde