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}