reth_node_core/args/
trace.rs

1//! Opentelemetry tracing configuration through CLI args.
2
3use clap::Parser;
4use eyre::WrapErr;
5use reth_tracing::{tracing_subscriber::EnvFilter, Layers};
6use reth_tracing_otlp::{OtlpConfig, OtlpProtocol};
7use url::Url;
8
9/// CLI arguments for configuring `Opentelemetry` trace and span 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    /// OTLP transport protocol to use for exporting traces.
34    ///
35    /// - `http`: expects endpoint path to end with `/v1/traces`
36    /// - `grpc`: expects endpoint without a path
37    ///
38    /// Defaults to HTTP if not specified.
39    #[arg(
40        long = "tracing-otlp-protocol",
41        env = "OTEL_EXPORTER_OTLP_PROTOCOL",
42        global = true,
43        value_name = "PROTOCOL",
44        default_value = "http",
45        help_heading = "Tracing"
46    )]
47    pub protocol: OtlpProtocol,
48
49    /// Set a filter directive for the OTLP tracer. This controls the verbosity
50    /// of spans and events sent to the OTLP endpoint. It follows the same
51    /// syntax as the `RUST_LOG` environment variable.
52    ///
53    /// Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
54    ///
55    /// Defaults to TRACE if not specified.
56    #[arg(
57        long = "tracing-otlp.filter",
58        global = true,
59        value_name = "FILTER",
60        default_value = "debug",
61        help_heading = "Tracing"
62    )]
63    pub otlp_filter: EnvFilter,
64
65    /// Service name to use for OTLP tracing export.
66    ///
67    /// This name will be used to identify the service in distributed tracing systems
68    /// like Jaeger or Zipkin. Useful for differentiating between multiple reth instances.
69    ///
70    /// Set via `OTEL_SERVICE_NAME` environment variable. Defaults to "reth" if not specified.
71    #[arg(
72        long = "tracing-otlp.service-name",
73        env = "OTEL_SERVICE_NAME",
74        global = true,
75        value_name = "NAME",
76        default_value = "reth",
77        hide = true,
78        help_heading = "Tracing"
79    )]
80    pub service_name: String,
81
82    /// Trace sampling ratio to control the percentage of traces to export.
83    ///
84    /// Valid range: 0.0 to 1.0
85    /// - 1.0, default: Sample all traces
86    /// - 0.01: Sample 1% of traces
87    /// - 0.0: Disable sampling
88    ///
89    /// Example: --tracing-otlp.sample-ratio=0.0.
90    #[arg(
91        long = "tracing-otlp.sample-ratio",
92        env = "OTEL_TRACES_SAMPLER_ARG",
93        global = true,
94        value_name = "RATIO",
95        help_heading = "Tracing"
96    )]
97    pub sample_ratio: Option<f64>,
98}
99
100impl Default for TraceArgs {
101    fn default() -> Self {
102        Self {
103            otlp: None,
104            protocol: OtlpProtocol::Http,
105            otlp_filter: EnvFilter::from_default_env(),
106            sample_ratio: None,
107            service_name: "reth".to_string(),
108        }
109    }
110}
111
112impl TraceArgs {
113    /// Initialize OTLP tracing with the given layers and runner.
114    ///
115    /// This method handles OTLP tracing initialization based on the configured options,
116    /// including validation, protocol selection, and feature flag checking.
117    ///
118    /// Returns the initialization status to allow callers to log appropriate messages.
119    ///
120    /// Note: even though this function is async, it does not actually perform any async operations.
121    /// It's needed only to be able to initialize the gRPC transport of OTLP tracing that needs to
122    /// be called inside a tokio runtime context.
123    pub async fn init_otlp_tracing(&mut self, layers: &mut Layers) -> eyre::Result<OtlpInitStatus> {
124        if let Some(endpoint) = self.otlp.as_mut() {
125            self.protocol.validate_endpoint(endpoint)?;
126
127            #[cfg(feature = "otlp")]
128            {
129                {
130                    let config = OtlpConfig::new(
131                        self.service_name.clone(),
132                        endpoint.clone(),
133                        self.protocol,
134                        self.sample_ratio,
135                    )?;
136
137                    layers.with_span_layer(config.clone(), self.otlp_filter.clone())?;
138
139                    Ok(OtlpInitStatus::Started(config.endpoint().clone()))
140                }
141            }
142            #[cfg(not(feature = "otlp"))]
143            {
144                Ok(OtlpInitStatus::NoFeature)
145            }
146        } else {
147            Ok(OtlpInitStatus::Disabled)
148        }
149    }
150}
151
152/// Status of OTLP tracing initialization.
153#[derive(Debug)]
154pub enum OtlpInitStatus {
155    /// OTLP tracing was successfully started with the given endpoint.
156    Started(Url),
157    /// OTLP tracing is disabled (no endpoint configured).
158    Disabled,
159    /// OTLP arguments provided but feature is not compiled.
160    NoFeature,
161}
162
163// Parses an OTLP endpoint url.
164fn parse_otlp_endpoint(arg: &str) -> eyre::Result<Url> {
165    Url::parse(arg).wrap_err("Invalid URL for OTLP trace output")
166}