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::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(
124        &mut self,
125        _layers: &mut Layers,
126    ) -> eyre::Result<OtlpInitStatus> {
127        if let Some(endpoint) = self.otlp.as_mut() {
128            self.protocol.validate_endpoint(endpoint)?;
129
130            #[cfg(feature = "otlp")]
131            {
132                {
133                    let config = reth_tracing_otlp::OtlpConfig::new(
134                        self.service_name.clone(),
135                        endpoint.clone(),
136                        self.protocol,
137                        self.sample_ratio,
138                    )?;
139
140                    _layers.with_span_layer(config.clone(), self.otlp_filter.clone())?;
141
142                    Ok(OtlpInitStatus::Started(config.endpoint().clone()))
143                }
144            }
145            #[cfg(not(feature = "otlp"))]
146            {
147                Ok(OtlpInitStatus::NoFeature)
148            }
149        } else {
150            Ok(OtlpInitStatus::Disabled)
151        }
152    }
153}
154
155/// Status of OTLP tracing initialization.
156#[derive(Debug)]
157pub enum OtlpInitStatus {
158    /// OTLP tracing was successfully started with the given endpoint.
159    Started(Url),
160    /// OTLP tracing is disabled (no endpoint configured).
161    Disabled,
162    /// OTLP arguments provided but feature is not compiled.
163    NoFeature,
164}
165
166// Parses an OTLP endpoint url.
167fn parse_otlp_endpoint(arg: &str) -> eyre::Result<Url> {
168    Url::parse(arg).wrap_err("Invalid URL for OTLP trace output")
169}