Skip to content

Telemetry & Observability

Every agent action produces OpenTelemetry spans — LLM calls, tool invocations, subagent delegations, chain/graph steps, retrieval. Spans land in an in-memory exporter for the live view, a local SQLite database for historical query, and optionally an external OTLP endpoint.

sequenceDiagram participant Agent participant SDK as OTel SDK (OpenLLMetry) participant Mem as InMemorySpanExporter participant DB as TelemetryPersistenceService participant Ext as OTLP HTTP exporter Note over Agent, Ext: Init SDK->>SDK: setupInstrumentation()<br/>appName: "codebuddy-agent" SDK->>Mem: Attach + SimpleSpanProcessor SDK->>DB: Open SQLite, create tables, prune old Note over Agent, Ext: Runtime Agent->>SDK: LLM / tool / subagent SDK->>SDK: Auto-instrument LangChain + LangGraph SDK->>Mem: Completed span (live view) SDK->>DB: Persist (span_id, trace_id, attrs, events) opt OTLP enabled SDK->>Ext: HTTP batch export<br/>(DNS-pinned to resolved IP) end

OpenLLMetry auto-instruments LangChain + LangGraph:

  • LLM calls — model, provider, token counts (input / output / cache_read / cache_creation), latency, streaming status
  • Tool invocations — tool name, input, output, duration
  • Subagent delegations — subagent type, description, result
  • Chain / graph steps — LangGraph node transitions, state changes
  • Retrieval — vector search queries, result counts

Each span carries: span_id, trace_id, parent_id, name, kind, start/end (ns), status, attributes (JSON), events (JSON).

SQLite at ~/.codebuddy/telemetry/traces.db via sql.js (WASM).

CREATE TABLE spans (
span_id TEXT PRIMARY KEY,
trace_id TEXT NOT NULL,
parent_id TEXT,
name TEXT NOT NULL,
kind INTEGER,
start_time_s INTEGER,
start_time_ns INTEGER,
end_time_s INTEGER,
end_time_ns INTEGER,
status_code INTEGER,
status_message TEXT,
attributes TEXT, -- JSON
events TEXT, -- JSON
links TEXT, -- JSON
session_id TEXT,
created_at TEXT
);

Indexed on trace_id, created_at, session_id, name.

Batched writes: buffered → flushed every 5 s or at 50 spans, whichever first. Transactional INSERT OR IGNORE.

Auto-pruning: spans older than retentionDays (default 7) removed on startup.

MethodSourceReturns
getTraces()In-memory exporterLive spans from current session
getPersistedTraces(days, limit)SQLiteHistorical spans with computed duration
getSessions()SQLiteDistinct session IDs + span counts + date ranges
getMetrics()PerformanceProfilerRuntime performance data
getRecentLogs()LoggerLast 1000 log entries from circular buffer

Set an endpoint:

{ "codebuddy.telemetry.otlpEndpoint": "https://your-langfuse.example/api/public/otel" }

Or the dedicated Langfuse settings for the vendor-specific auth flow — see the code base for the exact key names (codebuddy.telemetry.langfuse.*).

DNS-rebinding pinning: OTLP exports use validateAndPinOutboundUrl (landed 2026-07-10) — the resolved IP is pinned to the socket connect via a custom dns.LookupFunction. Closes the check-then-connect window where a malicious resolver could flip a public answer to a private IP.

Compatible platforms:

PlatformEndpoint
Langfusehttps://<host>/api/public/otel
LangSmithhttps://api.smith.langchain.com
Jaeger (OTLP)http://localhost:4318
Grafana TempoYour Tempo OTLP HTTP endpoint

Any OTLP HTTP endpoint works.

Beyond spans, CodeBuddy writes scores to Langfuse over a separate REST path (/api/public/ingestion) — scores don’t flow through OTLP. Batched, fail-open, and DNS-pinned via validateAndPinOutboundUrl. Recorded scores include:

  • User feedback — thumbs up/down on an answer from the chat UI.
  • Diff acceptance — whether proposed changes were applied or rejected.
  • Test outcomes and other post-session derived metrics.
  • LLM-as-judge — an optional model-scored quality pass.

Enable with the Langfuse settings below; the secret key is stored in the OS keychain via CodeBuddy: Set Langfuse Secret Key, not in settings.json.

codebuddy.telemetry.anonymousUserId (default false) attaches a non-reversible, per-install id (sha256 of a local random nonce) to traces so you can group a session’s activity without identifying the user. Opt-in only.

Every log event carries the current OTel traceId so logs + traces correlate.

interface ILogEvent {
timestamp: string;
level: string;
module: string;
message: string;
data: unknown;
sessionId: string;
traceId: string; // matches the active OTel trace
}

Destinations:

  • VS Code Output channel (“CodeBuddy”)
  • Log files — .codebuddy/logs/codebuddy-*.log (structured JSON, one event per line)
  • Circular buffer — last 1000 entries, accessible via getRecentLogs()
  • Telemetry — optionally forwarded to the persistence service

Rotation & retention. The file sink is a buffered async write stream (not a per-line appendFileSync), rolls at 5 MB, and prunes on three axes: age (7 days) → count (10 files) → total size (50 MB). An environment gate keeps shipped builds from ever creating .codebuddy/logs (file logging is source/dev only, or when codebuddy.debug.fileLogging is on); the sink fails open on stream error and drains on deactivate().

SettingDefaultPurpose
codebuddy.telemetry.persistTracestruePersist traces to SQLite across sessions
codebuddy.telemetry.retentionDays7Auto-prune after N days (1–90)
codebuddy.telemetry.otlpEndpoint""Generic OTLP HTTP endpoint (empty = disabled)
codebuddy.telemetry.langfuse.enabledfalseEnable Langfuse observability + scores
codebuddy.telemetry.langfuse.publicKey""Langfuse public key (pk-lf-…)
codebuddy.telemetry.langfuse.baseUrl"https://cloud.langfuse.com"Langfuse host (cloud or self-hosted)
codebuddy.telemetry.langfuse.allowInsecurefalseAllow non-HTTPS Langfuse (self-hosted only)
codebuddy.telemetry.anonymousUserIdfalseAttach an anonymous per-install id (opt-in)

The Langfuse secret key is set via CodeBuddy: Set Langfuse Secret Key (kept in the OS keychain), not as a setting.

PackageVersionRole
@opentelemetry/api^1.9.0OTel API
@opentelemetry/sdk-trace-base^2.5.1Trace SDK
@opentelemetry/exporter-metrics-otlp-http^0.212.0OTLP HTTP export
@traceloop/node-server-sdk^0.22.7Auto-instrument LangChain/LangGraph
  • Security — SSRF + DNS-rebinding gates that OTLP export inherits
  • Cost tracking — how token counts on spans get priced