turborepo
a73b4324 - feat: Add `runAttributes` config to OTel metrics for cardinality control (#12144)

Commit
2 days ago
feat: Add `runAttributes` config to OTel metrics for cardinality control (#12144) ## Summary - Adds a `runAttributes` config section to `experimentalObservability.otel.metrics` that controls which run-level attributes are attached to exported metrics, following the same pattern as the existing `taskAttributes` config - Gates high-cardinality metric attributes behind opt-in flags (all default to `false`) to prevent excessive metric series in observability backends - Removes `turbo.task.cache_time_saved_ms` as a metric attribute since numeric values used as metric tags are a cardinality anti-pattern ### New config options ```jsonc { "experimentalObservability": { "otel": { "metrics": { "runAttributes": { "id": false, // turbo.run.id — unique per invocation "scmRevision": false // turbo.scm.revision — unique per commit } } } } } ``` Corresponding environment variables: - `TURBO_EXPERIMENTAL_OTEL_METRICS_RUN_ATTRIBUTES_ID` - `TURBO_EXPERIMENTAL_OTEL_METRICS_RUN_ATTRIBUTES_SCM_REVISION` ### Why Some metric attributes have unbounded cardinality — their unique values grow without limit. When these are attached as tags/dimensions on metrics, each unique combination creates a new metric series. Observability backends often charge per unique series, so unbounded attributes can lead to unexpectedly high costs. This change gates the following attributes behind opt-in config flags: | Attribute | Risk | Config flag | |---|---|---| | `turbo.run.id` | Unique KSUID per `turbo` invocation | `runAttributes.id` | | `turbo.scm.revision` | Full Git SHA, unique per commit | `runAttributes.scmRevision` | | `turbo.task.id` | `package#task` combo per task | `taskAttributes.id` (existing) | | `turbo.task.hash` | Content hash, changes with inputs | `taskAttributes.hashes` (existing) | | `turbo.task.external_inputs_hash` | Content hash | `taskAttributes.hashes` (existing) | Additionally, `turbo.task.cache_time_saved_ms` was removed as a metric attribute entirely — it's a numeric value being used as a tag dimension, creating a new series for every distinct millisecond value. The cache time saved data remains available in the run summary JSON output. ### Changes across layers 1. **Config schema** (`raw.rs`): Added `RawObservabilityOtelRunAttributes` struct 2. **Config options** (`experimental_otel.rs`): Added `ExperimentalOtelRunAttributesOptions` with `id` and `scm_revision` fields 3. **Config parsing** (`turbo_json.rs`): Conversion from raw to config options 4. **Runtime config** (`turborepo-otel/lib.rs`): Added `RunAttributesConfig` and gated `turbo.run.id` / `turbo.scm.revision` behind it 5. **Env vars** (`env.rs`): Registered `TURBO_EXPERIMENTAL_OTEL_METRICS_RUN_ATTRIBUTES_ID` and `_SCM_REVISION` 6. **Docs** (`configuration.mdx`, `ARCHITECTURE.md`): Documented new options with cardinality guidance ## Test plan - [x] All existing tests pass (245 tests across 4 crates) - [x] New test cases for `run_attributes.id` enabled, `run_attributes` fully enabled, env var parsing for both `id` and `scm_revision` - [x] `is_empty` check updated to account for `run_attributes` - [x] Serialization consistency verified — `skip_serializing_if = "Option::is_none"` on all fields across all layers - [x] `cargo check` and `cargo clippy` pass cleanly
Author
Parents
Loading