Skip to main content

Overview

The BigQuery plugin stores a structured record of every request that flows through Bifrost in a Google BigQuery table. Each completed trace becomes a single row — provider, model, token usage, cost, latency breakdown, governance attribution, and (optionally) the full conversation — so you can run SQL analytics, build dashboards, and retain history for as long as your BigQuery dataset keeps it. Unlike the OTel and Datadog connectors, which stream spans to a tracing backend, the BigQuery plugin writes a flat, query-optimized table. It is ideal when you want to own the data, join it against your warehouse, or report on cost and usage with plain SQL.
The BigQuery plugin is a Bifrost Enterprise feature.
Key benefits:
  • SQL-native analytics — Query traces directly, or join them against the rest of your warehouse.
  • One row per request — A wide, denormalized table that is easy to aggregate, with no span-tree traversal.
  • Cost & token attribution — Per-request cost plus fine-grained token breakdowns, attributable to teams, customers, virtual keys, and users.
  • Long-term retention — Keep history far beyond what a logs database or APM retention window allows.
  • Zero request-latency impact — Traces are buffered and flushed asynchronously in the background.

Authentication

The plugin supports two ways to authenticate with Google Cloud:
When you provide a service_account_key, pass it as an env.VAR_NAME reference rather than pasting the raw JSON into stored configuration. The referenced environment variable should contain the unescaped service account JSON (for example '{"project_number": ...}'). The resolved value is never persisted, and API responses redact it.
The service account (whether from ADC or an explicit key) needs permission to read/write the target table and, if create_table_if_not_exists is enabled, to create datasets and tables — for example the roles/bigquery.dataEditor role on the dataset (plus roles/bigquery.user on the project for job execution).

Configuration

  1. Open the Observability page in the Bifrost dashboard.
  2. Select the BigQuery connector.
  3. Fill in the configuration fields:
    • GCP Project ID — the project that contains your dataset (required).
    • Dataset ID and Table ID — defaults are bifrost_traces and traces.
    • Location — the dataset region (e.g. US, EU, us-central1).
    • Authentication ModeApplication Default Credentials (recommended) or Service Account Key.
    • Disable Content Logging, Auto-Create Table, Request Headers, Flush Interval, Buffer Size, and Custom Labels as needed.
  4. Toggle Enabled on and click Save BigQuery Configuration. BigQuery connector configuration in the Observability page
If you turn Auto-Create Table off, the form exposes a View table schema dialog with a ready-to-run CREATE TABLE statement (including partitioning and clustering) that you can copy and run in BigQuery yourself. See Table Schema below.

Table Schema

The traces table is partitioned by DATE(timestamp) and clustered by provider, model, virtual_key_id. Partitioning keeps time-range queries cheap (BigQuery prunes partitions), and clustering speeds up filtering by provider, model, or virtual key. Each row corresponds to one trace — one full LLM request lifecycle. The columns you will reach for most are timestamp, provider, model, status, latency_ms, total_tokens, and cost, plus the governance columns (team_name, customer_name, virtual_key_name) for attribution. Expand a category below for the full column list.
If you prefer to manage the table yourself (with create_table_if_not_exists set to false), create the dataset and table before enabling the plugin. The UI’s View table schema dialog generates the complete statement for your exact project/dataset/table/location. The outline below shows the structure — expand the accordions above for the full column list:
Letting the plugin auto-create the table (the default) guarantees the schema stays in sync as new columns are added across Bifrost releases. If you manage the table manually, you may need to add new columns after an upgrade.

Content & Header Capture

The plugin lets you control how much request detail lands in BigQuery:
  • disable_content_logging — Set to true to omit conversation content. The input_history and output_message columns are left empty, while all metadata (tokens, cost, latency, attribution) is still recorded. Use this for privacy-sensitive workloads.
  • request_headers — A list of header-name patterns whose values are captured into the request_headers column as a JSON map. Supports exact names (X-Tenant-ID) and wildcards (x-custom-*, or * for all headers).
    Using * captures all request headers, including sensitive ones like Authorization. Prefer explicit names or scoped wildcards.
  • custom_labels — Static key-value pairs attached to every row in the labels column (JSON). Values support env.VAR_NAME, which is handy for environment or region tags.
  • dimensions — Per-request dimensions sent via x-bf-dim-* headers are captured automatically into the dimensions column (JSON). No configuration required.

Example Queries

Because each trace is a single row, common analytics are plain SQL aggregations. Replace my-gcp-project.bifrost_traces.traces with your project, dataset, and table. Total cost by team over the last 7 days:
p95 latency by model (today):
Error rate by provider (last 24 hours):
Daily token usage trend:
Always filter on timestamp (the partition key) to prune partitions and keep query costs low.

Plugin Span Filtering

By default every plugin’s pre- and post-hook execution contributes its latency to the stored row. Use plugin_span_filter inside the BigQuery plugin config to control which plugin spans count toward the flattened latency columns (plugin_pre_latency_ms / plugin_post_latency_ms). Via config.json (inside the BigQuery plugin config):
Via the UI: Open the Observability page, select the BigQuery connector, and click Configure Plugin Tracing. Toggle individual plugins on or off and save. Filter modes: Plugin names: list each plugin using the exact name shown in the Configure Plugin Tracing sheet. Note that some plugins are registered under a different name than their config key — for example the enterprise prompts and governance plugins appear as enterprise-prompts and enterprise-governance. Common names include telemetry, logging, otel, semantic_cache, compat, maxim, enterprise-prompts, enterprise-governance, datadog, bigquery, and guardrails.
Unlike the OTEL and Datadog connectors, BigQuery has no span tree — a filtered plugin span’s latency is simply omitted from the flattened row, and no re-parenting occurs. Each observability connector has its own independent plugin_span_filter. It follows the standard plugin config precedence rules; to make a config.json value override UI-saved DB settings on restart, set a higher version on the BigQuery plugin entry (e.g. "version": 2). See Plugin Versioning for details.

Troubleshooting

Table or dataset not found

Symptom: Errors about a missing dataset or table on startup. Cause: create_table_if_not_exists is false and the table does not exist, or the service account lacks create permissions. Fix: Either enable auto-creation, or run the CREATE TABLE statement manually and grant the service account roles/bigquery.dataEditor.

Authentication failures

Symptom: failed to create BigQuery client errors at startup. Cause: ADC is not available in the environment, or service_account_key is set but empty/invalid. Fix: On GCP, confirm the workload’s service account is attached. Off GCP, set service_account_key to an env.VAR_NAME whose value is the unescaped service account JSON, and verify the variable is populated:

No rows appearing

Symptom: The plugin is enabled but the table stays empty. Cause: Rows are written in batches, so very low traffic can delay the first write. The service account may also lack write permission. Fix: Send some traffic and wait at least flush_interval_seconds, then confirm the service account has roles/bigquery.dataEditor on the dataset. Enable debug logging to surface any write errors:

Missing conversation content

Symptom: input_history and output_message are empty. Cause: disable_content_logging is true. Fix: Set it to false if you want conversation content stored.

Next Steps