1#![cfg(feature = "otlp")]
2
3use clap::ValueEnum;
10use eyre::ensure;
11use opentelemetry::{global, trace::TracerProvider, KeyValue, Value};
12use opentelemetry_otlp::{SpanExporter, WithExportConfig};
13use opentelemetry_sdk::{
14 propagation::TraceContextPropagator,
15 trace::{SdkTracer, SdkTracerProvider},
16 Resource,
17};
18use opentelemetry_semantic_conventions::{attribute::SERVICE_VERSION, SCHEMA_URL};
19use tracing::Subscriber;
20use tracing_opentelemetry::OpenTelemetryLayer;
21use tracing_subscriber::registry::LookupSpan;
22use url::Url;
23
24const HTTP_TRACE_ENDPOINT: &str = "/v1/traces";
27
28pub fn span_layer<S>(
33 service_name: impl Into<Value>,
34 endpoint: &Url,
35 protocol: OtlpProtocol,
36) -> eyre::Result<OpenTelemetryLayer<S, SdkTracer>>
37where
38 for<'span> S: Subscriber + LookupSpan<'span>,
39{
40 global::set_text_map_propagator(TraceContextPropagator::new());
41
42 let resource = build_resource(service_name);
43
44 let span_builder = SpanExporter::builder();
45
46 let span_exporter = match protocol {
47 OtlpProtocol::Http => span_builder.with_http().with_endpoint(endpoint.as_str()).build()?,
48 OtlpProtocol::Grpc => span_builder.with_tonic().with_endpoint(endpoint.as_str()).build()?,
49 };
50
51 let tracer_provider = SdkTracerProvider::builder()
52 .with_resource(resource)
53 .with_batch_exporter(span_exporter)
54 .build();
55
56 global::set_tracer_provider(tracer_provider.clone());
57
58 let tracer = tracer_provider.tracer("reth");
59 Ok(tracing_opentelemetry::layer().with_tracer(tracer))
60}
61
62fn build_resource(service_name: impl Into<Value>) -> Resource {
64 Resource::builder()
65 .with_service_name(service_name)
66 .with_schema_url([KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION"))], SCHEMA_URL)
67 .build()
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
72pub enum OtlpProtocol {
73 Http,
75 Grpc,
77}
78
79impl OtlpProtocol {
80 pub fn validate_endpoint(&self, url: &mut Url) -> eyre::Result<()> {
85 match self {
86 Self::Http => {
87 if !url.path().ends_with(HTTP_TRACE_ENDPOINT) {
88 let path = url.path().trim_end_matches('/');
89 url.set_path(&format!("{}{}", path, HTTP_TRACE_ENDPOINT));
90 }
91 }
92 Self::Grpc => {
93 ensure!(
94 !url.path().ends_with(HTTP_TRACE_ENDPOINT),
95 "OTLP gRPC endpoint should not include {} path, got: {}",
96 HTTP_TRACE_ENDPOINT,
97 url
98 );
99 }
100 }
101 Ok(())
102 }
103}