Skip to main content

toolkit/telemetry/
config.rs

1//! OpenTelemetry tracing and metrics configuration types
2//!
3//! These types define the configuration structure for OpenTelemetry distributed
4//! tracing and metrics.
5
6use serde::{Deserialize, Serialize};
7use std::collections::{BTreeMap, HashMap};
8
9/// Top-level OpenTelemetry configuration grouping resource identity,
10/// a shared default exporter, tracing settings and metrics settings.
11#[derive(Debug, Clone, Deserialize, Serialize, Default)]
12#[serde(deny_unknown_fields)]
13pub struct OpenTelemetryConfig {
14    #[serde(default)]
15    pub resource: OpenTelemetryResource,
16    /// Default exporter shared by tracing and metrics. Per-signal `exporter`
17    /// fields override this when present.
18    pub exporter: Option<Exporter>,
19    #[serde(default)]
20    pub tracing: TracingConfig,
21    #[serde(default)]
22    pub metrics: MetricsConfig,
23}
24
25impl OpenTelemetryConfig {
26    /// Resolve the effective exporter for tracing (per-signal or shared fallback).
27    #[must_use]
28    pub fn tracing_exporter(&self) -> Option<&Exporter> {
29        self.tracing.exporter.as_ref().or(self.exporter.as_ref())
30    }
31    /// Resolve the effective exporter for metrics (per-signal or shared fallback).
32    #[must_use]
33    pub fn metrics_exporter(&self) -> Option<&Exporter> {
34        self.metrics.exporter.as_ref().or(self.exporter.as_ref())
35    }
36    /// Whether JSON log records should carry top-level `trace_id` / `span_id`.
37    ///
38    /// Off unless explicitly enabled: resolving the span context costs a lookup
39    /// on every event.
40    #[must_use]
41    pub fn inject_trace_ids_into_logs(&self) -> bool {
42        self.tracing
43            .logs_correlation
44            .as_ref()
45            .and_then(|c| c.inject_trace_ids_into_logs)
46            .unwrap_or(false)
47    }
48}
49
50/// OpenTelemetry resource identity — attached to all traces and metrics.
51#[derive(Debug, Clone, Deserialize, Serialize)]
52#[serde(deny_unknown_fields)]
53pub struct OpenTelemetryResource {
54    /// Logical service name.
55    #[serde(default = "default_service_name")]
56    pub service_name: String,
57    /// Extra resource attributes added to every span and metric data point.
58    #[serde(default)]
59    pub attributes: BTreeMap<String, String>,
60}
61
62/// Return the default OpenTelemetry service name used when none is configured.
63fn default_service_name() -> String {
64    "cf-gears".to_owned()
65}
66
67impl Default for OpenTelemetryResource {
68    fn default() -> Self {
69        Self {
70            service_name: default_service_name(),
71            attributes: BTreeMap::default(),
72        }
73    }
74}
75
76/// Tracing configuration for OpenTelemetry distributed tracing
77#[derive(Debug, Clone, Deserialize, Serialize, Default)]
78#[serde(deny_unknown_fields)]
79pub struct TracingConfig {
80    pub enabled: bool,
81    /// Per-signal exporter override. When `None`, the shared
82    /// [`OpenTelemetryConfig::exporter`] is used instead.
83    pub exporter: Option<Exporter>,
84    pub sampler: Option<Sampler>,
85    pub propagation: Option<Propagation>,
86    pub http: Option<HttpOpts>,
87    pub logs_correlation: Option<LogsCorrelation>,
88}
89
90/// Metrics configuration for OpenTelemetry metrics collection
91#[derive(Debug, Clone, Deserialize, Serialize, Default)]
92#[serde(deny_unknown_fields)]
93pub struct MetricsConfig {
94    pub enabled: bool,
95    /// Per-signal exporter override. When `None`, the shared
96    /// [`OpenTelemetryConfig::exporter`] is used instead.
97    pub exporter: Option<Exporter>,
98    /// Maximum number of distinct attribute combinations per instrument.
99    /// When the limit is reached, new combinations are folded into an
100    /// overflow data point.  `None` means the SDK default is used.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub cardinality_limit: Option<usize>,
103}
104
105#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, Copy)]
106#[serde(rename_all = "snake_case")]
107pub enum ExporterKind {
108    #[default]
109    OtlpGrpc,
110    OtlpHttp,
111}
112
113#[derive(Debug, Clone, Deserialize, Serialize)]
114pub struct Exporter {
115    /// Defaults to `otlp_grpc`, matching `extract_exporter_config`. Without a
116    /// default, overriding only the endpoint (e.g. via
117    /// `APP__OPENTELEMETRY__EXPORTER__ENDPOINT`) would fail to load because
118    /// `kind` would be missing from the partially-built map.
119    #[serde(default)]
120    pub kind: ExporterKind,
121    pub endpoint: Option<String>,
122    pub headers: Option<HashMap<String, String>>,
123    pub timeout_ms: Option<u64>,
124}
125
126#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
127#[serde(rename_all = "snake_case")]
128pub enum Sampler {
129    ParentBasedAlwaysOn {},
130    ParentBasedRatio {
131        #[serde(skip_serializing_if = "Option::is_none")]
132        ratio: Option<f64>,
133    },
134    AlwaysOn {},
135    AlwaysOff {},
136}
137
138#[derive(Debug, Clone, Deserialize, Serialize)]
139pub struct Propagation {
140    pub w3c_trace_context: Option<bool>,
141}
142
143#[derive(Debug, Clone, Deserialize, Serialize)]
144pub struct HttpOpts {
145    pub inject_request_id_header: Option<String>,
146    pub record_headers: Option<Vec<String>>,
147}
148
149#[derive(Debug, Clone, Deserialize, Serialize)]
150pub struct LogsCorrelation {
151    pub inject_trace_ids_into_logs: Option<bool>,
152}