Skip to main content

reth_node_core/args/
trace.rs

1//! Opentelemetry tracing and logging configuration through CLI args.
2
3use clap::Parser;
4use eyre::WrapErr;
5use reth_tracing::{tracing_subscriber::EnvFilter, Layers};
6use reth_tracing_otlp::OtlpProtocol;
7use url::Url;
8
9/// CLI arguments for configuring `Opentelemetry` trace and logs export.
10#[derive(Debug, Clone, Parser)]
11pub struct TraceArgs {
12    /// Enable `Opentelemetry` tracing export to an OTLP endpoint.
13    ///
14    /// If no value provided, defaults based on protocol:
15    /// - HTTP: `http://localhost:4318/v1/traces`
16    /// - gRPC: `http://localhost:4317`
17    ///
18    /// Example: --tracing-otlp=http://collector:4318/v1/traces
19    #[arg(
20        long = "tracing-otlp",
21        // Per specification.
22        env = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
23        global = true,
24        value_name = "URL",
25        num_args = 0..=1,
26        default_missing_value = "http://localhost:4318/v1/traces",
27        require_equals = true,
28        value_parser = parse_otlp_endpoint,
29        help_heading = "Tracing"
30    )]
31    pub otlp: Option<Url>,
32
33    /// Enable `Opentelemetry` logs export to an OTLP endpoint.
34    ///
35    /// If no value provided, defaults based on protocol:
36    /// - HTTP: `http://localhost:4318/v1/logs`
37    /// - gRPC: `http://localhost:4317`
38    ///
39    /// Example: --logs-otlp=http://collector:4318/v1/logs
40    #[arg(
41        long = "logs-otlp",
42        env = "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
43        global = true,
44        value_name = "URL",
45        num_args = 0..=1,
46        default_missing_value = "http://localhost:4318/v1/logs",
47        require_equals = true,
48        value_parser = parse_otlp_endpoint,
49        help_heading = "Logging"
50    )]
51    pub logs_otlp: Option<Url>,
52
53    /// OTLP transport protocol to use for exporting traces and logs.
54    ///
55    /// - `http`: expects endpoint path to end with `/v1/traces` or `/v1/logs`
56    /// - `grpc`: expects endpoint without a path
57    ///
58    /// Defaults to HTTP if not specified.
59    #[arg(
60        long = "tracing-otlp-protocol",
61        env = "OTEL_EXPORTER_OTLP_PROTOCOL",
62        global = true,
63        value_name = "PROTOCOL",
64        default_value = "http",
65        help_heading = "Tracing"
66    )]
67    pub protocol: OtlpProtocol,
68
69    /// Set a filter directive for the OTLP tracer. This controls the verbosity
70    /// of spans and events sent to the OTLP endpoint. It follows the same
71    /// syntax as the `RUST_LOG` environment variable.
72    ///
73    /// Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
74    ///
75    /// Defaults to TRACE if not specified.
76    #[arg(
77        long = "tracing-otlp.filter",
78        global = true,
79        value_name = "FILTER",
80        default_value = "debug",
81        help_heading = "Tracing"
82    )]
83    pub otlp_filter: EnvFilter,
84
85    /// Set a filter directive for the OTLP logs exporter. This controls the verbosity
86    /// of logs sent to the OTLP endpoint. It follows the same syntax as the
87    /// `RUST_LOG` environment variable.
88    ///
89    /// Example: --logs-otlp.filter=info,reth=debug
90    ///
91    /// Defaults to INFO if not specified.
92    #[arg(
93        long = "logs-otlp.filter",
94        global = true,
95        value_name = "FILTER",
96        default_value = "info",
97        help_heading = "Logging"
98    )]
99    pub logs_otlp_filter: EnvFilter,
100
101    /// Service name to use for OTLP tracing export.
102    ///
103    /// This name will be used to identify the service in distributed tracing systems
104    /// like Jaeger or Zipkin. Useful for differentiating between multiple reth instances.
105    ///
106    /// Set via `OTEL_SERVICE_NAME` environment variable. Defaults to "reth" if not specified.
107    #[arg(
108        long = "tracing-otlp.service-name",
109        env = "OTEL_SERVICE_NAME",
110        global = true,
111        value_name = "NAME",
112        default_value = "reth",
113        hide = true,
114        help_heading = "Tracing"
115    )]
116    pub service_name: String,
117
118    /// Trace sampling ratio to control the percentage of traces to export.
119    ///
120    /// Valid range: 0.0 to 1.0
121    /// - 1.0, default: Sample all traces
122    /// - 0.01: Sample 1% of traces
123    /// - 0.0: Disable sampling
124    ///
125    /// Example: --tracing-otlp.sample-ratio=0.0.
126    #[arg(
127        long = "tracing-otlp.sample-ratio",
128        env = "OTEL_TRACES_SAMPLER_ARG",
129        global = true,
130        value_name = "RATIO",
131        help_heading = "Tracing"
132    )]
133    pub sample_ratio: Option<f64>,
134}
135
136impl Default for TraceArgs {
137    fn default() -> Self {
138        Self {
139            otlp: None,
140            logs_otlp: None,
141            protocol: OtlpProtocol::Http,
142            otlp_filter: EnvFilter::from_default_env(),
143            logs_otlp_filter: EnvFilter::try_new("info").expect("valid filter"),
144            sample_ratio: None,
145            service_name: "reth".to_string(),
146        }
147    }
148}
149
150impl TraceArgs {
151    /// Initialize OTLP tracing with the given layers and runner.
152    ///
153    /// This method handles OTLP tracing initialization based on the configured options,
154    /// including validation, protocol selection, and feature flag checking.
155    ///
156    /// Returns the initialization status to allow callers to log appropriate messages.
157    ///
158    /// Note: even though this function is async, it does not actually perform any async operations.
159    /// It's needed only to be able to initialize the gRPC transport of OTLP tracing that needs to
160    /// be called inside a tokio runtime context.
161    pub async fn init_otlp_tracing(
162        &mut self,
163        _layers: &mut Layers,
164    ) -> eyre::Result<OtlpInitStatus> {
165        if let Some(endpoint) = self.otlp.as_mut() {
166            self.protocol.validate_endpoint(endpoint)?;
167
168            #[cfg(feature = "otlp")]
169            {
170                {
171                    let config = reth_tracing_otlp::OtlpConfig::new(
172                        self.service_name.clone(),
173                        endpoint.clone(),
174                        self.protocol,
175                        self.sample_ratio,
176                    )?;
177
178                    _layers.with_span_layer(config.clone(), self.otlp_filter.clone())?;
179
180                    Ok(OtlpInitStatus::Started(config.endpoint().clone()))
181                }
182            }
183            #[cfg(not(feature = "otlp"))]
184            {
185                Ok(OtlpInitStatus::NoFeature)
186            }
187        } else {
188            Ok(OtlpInitStatus::Disabled)
189        }
190    }
191
192    /// Initialize OTLP logs export with the given layers.
193    ///
194    /// This method handles OTLP logs initialization based on the configured options,
195    /// including validation and protocol selection.
196    ///
197    /// Returns the initialization status to allow callers to log appropriate messages.
198    pub async fn init_otlp_logs(&mut self, _layers: &mut Layers) -> eyre::Result<OtlpLogsStatus> {
199        if let Some(endpoint) = self.logs_otlp.as_mut() {
200            self.protocol.validate_logs_endpoint(endpoint)?;
201
202            #[cfg(feature = "otlp-logs")]
203            {
204                let config = reth_tracing_otlp::OtlpLogsConfig::new(
205                    self.service_name.clone(),
206                    endpoint.clone(),
207                    self.protocol,
208                )?;
209
210                _layers.with_log_layer(config.clone(), self.logs_otlp_filter.clone())?;
211
212                Ok(OtlpLogsStatus::Started(config.endpoint().clone()))
213            }
214            #[cfg(not(feature = "otlp-logs"))]
215            {
216                Ok(OtlpLogsStatus::NoFeature)
217            }
218        } else {
219            Ok(OtlpLogsStatus::Disabled)
220        }
221    }
222}
223
224/// Status of OTLP tracing initialization.
225#[derive(Debug)]
226pub enum OtlpInitStatus {
227    /// OTLP tracing was successfully started with the given endpoint.
228    Started(Url),
229    /// OTLP tracing is disabled (no endpoint configured).
230    Disabled,
231    /// OTLP arguments provided but feature is not compiled.
232    NoFeature,
233}
234
235/// Status of OTLP logs initialization.
236#[derive(Debug)]
237pub enum OtlpLogsStatus {
238    /// OTLP logs export was successfully started with the given endpoint.
239    Started(Url),
240    /// OTLP logs export is disabled (no endpoint configured).
241    Disabled,
242    /// OTLP logs arguments provided but feature is not compiled.
243    NoFeature,
244}
245
246// Parses an OTLP endpoint url.
247fn parse_otlp_endpoint(arg: &str) -> eyre::Result<Url> {
248    Url::parse(arg).wrap_err("Invalid URL for OTLP trace output")
249}