feat: add separate headers support for traces and metrics in OTEL collector - #5940
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughOTEL profiles now support shared, trace-specific, and metrics-specific headers. The backend persists, redacts, resolves, and applies each map independently. The UI and configuration schemas expose the new fields with secret-variable support. ChangesOTEL signal-specific headers
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant OTEL Profile
participant Header Resolver
participant Trace Client
participant Metrics Exporter
OTEL Profile->>Header Resolver: resolve shared and trace headers
Header Resolver->>Trace Client: provide resolved trace headers
OTEL Profile->>Header Resolver: resolve shared and metrics headers
Header Resolver->>Metrics Exporter: provide resolved metrics headers
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
plugins/otel/profiles_test.go (1)
531-538: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCover trace-header redaction.
The test verifies literal-value masking only for
MetricsHeaders. Add a literalTraceHeadersvalue and assert thatRedacted()masks it. This detects a regression where trace headers bypassredactHeaderMap.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/otel/profiles_test.go` around lines 531 - 538, Add a literal entry to the test profile’s TraceHeaders alongside the existing MetricsHeaders fixture, then extend the redacted profile assertions to verify that Redacted() masks the trace-header value rather than preserving the literal. Keep the existing environment-reference preservation and metrics-header checks unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/otel/main.go`:
- Around line 557-563: Update the profile header resolution flow around
mergedResolvedHeaders so trace headers are resolved only when traces are enabled
and metrics headers only when metrics are enabled; preserve common headers and
enabled-signal merging, while ignoring unset environment references in inactive
signal maps. Add trace-only and metrics-only tests covering an unset environment
reference in the disabled signal’s headers.
---
Nitpick comments:
In `@plugins/otel/profiles_test.go`:
- Around line 531-538: Add a literal entry to the test profile’s TraceHeaders
alongside the existing MetricsHeaders fixture, then extend the redacted profile
assertions to verify that Redacted() masks the trace-header value rather than
preserving the literal. Keep the existing environment-reference preservation and
metrics-header checks unchanged.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 79a11d6b-e1ff-4424-9fbb-b8f164412bd0
📒 Files selected for processing (6)
plugins/otel/main.goplugins/otel/profiles_test.gotransports/config.schema.jsonui/app/workspace/observability/fragments/otelFormFragment.tsxui/app/workspace/observability/views/plugins/otelView.tsxui/lib/types/schemas.ts
e48d172 to
a6883e2
Compare
a8828bc to
ebbecb6
Compare
Merge activity
|
The base branch was changed.
a6883e2 to
8a5f90f
Compare
…lector (#5940) ## Summary Adds support for per-signal HTTP headers in OTel profiles, allowing separate headers to be sent exclusively to the trace endpoint or the metrics endpoint, in addition to the existing shared `headers` field. This is particularly useful when a metrics collector requires a signal-specific header (e.g. a Databricks table name) that should not be forwarded to the trace endpoint. ## Changes - Added `trace_headers` and `metrics_headers` fields to the `Profile` struct and `profileForStorage` struct, alongside the existing `headers` field. - `headers` continues to apply to both endpoints. `trace_headers` and `metrics_headers` are overlaid on top of the common headers at build time, with per-signal keys winning on collision. - Introduced `mergedResolvedHeaders` to merge common and per-signal header maps and resolve `env.VAR_NAME` references without mutating the inputs. - Extracted `redactHeaderMap` to eliminate duplicated redaction logic and applied it to all three header maps in `Redacted()`. - Updated the JSON schema (`config.schema.json`) with descriptions for all three header fields. - Updated the UI form to render separate `HeadersTable` inputs for common, trace-only, and metrics-only headers, each with descriptive labels and `FormDescription` text. - Updated the Zod schema and form serialization to include `trace_headers` and `metrics_headers`. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` Configure an OTel profile with all three header fields: ```json { "headers": { "Authorization": "env.OTEL_TOKEN" }, "trace_headers": { "X-Trace-Only": "trace-value" }, "metrics_headers": { "X-Databricks-Table": "my_table" } } ``` Verify that: - Trace requests include `Authorization` and `X-Trace-Only` but not `X-Databricks-Table`. - Metrics requests include `Authorization` and `X-Databricks-Table` but not `X-Trace-Only`. - `env.OTEL_TOKEN` is resolved from the environment on both endpoints. - Redacted config masks literal header values and preserves `env.` references across all three maps. ## Breaking changes - [ ] Yes - [x] No ## Security considerations All three header maps (`headers`, `trace_headers`, `metrics_headers`) are subject to the same redaction logic in `Redacted()`. Literal header values are masked and `env.` references are preserved as-is, consistent with prior behavior. No new secret storage paths are introduced. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…lector (#5940) ## Summary Adds support for per-signal HTTP headers in OTel profiles, allowing separate headers to be sent exclusively to the trace endpoint or the metrics endpoint, in addition to the existing shared `headers` field. This is particularly useful when a metrics collector requires a signal-specific header (e.g. a Databricks table name) that should not be forwarded to the trace endpoint. ## Changes - Added `trace_headers` and `metrics_headers` fields to the `Profile` struct and `profileForStorage` struct, alongside the existing `headers` field. - `headers` continues to apply to both endpoints. `trace_headers` and `metrics_headers` are overlaid on top of the common headers at build time, with per-signal keys winning on collision. - Introduced `mergedResolvedHeaders` to merge common and per-signal header maps and resolve `env.VAR_NAME` references without mutating the inputs. - Extracted `redactHeaderMap` to eliminate duplicated redaction logic and applied it to all three header maps in `Redacted()`. - Updated the JSON schema (`config.schema.json`) with descriptions for all three header fields. - Updated the UI form to render separate `HeadersTable` inputs for common, trace-only, and metrics-only headers, each with descriptive labels and `FormDescription` text. - Updated the Zod schema and form serialization to include `trace_headers` and `metrics_headers`. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` Configure an OTel profile with all three header fields: ```json { "headers": { "Authorization": "env.OTEL_TOKEN" }, "trace_headers": { "X-Trace-Only": "trace-value" }, "metrics_headers": { "X-Databricks-Table": "my_table" } } ``` Verify that: - Trace requests include `Authorization` and `X-Trace-Only` but not `X-Databricks-Table`. - Metrics requests include `Authorization` and `X-Databricks-Table` but not `X-Trace-Only`. - `env.OTEL_TOKEN` is resolved from the environment on both endpoints. - Redacted config masks literal header values and preserves `env.` references across all three maps. ## Breaking changes - [ ] Yes - [x] No ## Security considerations All three header maps (`headers`, `trace_headers`, `metrics_headers`) are subject to the same redaction logic in `Redacted()`. Literal header values are masked and `env.` references are preserved as-is, consistent with prior behavior. No new secret storage paths are introduced. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…lector (#5940) ## Summary Adds support for per-signal HTTP headers in OTel profiles, allowing separate headers to be sent exclusively to the trace endpoint or the metrics endpoint, in addition to the existing shared `headers` field. This is particularly useful when a metrics collector requires a signal-specific header (e.g. a Databricks table name) that should not be forwarded to the trace endpoint. ## Changes - Added `trace_headers` and `metrics_headers` fields to the `Profile` struct and `profileForStorage` struct, alongside the existing `headers` field. - `headers` continues to apply to both endpoints. `trace_headers` and `metrics_headers` are overlaid on top of the common headers at build time, with per-signal keys winning on collision. - Introduced `mergedResolvedHeaders` to merge common and per-signal header maps and resolve `env.VAR_NAME` references without mutating the inputs. - Extracted `redactHeaderMap` to eliminate duplicated redaction logic and applied it to all three header maps in `Redacted()`. - Updated the JSON schema (`config.schema.json`) with descriptions for all three header fields. - Updated the UI form to render separate `HeadersTable` inputs for common, trace-only, and metrics-only headers, each with descriptive labels and `FormDescription` text. - Updated the Zod schema and form serialization to include `trace_headers` and `metrics_headers`. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` Configure an OTel profile with all three header fields: ```json { "headers": { "Authorization": "env.OTEL_TOKEN" }, "trace_headers": { "X-Trace-Only": "trace-value" }, "metrics_headers": { "X-Databricks-Table": "my_table" } } ``` Verify that: - Trace requests include `Authorization` and `X-Trace-Only` but not `X-Databricks-Table`. - Metrics requests include `Authorization` and `X-Databricks-Table` but not `X-Trace-Only`. - `env.OTEL_TOKEN` is resolved from the environment on both endpoints. - Redacted config masks literal header values and preserves `env.` references across all three maps. ## Breaking changes - [ ] Yes - [x] No ## Security considerations All three header maps (`headers`, `trace_headers`, `metrics_headers`) are subject to the same redaction logic in `Redacted()`. Literal header values are masked and `env.` references are preserved as-is, consistent with prior behavior. No new secret storage paths are introduced. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
## ✨ Features - **MCP Per-User OAuth** - MCP clients can hold per-user OAuth credentials and per-user headers, configurable from `config.json` as well as the UI, with a documented shared vs per-identity token lookup contract and VK/Users filters on the OAuth Grants and MCP Auth Sessions sidebars - **Token Exchange IDP Credentials** - New `use_idp_credentials` on `token_exchange` reuses SSO login app credentials for providers that require it, such as Microsoft Entra ID; `client_id` becomes optional when it is set (#6068, #6069) - **Bedrock VPC Endpoints** - AWS Bedrock keys can target VPC endpoints (#6064) - **Per-Request Flat-Fee Pricing** - New `cost_per_request` field flows through datasheet sync, the cost engine, custom overrides and the UI override form (#6079) - **Pricing Overrides in the Model Catalog** - `/api/models/details` exposes resolved pricing overrides, and catalog rows resolve overrides server-side (#6055, #6056) - **MCP Tool Discovery Persistence** - Discovered MCP tools persist and resync uniformly across all client types through a hash-gated core callback, surviving restarts and propagating across a cluster - **W3C Trace ID Propagation** - Requests carry a W3C trace ID on the context (#5945) - **Cancellable Log Cost Recalculation** - Log cost recalculation tasks can be cancelled from the backend (#5801) - **Separate OTEL Metrics Pipeline** - The OTEL collector supports a metrics tab independent of traces, plus separate headers for traces and metrics (#5939, #5940) - **Roots-Only Log Filter** - New `roots_only` filter collapses fallback chains into their root entry with child aggregates (#5737) - **MCP Log Redaction and Plugin Logs** - MCP tool logs carry redaction mappings and plugin logs (#5744, #5746) - **User Agent and App Attribution in Logs** - Logs and MCP tool logs record user agent, app, source, decision, app key and device ID - **S3 Log Export Metadata** - Additional metadata is written alongside S3 log exports (#6070) - **Matview Maintenance Off Switch** - `matview_refresh_interval` accepts `"off"` to disable logstore matview maintenance entirely (thanks [@jeremym-tanium](https://github.com/jeremym-tanium)!) (#5693) - **Video Request Info in Logs UI** - Video requests surface their details in the logs UI (#5946) - **Shell Rewriter Hook** - The UI handler exposes a `ShellRewriter` hook for pre-hydration HTML rewriting (#5807) - **Auth Skip Path** - Adds a context path letting trusted internal callers bypass auth resolution ## 🐞 Fixed - **Path Normalization Auth Bypass** - Fixed a path normalization flaw that allowed auth to be bypassed (#5763) - **Minimal Reasoning Effort on GPT-5 Models** - `reasoning_effort: "minimal"` is preserved for GPT-5-family OpenAI models instead of being downgraded to `low` (thanks [@jitokim](https://github.com/jitokim)!) (#6046) - **Gemini Truncated Response Finish Reason** - Truncated Gemini responses report `MAX_TOKENS` instead of `OTHER` (thanks [@AdityaPainuli](https://github.com/AdityaPainuli)!) (#5979) - **Null Tool-Call Function Name on Streaming** - Streaming continuation deltas no longer materialize an absent tool-call function name as `null` (thanks [@AdityaPainuli](https://github.com/AdityaPainuli)!) (#5966) - **Bedrock Document Uploads** - Fixed Bedrock file handling in inference so office and PDF documents sent as OpenAI `type: "file"` are accepted (#5947) - **xAI Usage Cost** - Fixed USD cost ticks for xAI usage (#5950) - **Anthropic Encrypted Reasoning** - Added an Anthropic error branch when stripping encrypted reasoning content - **MCP Reconnect and Lock Ordering** - Broke a lock-order inversion in `ConnectionCheckerManager`, rebuilt ephemeral clients across the whole connect+init retry, preserved last-known tool maps across close-first reconnects, bound connect attempts to entry identity, deduped background reconnects and gated SSE `OnConnectionLost` on connection identity - **MCP OAuth Session Correctness** - Restricted `Reauthorize` to shared OAuth clients, rejected inactive tokens in `ValidateToken`, made the OAuth flow claim atomic against concurrent reauth, stopped dropping stored scopes on decode failure, and closed a verify-headers double-submit race that also dropped TLS, timeout and per-user-header fields - **Session Stickiness Reconciliation** - `needs_session_stickiness` is pinned across `config.json` reconciliation, so an unrelated file edit can no longer silently revert a client to per-call - **Credential Cache Cancellation** - `headerCredentialCache.Fill` and `userTokenCache.Fill` propagate context so a cancelled request unblocks instead of waiting on an unrelated leader; LRU entries carry a version so a rejected stale `Get` cannot evict a concurrently-updated value - **Governance List-Models Call** - Budgets and rate limits no longer trigger a list-models call (#6051) - **Realtime Response Create Input** - Guarded `response.create` input (#6050) - **HTTP Server Timeouts** - Configured bounded `http.Server` timeouts and a request-body limit - **MCP Client State Badges** - State badges render with spaces instead of underscores, and the state filter bucket was renamed from `disconnected` to `unstable` - **Entra OBO Scope** - `offline_access` is combined with `<audience>/.default` for Entra OBO instead of replacing it (#6078) ## 🔧 Maintenance - **Governance Route Families** - Editions can override governance route families (#5839) - **Dependency Upgrades** - Dependabot updates across all modules, plus module path fixes (#6040, #5864) - **Documentation** - config.schema.json doc fixes and Datadog env var reference fixes in the helm chart docs (#5938, #6019) ## 🗄️ Database Migrations **configstore:** - **add_mcp_client_pending_oauth_config_json_column** - Adds `pending_oauth_config_json` to `config_mcp_clients`. Reversible: drops the added column. - **merge_oauth_token_tables** - Consolidates `oauth_tokens` and `oauth_user_tokens` into `mcp_oauth_tokens`. **Non-reversible**: rollback deliberately leaves `mcp_oauth_tokens` in place, because every OAuth read and write targets it from this migration onward and dropping it would destroy any token created or refreshed since, forcing every holder to re-authorize. - **create_mcp_oauth_flows_table** - Creates `mcp_oauth_flows` to track in-flight OAuth flows. Reversible: drops the new table. - **drop_oauth_config_pkce_columns** - Drops CSRF state, PKCE verifier and `expires_at` from the OAuth config table now that they live on `mcp_oauth_flows`. **Non-reversible**: forward-only, the dropped values were per-flow ephemeral and re-adding empty columns would restore nothing. - **drop_oauth_config_token_id_column** - Drops `token_id`. **Non-reversible**: forward-only, it was a pure FK shortcut now reachable via `(oauth_config_id, auth_mode)`. - **add_mcp_admin_auth_mode_indexes** - Adds admin partial unique indexes on `mcp_oauth_tokens` and `mcp_per_user_header_credentials`. Reversible: drops both indexes. - **add_mcp_client_token_exchange_json_column** - Adds `token_exchange_json` to `config_mcp_clients`. Reversible: drops the added column. - **add_needs_session_stickiness_column** - Adds `needs_session_stickiness` to `config_mcp_clients`. Reversible: drops the added column. - **add_bedrock_endpoints_columns** - Adds Bedrock VPC endpoint columns to the keys table. Reversible: drops the added columns. - **add_cost_per_request_pricing_column** - Adds `cost_per_request` to model pricing. Reversible: drops the added column. **logstore:** - **logs_add_guardrail_debug_column** - Adds `guardrail_debug` to logs. Reversible: drops the added column. - **mcp_tool_logs_add_redaction_mapping_column** - Adds the redaction mapping column to MCP tool logs. **Non-reversible**: rollback is a no-op because dropping the column would permanently destroy reveal data for already-redacted MCP logs. - **logs_add_user_agent_column** - Adds user agent and app columns, their indexes, and a `UserAgentMapping` table. Reversible: drops the indexes and the mapping table. - **mcp_tool_logs_add_user_agent_column** - Adds user agent and app columns plus indexes to MCP tool logs. Reversible: drops both indexes and the `app` column. - **mcp_tool_logs_add_endpoint_columns** - Adds `source`, `decision`, `app_key` and `device_id` to MCP tool logs. Reversible: drops all four columns. - **mcp_tool_logs_add_plugin_logs_column** - Adds `plugin_logs` to MCP tool logs. Reversible: drops the added column. - **logs_recreate_matviews_with_user_agent_column** and **logs_recreate_matviews_with_app_column** - Recreate the log materialized views to include the new columns. Rollback is a no-op because `ensureMatViews` recreates them on next startup. <Warning> **High-throughput deployments: run the logstore migrations during a low-activity window.** Every logstore migration above alters `logs` or `mcp_tool_logs`, the two highest-insert tables in Bifrost, and several also build indexes on them. On a busy instance the index builds hold locks that block concurrent log inserts for the duration of the build, and the matview recreations rebuild against the full table. Schedule the upgrade for a low-traffic period, or expect elevated log-write latency and possible request-path backpressure while the migrations run. </Warning> <Warning> `merge_oauth_token_tables`, `drop_oauth_config_pkce_columns` and `drop_oauth_config_token_id_column` transform or remove existing OAuth state and cannot be rolled back. Take a database backup before upgrading, and do not roll the binary back past this release once the migration has run. </Warning> ## 🐙 Closed GitHub Issues - [#123](#123) - Files API Support - [#5472](#5472) - [Bug]: Bedrock rejects office/PDF document uploads via OpenAI `type:"file"` - "The PDF specified was not valid" - [#5900](#5900) - [Bug]: Streaming continuation chunks materialize omitted tool-call metadata as null - [#5978](#5978) - [Bug]: Gemini egress reports truncated responses as FinishReason OTHER, IncompleteDetails switch matches a string that never occurs - [#6044](#6044) - [Bug]: normalizeOpenAIReasoningEffort maps 'minimal' to 'low' for ALL OpenAI models, even ones that natively support 'minimal'
…lector (#5940) ## Summary Adds support for per-signal HTTP headers in OTel profiles, allowing separate headers to be sent exclusively to the trace endpoint or the metrics endpoint, in addition to the existing shared `headers` field. This is particularly useful when a metrics collector requires a signal-specific header (e.g. a Databricks table name) that should not be forwarded to the trace endpoint. ## Changes - Added `trace_headers` and `metrics_headers` fields to the `Profile` struct and `profileForStorage` struct, alongside the existing `headers` field. - `headers` continues to apply to both endpoints. `trace_headers` and `metrics_headers` are overlaid on top of the common headers at build time, with per-signal keys winning on collision. - Introduced `mergedResolvedHeaders` to merge common and per-signal header maps and resolve `env.VAR_NAME` references without mutating the inputs. - Extracted `redactHeaderMap` to eliminate duplicated redaction logic and applied it to all three header maps in `Redacted()`. - Updated the JSON schema (`config.schema.json`) with descriptions for all three header fields. - Updated the UI form to render separate `HeadersTable` inputs for common, trace-only, and metrics-only headers, each with descriptive labels and `FormDescription` text. - Updated the Zod schema and form serialization to include `trace_headers` and `metrics_headers`. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` Configure an OTel profile with all three header fields: ```json { "headers": { "Authorization": "env.OTEL_TOKEN" }, "trace_headers": { "X-Trace-Only": "trace-value" }, "metrics_headers": { "X-Databricks-Table": "my_table" } } ``` Verify that: - Trace requests include `Authorization` and `X-Trace-Only` but not `X-Databricks-Table`. - Metrics requests include `Authorization` and `X-Databricks-Table` but not `X-Trace-Only`. - `env.OTEL_TOKEN` is resolved from the environment on both endpoints. - Redacted config masks literal header values and preserves `env.` references across all three maps. ## Breaking changes - [ ] Yes - [x] No ## Security considerations All three header maps (`headers`, `trace_headers`, `metrics_headers`) are subject to the same redaction logic in `Redacted()`. Literal header values are masked and `env.` references are preserved as-is, consistent with prior behavior. No new secret storage paths are introduced. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable
…lector (#5940) ## Summary Adds support for per-signal HTTP headers in OTel profiles, allowing separate headers to be sent exclusively to the trace endpoint or the metrics endpoint, in addition to the existing shared `headers` field. This is particularly useful when a metrics collector requires a signal-specific header (e.g. a Databricks table name) that should not be forwarded to the trace endpoint. ## Changes - Added `trace_headers` and `metrics_headers` fields to the `Profile` struct and `profileForStorage` struct, alongside the existing `headers` field. - `headers` continues to apply to both endpoints. `trace_headers` and `metrics_headers` are overlaid on top of the common headers at build time, with per-signal keys winning on collision. - Introduced `mergedResolvedHeaders` to merge common and per-signal header maps and resolve `env.VAR_NAME` references without mutating the inputs. - Extracted `redactHeaderMap` to eliminate duplicated redaction logic and applied it to all three header maps in `Redacted()`. - Updated the JSON schema (`config.schema.json`) with descriptions for all three header fields. - Updated the UI form to render separate `HeadersTable` inputs for common, trace-only, and metrics-only headers, each with descriptive labels and `FormDescription` text. - Updated the Zod schema and form serialization to include `trace_headers` and `metrics_headers`. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [x] Core (Go) - [ ] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... # UI cd ui pnpm i || npm i pnpm test || npm test pnpm build || npm run build ``` Configure an OTel profile with all three header fields: ```json { "headers": { "Authorization": "env.OTEL_TOKEN" }, "trace_headers": { "X-Trace-Only": "trace-value" }, "metrics_headers": { "X-Databricks-Table": "my_table" } } ``` Verify that: - Trace requests include `Authorization` and `X-Trace-Only` but not `X-Databricks-Table`. - Metrics requests include `Authorization` and `X-Databricks-Table` but not `X-Trace-Only`. - `env.OTEL_TOKEN` is resolved from the environment on both endpoints. - Redacted config masks literal header values and preserves `env.` references across all three maps. ## Breaking changes - [ ] Yes - [x] No ## Security considerations All three header maps (`headers`, `trace_headers`, `metrics_headers`) are subject to the same redaction logic in `Redacted()`. Literal header values are masked and `env.` references are preserved as-is, consistent with prior behavior. No new secret storage paths are introduced. ## Checklist - [ ] I read `docs/contributing/README.md` and followed the guidelines - [x] I added/updated tests where appropriate - [ ] I updated documentation where needed - [x] I verified builds succeed (Go and UI) - [ ] I verified the CI pipeline passes locally if applicable

Summary
Adds support for per-signal HTTP headers in OTel profiles, allowing separate headers to be sent exclusively to the trace endpoint or the metrics endpoint, in addition to the existing shared
headersfield. This is particularly useful when a metrics collector requires a signal-specific header (e.g. a Databricks table name) that should not be forwarded to the trace endpoint.Changes
trace_headersandmetrics_headersfields to theProfilestruct andprofileForStoragestruct, alongside the existingheadersfield.headerscontinues to apply to both endpoints.trace_headersandmetrics_headersare overlaid on top of the common headers at build time, with per-signal keys winning on collision.mergedResolvedHeadersto merge common and per-signal header maps and resolveenv.VAR_NAMEreferences without mutating the inputs.redactHeaderMapto eliminate duplicated redaction logic and applied it to all three header maps inRedacted().config.schema.json) with descriptions for all three header fields.HeadersTableinputs for common, trace-only, and metrics-only headers, each with descriptive labels andFormDescriptiontext.trace_headersandmetrics_headers.Type of change
Affected areas
How to test
Configure an OTel profile with all three header fields:
{ "headers": { "Authorization": "env.OTEL_TOKEN" }, "trace_headers": { "X-Trace-Only": "trace-value" }, "metrics_headers": { "X-Databricks-Table": "my_table" } }Verify that:
AuthorizationandX-Trace-Onlybut notX-Databricks-Table.AuthorizationandX-Databricks-Tablebut notX-Trace-Only.env.OTEL_TOKENis resolved from the environment on both endpoints.env.references across all three maps.Breaking changes
Security considerations
All three header maps (
headers,trace_headers,metrics_headers) are subject to the same redaction logic inRedacted(). Literal header values are masked andenv.references are preserved as-is, consistent with prior behavior. No new secret storage paths are introduced.Checklist
docs/contributing/README.mdand followed the guidelines