Convert the src/ai/* raw-OTel spans to tracing, drop the cx: &Context first param (36 sites: openrouter.rs 6, ollama.rs 18, llm_client.rs 5, llamacpp.rs 6), and carry the parent span across the streaming spawn boundaries.
Replace start_request_span / manual set_attribute with #[instrument] + tracing fields.
Drop cx: &Context from the LlmClient trait methods and every call site.
At each streaming tokio::spawn boundary (critical: insight_chat::chat_turn_stream_inner), carry the parent span (.instrument(parent) or span.enter()).
Keep llm.model / llm.backend / traceparent injection behavior identical in the exported spans.
Resolution
Confirmation the LLM/streaming paths emit equivalent spans with no context params and the streaming trace is no longer orphaned.
Part of #135
## Work
Convert the `src/ai/*` raw-OTel spans to `tracing`, drop the `cx: &Context` first param (36 sites: `openrouter.rs` 6, `ollama.rs` 18, `llm_client.rs` 5, `llamacpp.rs` 6), and carry the parent span across the streaming spawn boundaries.
- Replace `start_request_span` / manual `set_attribute` with `#[instrument]` + `tracing` fields.
- Drop `cx: &Context` from the `LlmClient` trait methods and every call site.
- At each streaming `tokio::spawn` boundary (critical: `insight_chat::chat_turn_stream_inner`), carry the parent span (`.instrument(parent)` or `span.enter()`).
- Keep `llm.model` / `llm.backend` / traceparent injection behavior identical in the exported spans.
## Resolution
Confirmation the LLM/streaming paths emit equivalent spans with no context params and the streaming trace is no longer orphaned.
cameron
added a new dependency 2026-09-29 14:57:54 +00:00
Resolved on feature/tracing-migration (commit ea1d425).
What was done
LLM layer has no context params.cx: &Context is gone from the LlmClient trait, all three providers (openrouter / llamacpp / ollama, including Ollama's inherent generate* / generate_embedding(s) methods) and every call site. otel::start_llm_request_span(provider, operation, model) now returns a tracing::Span (llm.request, otel.kind = "client", llm.provider / llm.operation / llm.model) that parents on the caller's ambient span. Token counts, cost, http.status_code and status go through OpenTelemetrySpanExt::set_attribute / set_status, so the attribute set is unchanged. with_traceparent injects the llm.request span's own context.
Feature spans in src/ai/* are on tracing. All 30 raw-OTel span sites (AI handlers, TTS handlers, insight generate / agentic, RAG + context gatherers, chat turn, persona chat, daily summaries) are now #[tracing::instrument(name = ..., skip_all)] or info_span!. For the mid-function loop spans (ai.chat.loop, ai.agentic.loop), each LLM and tool await is wrapped in .instrument(loop_span.clone()). parent_cx params that only carried the parent are dropped.
Spawn boundaries carry the parent.
chat_turn_stream creates ai.chat.turn.stream eagerly inside the request span, and chat_turn_stream_inner spawns with .instrument(span).
ai.chat.turn.execute and ai.persona_chat.turn.execute are created before their spawns, so they now land in the dispatching request's trace instead of a fresh root.
The background insight jobs (generate and agentic) and the TTS job spawn with .instrument(Span::current()).
DAO calls are unchanged in shape. They still take &Context, now derived from the tracing span via span.context(), so they nest correctly. Removing those is #141's job.
Verification
New tests/otel_llm_spans.rs uses a mock OpenAI-compatible server on a multi-thread runtime. Blocking and streaming llm.request spans, called inside a tokio::spawn(...instrument(turn)), share the turn's trace_id and parent on it. Kind=Client, provider/operation/model/http.status_code all match, and the outgoing traceparent names the llm.request span. Mutation check: removing the .instrument fails the test (orphaned trace).
cargo clippy --all-targets: 0 warnings. Full suite green: 690 lib + 775 integration + 3 OTel regression tests + doc-tests.
Behaviour changes for #143 to check against the baseline
The 202 agentic insight job, async chat turns and the streaming chat turn are now one trace with their request (the baseline had them separate or orphaned).
Spans are now subject to EnvFilter. With RUST_LOG=warn the info-level spans (root, feature and llm.request) are disabled, so they aren't exported and no traceparent is sent. Prod runs at info, but see the new fog item.
tracing-opentelemetry adds its default code.* / thread.* attributes. The required attributes are unchanged.
Resolved on `feature/tracing-migration` (commit ea1d425).
**What was done**
- **LLM layer has no context params.** `cx: &Context` is gone from the `LlmClient` trait, all three providers (openrouter / llamacpp / ollama, including Ollama's inherent `generate*` / `generate_embedding(s)` methods) and every call site. `otel::start_llm_request_span(provider, operation, model)` now returns a `tracing::Span` (`llm.request`, `otel.kind = "client"`, `llm.provider` / `llm.operation` / `llm.model`) that parents on the caller's ambient span. Token counts, cost, `http.status_code` and status go through `OpenTelemetrySpanExt::set_attribute` / `set_status`, so the attribute set is unchanged. `with_traceparent` injects the `llm.request` span's own context.
- **Feature spans in `src/ai/*` are on tracing.** All 30 raw-OTel span sites (AI handlers, TTS handlers, insight generate / agentic, RAG + context gatherers, chat turn, persona chat, daily summaries) are now `#[tracing::instrument(name = ..., skip_all)]` or `info_span!`. For the mid-function loop spans (`ai.chat.loop`, `ai.agentic.loop`), each LLM and tool await is wrapped in `.instrument(loop_span.clone())`. `parent_cx` params that only carried the parent are dropped.
- **Spawn boundaries carry the parent.**
- `chat_turn_stream` creates `ai.chat.turn.stream` eagerly inside the request span, and `chat_turn_stream_inner` spawns with `.instrument(span)`.
- `ai.chat.turn.execute` and `ai.persona_chat.turn.execute` are created before their spawns, so they now land in the dispatching request's trace instead of a fresh root.
- The background insight jobs (generate and agentic) and the TTS job spawn with `.instrument(Span::current())`.
- **DAO calls are unchanged in shape.** They still take `&Context`, now derived from the tracing span via `span.context()`, so they nest correctly. Removing those is #141's job.
**Verification**
- New `tests/otel_llm_spans.rs` uses a mock OpenAI-compatible server on a multi-thread runtime. Blocking and streaming `llm.request` spans, called inside a `tokio::spawn(...instrument(turn))`, share the turn's trace_id and parent on it. Kind=Client, provider/operation/model/http.status_code all match, and the outgoing `traceparent` names the `llm.request` span. Mutation check: removing the `.instrument` fails the test (orphaned trace).
- `cargo clippy --all-targets`: 0 warnings. Full suite green: 690 lib + 775 integration + 3 OTel regression tests + doc-tests.
**Behaviour changes for #143 to check against the baseline**
- The 202 agentic insight job, async chat turns and the streaming chat turn are **now one trace** with their request (the baseline had them separate or orphaned).
- Spans are now subject to `EnvFilter`. With `RUST_LOG=warn` the info-level spans (root, feature and `llm.request`) are disabled, so they aren't exported and no traceparent is sent. Prod runs at `info`, but see the new fog item.
- tracing-opentelemetry adds its default `code.*` / `thread.*` attributes. The required attributes are unchanged.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #135
Work
Convert the
src/ai/*raw-OTel spans totracing, drop thecx: &Contextfirst param (36 sites:openrouter.rs6,ollama.rs18,llm_client.rs5,llamacpp.rs6), and carry the parent span across the streaming spawn boundaries.start_request_span/ manualset_attributewith#[instrument]+tracingfields.cx: &Contextfrom theLlmClienttrait methods and every call site.tokio::spawnboundary (critical:insight_chat::chat_turn_stream_inner), carry the parent span (.instrument(parent)orspan.enter()).llm.model/llm.backend/ traceparent injection behavior identical in the exported spans.Resolution
Confirmation the LLM/streaming paths emit equivalent spans with no context params and the streaming trace is no longer orphaned.
Resolved on
feature/tracing-migration(commit ea1d425).What was done
cx: &Contextis gone from theLlmClienttrait, all three providers (openrouter / llamacpp / ollama, including Ollama's inherentgenerate*/generate_embedding(s)methods) and every call site.otel::start_llm_request_span(provider, operation, model)now returns atracing::Span(llm.request,otel.kind = "client",llm.provider/llm.operation/llm.model) that parents on the caller's ambient span. Token counts, cost,http.status_codeand status go throughOpenTelemetrySpanExt::set_attribute/set_status, so the attribute set is unchanged.with_traceparentinjects thellm.requestspan's own context.src/ai/*are on tracing. All 30 raw-OTel span sites (AI handlers, TTS handlers, insight generate / agentic, RAG + context gatherers, chat turn, persona chat, daily summaries) are now#[tracing::instrument(name = ..., skip_all)]orinfo_span!. For the mid-function loop spans (ai.chat.loop,ai.agentic.loop), each LLM and tool await is wrapped in.instrument(loop_span.clone()).parent_cxparams that only carried the parent are dropped.chat_turn_streamcreatesai.chat.turn.streameagerly inside the request span, andchat_turn_stream_innerspawns with.instrument(span).ai.chat.turn.executeandai.persona_chat.turn.executeare created before their spawns, so they now land in the dispatching request's trace instead of a fresh root..instrument(Span::current()).&Context, now derived from the tracing span viaspan.context(), so they nest correctly. Removing those is #141's job.Verification
tests/otel_llm_spans.rsuses a mock OpenAI-compatible server on a multi-thread runtime. Blocking and streamingllm.requestspans, called inside atokio::spawn(...instrument(turn)), share the turn's trace_id and parent on it. Kind=Client, provider/operation/model/http.status_code all match, and the outgoingtraceparentnames thellm.requestspan. Mutation check: removing the.instrumentfails the test (orphaned trace).cargo clippy --all-targets: 0 warnings. Full suite green: 690 lib + 775 integration + 3 OTel regression tests + doc-tests.Behaviour changes for #143 to check against the baseline
EnvFilter. WithRUST_LOG=warnthe info-level spans (root, feature andllm.request) are disabled, so they aren't exported and no traceparent is sent. Prod runs atinfo, but see the new fog item.code.*/thread.*attributes. The required attributes are unchanged.