feat(telemetry): add OpenTelemetry tracing with optional OTLP export to Tempo
This commit is contained in:
parent
3f1638c98b
commit
fd9f55faff
9 changed files with 601 additions and 2 deletions
26
v2/src/lib/adapters/telemetry-bridge.ts
Normal file
26
v2/src/lib/adapters/telemetry-bridge.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Telemetry bridge — routes frontend events to Rust tracing via IPC
|
||||
// No browser OTEL SDK needed (WebKit2GTK incompatible)
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
|
||||
|
||||
/** Emit a structured log event to the Rust tracing layer */
|
||||
export function telemetryLog(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: Record<string, unknown>,
|
||||
): void {
|
||||
invoke('frontend_log', { level, message, context: context ?? null }).catch(() => {
|
||||
// Swallow IPC errors — telemetry must never break the app
|
||||
});
|
||||
}
|
||||
|
||||
/** Convenience wrappers */
|
||||
export const tel = {
|
||||
error: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('error', msg, ctx),
|
||||
warn: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('warn', msg, ctx),
|
||||
info: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('info', msg, ctx),
|
||||
debug: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('debug', msg, ctx),
|
||||
trace: (msg: string, ctx?: Record<string, unknown>) => telemetryLog('trace', msg, ctx),
|
||||
};
|
||||
|
|
@ -22,6 +22,7 @@ import {
|
|||
saveAgentMessages,
|
||||
type AgentMessageRecord,
|
||||
} from './adapters/groups-bridge';
|
||||
import { tel } from './adapters/telemetry-bridge';
|
||||
|
||||
let unlistenMsg: (() => void) | null = null;
|
||||
let unlistenExit: (() => void) | null = null;
|
||||
|
|
@ -66,6 +67,7 @@ export async function startAgentDispatcher(): Promise<void> {
|
|||
switch (msg.type) {
|
||||
case 'agent_started':
|
||||
updateAgentStatus(sessionId, 'running');
|
||||
tel.info('agent_started', { sessionId });
|
||||
break;
|
||||
|
||||
case 'agent_event':
|
||||
|
|
@ -74,11 +76,13 @@ export async function startAgentDispatcher(): Promise<void> {
|
|||
|
||||
case 'agent_stopped':
|
||||
updateAgentStatus(sessionId, 'done');
|
||||
tel.info('agent_stopped', { sessionId });
|
||||
notify('success', `Agent ${sessionId.slice(0, 8)} completed`);
|
||||
break;
|
||||
|
||||
case 'agent_error':
|
||||
updateAgentStatus(sessionId, 'error', msg.message);
|
||||
tel.error('agent_error', { sessionId, error: msg.message });
|
||||
notify('error', `Agent error: ${msg.message ?? 'Unknown'}`);
|
||||
break;
|
||||
|
||||
|
|
@ -89,6 +93,7 @@ export async function startAgentDispatcher(): Promise<void> {
|
|||
|
||||
unlistenExit = await onSidecarExited(async () => {
|
||||
sidecarAlive = false;
|
||||
tel.error('sidecar_crashed', { restartAttempts });
|
||||
|
||||
// Guard against re-entrant exit handler (double-restart race)
|
||||
if (restarting) return;
|
||||
|
|
@ -176,6 +181,15 @@ function handleAgentEvent(sessionId: string, event: Record<string, unknown>): vo
|
|||
numTurns: cost.numTurns,
|
||||
durationMs: cost.durationMs,
|
||||
});
|
||||
tel.info('agent_cost', {
|
||||
sessionId,
|
||||
costUsd: cost.totalCostUsd,
|
||||
inputTokens: cost.inputTokens,
|
||||
outputTokens: cost.outputTokens,
|
||||
numTurns: cost.numTurns,
|
||||
durationMs: cost.durationMs,
|
||||
isError: cost.isError,
|
||||
});
|
||||
if (cost.isError) {
|
||||
updateAgentStatus(sessionId, 'error', cost.errors?.join('; '));
|
||||
notify('error', `Agent failed: ${cost.errors?.[0] ?? 'Unknown error'}`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue