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}