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 (just gen) and gate-checked in CI, so it cannot drift from what the binary actually emits.

OTLP resource attributes and Prometheus labels

Product telemetry uses ProductIdentity() for its resource identity: it stamps service.name, service.namespace, and deployment.environment.name. It deliberately does not stamp service.version. The bridge sets service.version only on self-observability metrics and traces, from the build version configured in provider.go and tracing.go.

The resulting Prometheus labels are determined by the receiving OTLP gateway, not by this catalogue alone. With Mimir's default OTLP mapping, service.name and service.namespace are combined into the job label, service.instance.id becomes instance, and other resource attributes are represented on target_info. A per-series label such as service_version requires the tenant to opt in through promote_resource_attributes; it is not a default convention. This documents the receiver convention, not a claim that this path has been deployed or observed live.

Product telemetry

Metrics

NameKindUnitLabels / attributesDepends onDescription
{loops.analytics.metric_prefix}_cost_usdgaugeUSD—loops.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}_errorsgauge1—loops.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}_requestsgauge1—loops.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}_usersgauge1—loops.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 record——run_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 record——ai_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, source—upstream source API responded 401/403 — a credential failure
genai_otel_bridge_bucket_revised_after_settle_age_secondshistogramsloop—base2 exponential histogram: age (now − bucketEnd) of a settled bucket observed to change after bucket_settle
genai_otel_bridge_bucket_revised_after_settle_totalcounter1loop—settled buckets observed to change value after settle (late arrival beyond bucket_settle)
genai_otel_bridge_emit_errors_totalcounter1loop, kind—emit errors by kind
genai_otel_bridge_emit_partial_success_rejected_totalcounter1plane—data 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_class—base2 exponential histogram: outbound OTLP emit request latency (per POST attempt to /v1/metrics or /v1/logs)
genai_otel_bridge_emitted_logs_totalcounter1loop—log records emitted (logs-export loop)
genai_otel_bridge_emitted_totalcounter1loop—samples emitted
genai_otel_bridge_guard_dropped_totalcounter1loop—data points or log records dropped by the governance guard
genai_otel_bridge_last_success_timestamp_secondsgaugesloop—unix time of last successful emit
genai_otel_bridge_loop_degradedgauge1loop, reason—1 while a loop is degraded (reason attribute), 0 after the clearing commit
genai_otel_bridge_new_label_values_totalcounter1series—new label-value combinations seen per series
genai_otel_bridge_queue_depthgauge1loop—per-loop queue depth
genai_otel_bridge_samples_capped_totalcounter1loop, reason—samples suppressed by the DPM cap (coalesced last-write-wins per series-minute)
genai_otel_bridge_samples_skipped_totalcounter1loop, reason—data points or log records skipped with a counted gap
genai_otel_bridge_source_capability_totalcounter1loop, graph, state—source capability or permission condition by graph and closed state: endpoint-absent, plan-unsupported, permission-denied, no-data, transient-404, or schema-changed. endpoint-absent and plan-unsupported are enumerated but currently producerless because one 404 cannot distinguish them from a transient failure; steady versus intermittent increments remain a query-time judgement
genai_otel_bridge_source_data_incomplete_totalcounter1loop, reason—source data was incomplete for a closed reason: window_truncated, sessions_truncated, backfill_skipped, window_oversize, span_stats_unavailable, duplicate_dimension, trace_id_unparsed, line_oversize, line_unparseable, export_stuck, or export_failed
genai_otel_bridge_upstream_request_duration_secondshistogramstarget, method, status_class—base2 exponential histogram: outbound request latency to upstream source APIs (time to response headers)
genai_otel_bridge_window_lag_secondsgaugesloop—now 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)