Skip to content

Telemetry reference

genai-otel-bridge emits two planes of telemetry: product (operational signals republished from the upstream AI platforms — Portkey, LangSmith) and self-observability (the bridge's own health). It is content-free by design: no prompts, completions, inputs, or outputs ever leave the bridge.

Product metric/log names are config-derived — the {loops.*.metric_prefix} and label-key placeholders below resolve from your config. With the README's default config, {loops.analytics.metric_prefix} is portkey_api, so {loops.analytics.metric_prefix}_requests becomes portkey_api_requests.

The catalogue below is generated from the code (make generate) and gate-checked in CI, so it cannot drift from what the binary actually emits.

Product telemetry

Metrics

NameKindUnitLabels / attributesDepends onDescription
{loops.analytics.metric_prefix}_cost_usdgaugeUSDloops.analytics.graphs includes 'cost'request cost per bucket (US dollars)
{loops.analytics.metric_prefix}_cost_usd_by_metadatagaugeUSDmetadata_key, metadata_valueloops.groups settings.emit_cost=true with a metadata dimensioncost per metadata-dimension value (÷100 from Portkey cents)
{loops.analytics.metric_prefix}_cost_usd_by_modelgaugeUSDai_modelloops.groups settings.emit_cost=truecost per AI model (÷100 from Portkey cents)
{loops.analytics.metric_prefix}_errorsgauge1loops.analytics.graphs includes 'errors'error count per bucket
{loops.analytics.metric_prefix}_latency_secondsgaugesquantileloops.analytics.graphs includes 'latency'request latency statistic per bucket; one series per quantile (avg/p50/p90/p99)
{loops.analytics.metric_prefix}_requestsgauge1loops.analytics.graphs includes 'requests'request count per bucket
{loops.analytics.metric_prefix}_requests_by_metadatagauge1metadata_key, metadata_valueloops.groups enabled with a metadata dimensionrequest count per metadata-dimension value
{loops.analytics.metric_prefix}_requests_by_modelgauge1ai_modelloops.groups.enabled=truerequest count per AI model (groups ai-models dimension)
{loops.analytics.metric_prefix}_requests_by_promptgauge1promptloops.groups settings.emit_prompts=truerequest count per saved-prompt id (content-free — the label is a prompt ID, not text)
{loops.analytics.metric_prefix}_tokensgauge1token_typeloops.analytics.graphs includes 'tokens'token units per bucket; split by token_type (total/input/output) — do NOT bare-sum across token_type
{loops.analytics.metric_prefix}_usersgauge1loops.analytics.graphs includes 'users'distinct-user count per bucket
{loops.sessions.metric_prefix}_completion_cost_usdgaugeUSDsessionloops.sessions.enabled=trueper-session completion cost (US dollars)
{loops.sessions.metric_prefix}_completion_tokensgauge1sessionloops.sessions.enabled=trueper-session completion (output) token count
{loops.sessions.metric_prefix}_cost_usdgaugeUSDsessionloops.sessions.enabled=trueper-session total cost (US dollars)
{loops.sessions.metric_prefix}_error_rategauge1sessionloops.sessions.enabled=trueper-session error rate (ratio)
{loops.sessions.metric_prefix}_feedback_countgauge1session, feedback_keyloops.sessions settings.emit_feedback=trueper-session numeric feedback sample count; one series per feedback_key
{loops.sessions.metric_prefix}_feedback_scoregauge1session, feedback_keyloops.sessions settings.emit_feedback=trueper-session numeric feedback aggregate; one series per feedback_key
{loops.sessions.metric_prefix}_first_token_secondsgaugessession, quantileloops.sessions.enabled=trueper-session time-to-first-token; one series per quantile (p50/p99); absent when not streaming
{loops.sessions.metric_prefix}_latency_secondsgaugessession, quantileloops.sessions.enabled=trueper-session run latency; one series per quantile (p50/p99)
{loops.sessions.metric_prefix}_prompt_cost_usdgaugeUSDsessionloops.sessions.enabled=trueper-session prompt cost (US dollars)
{loops.sessions.metric_prefix}_prompt_tokensgauge1sessionloops.sessions.enabled=trueper-session prompt (input) token count
{loops.sessions.metric_prefix}_runsgauge1sessionloops.sessions.enabled=trueper-session run count (aggregate-now snapshot)
{loops.sessions.metric_prefix}_streaming_rategauge1sessionloops.sessions.enabled=trueper-session streaming rate (ratio)
{loops.sessions.metric_prefix}_tokensgauge1sessionloops.sessions.enabled=trueper-session total token count
{loops.usage.metric_prefix}_usage_spansgauge1session, retention_tierloops.usage settings.emit_span_counts=truePLATFORM cost driver: spans (all runs) ingested per project = the storage/volume driver; one series per retention_tier
{loops.usage.metric_prefix}_usage_tracesgauge1session, retention_tierloops.usage.enabled=truePLATFORM cost driver: traces (root runs) ingested per project = the LangSmith billing unit; one series per retention_tier

Logs

NameKindUnitLabels / attributesDepends onDescription
langsmith runs recordrun_type, statusloops.runs.enabled=trueone content-free OTLP log per run: type/status/latency/tokens/cost as structured metadata; no inputs/outputs
portkey logs_export recordai_org, ai_model, response_status_code, api_key_use_caseloops.logs_export.enabled=trueone content-free OTLP log per request: status/latency/tokens/cost as structured metadata; no prompt/response bodies

Self-observability

Metrics

NameKindUnitLabels / attributesDepends onDescription
genai_otel_bridge_auth_errors_totalcounter1loop, sourceupstream source API responded 401/403 — a credential failure
genai_otel_bridge_bucket_revised_after_settle_age_secondshistogramsloopage (now − bucketEnd) of a settled bucket observed to change after bucket_settle
genai_otel_bridge_bucket_revised_after_settle_totalcounter1loopsettled buckets observed to change value after settle (late arrival beyond bucket_settle)
genai_otel_bridge_emit_errors_totalcounter1loop, kindemit errors by kind
genai_otel_bridge_emit_partial_success_rejected_totalcounter1planedata points or log records the gateway rejected via an OTLP 200 partial_success response (rejected_data_points/rejected_log_records)
genai_otel_bridge_emit_request_duration_secondshistogramsplane, status_classoutbound OTLP emit request latency (per POST attempt to /v1/metrics or /v1/logs)
genai_otel_bridge_emitted_logs_totalcounter1looplog records emitted (logs-export loop)
genai_otel_bridge_emitted_totalcounter1loopsamples emitted
genai_otel_bridge_guard_dropped_totalcounter1loopdata points or log records dropped by the governance guard
genai_otel_bridge_last_success_timestamp_secondsgaugesloopunix time of last successful emit
genai_otel_bridge_loop_degradedgauge1loop, reason1 while a loop is degraded (reason attribute), 0 after the clearing commit
genai_otel_bridge_new_label_values_totalcounter1seriesnew label-value combinations seen per series
genai_otel_bridge_queue_depthgauge1loopper-loop queue depth
genai_otel_bridge_samples_capped_totalcounter1loop, reasonsamples suppressed by the DPM cap (coalesced last-write-wins per series-minute)
genai_otel_bridge_samples_skipped_totalcounter1loop, reasondata points or log records skipped with a counted gap
genai_otel_bridge_source_graph_unavailable_totalcounter1loop, graphconfigured source graph skipped on a poll due to a 404 (capability/permission/absence)
genai_otel_bridge_upstream_request_duration_secondshistogramstarget, method, status_classoutbound request latency to upstream source APIs (time to response headers)
genai_otel_bridge_window_lag_secondsgaugesloopnow minus the watermark frontier

Traces

NameKindUnitLabels / attributesDepends onDescription
genai-otel-bridge/selfobs (tracer scope)self-observability tracing enabled in configthe bridge's own internal spans, exported via the self-observability OTLP TracerProvider (internal/selfobs/tracing.go)