unc_o11y/
opentelemetry.rs

1use crate::reload::TracingLayer;
2use opentelemetry::sdk::trace::{self, IdGenerator, Sampler};
3use opentelemetry::sdk::Resource;
4use opentelemetry::KeyValue;
5use opentelemetry_semantic_conventions::resource::SERVICE_NAME;
6use tracing::level_filters::LevelFilter;
7use tracing_subscriber::layer::SubscriberExt;
8use tracing_subscriber::registry::LookupSpan;
9use tracing_subscriber::{reload, Layer};
10use unc_crypto::PublicKey;
11use unc_primitives_core::types::AccountId;
12
13// Doesn't define WARN and ERROR, because the highest verbosity of spans is INFO.
14#[derive(Copy, Clone, Debug, Default, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
15pub enum OpenTelemetryLevel {
16    #[default]
17    OFF,
18    INFO,
19    DEBUG,
20    TRACE,
21}
22
23/// Constructs an OpenTelemetryConfig which sends span data to an external collector.
24//
25// NB: this function is `async` because `install_batch(Tokio)` requires a tokio context to
26// register timers and channels and whatnot.
27pub(crate) async fn add_opentelemetry_layer<S>(
28    opentelemetry_level: OpenTelemetryLevel,
29    chain_id: String,
30    node_public_key: PublicKey,
31    account_id: Option<AccountId>,
32    subscriber: S,
33) -> (TracingLayer<S>, reload::Handle<LevelFilter, S>)
34where
35    S: tracing::Subscriber + for<'span> LookupSpan<'span> + Send + Sync,
36{
37    let filter = get_opentelemetry_filter(opentelemetry_level);
38    let (filter, handle) = reload::Layer::<LevelFilter, S>::new(filter);
39
40    let mut resource = vec![
41        KeyValue::new("chain_id", chain_id),
42        KeyValue::new("node_id", node_public_key.to_string()),
43    ];
44    // Prefer account name as the node name.
45    // Fallback to a node public key if a validator key is unavailable.
46    let service_name = if let Some(account_id) = account_id {
47        resource.push(KeyValue::new("account_id", account_id.to_string()));
48        format!("unc-node:{}", account_id)
49    } else {
50        format!("unc-node:{}", node_public_key)
51    };
52    resource.push(KeyValue::new(SERVICE_NAME, service_name));
53
54    let tracer = opentelemetry_otlp::new_pipeline()
55        .tracing()
56        .with_exporter(opentelemetry_otlp::new_exporter().tonic())
57        .with_trace_config(
58            trace::config()
59                .with_sampler(Sampler::AlwaysOn)
60                .with_id_generator(IdGenerator::default())
61                .with_resource(Resource::new(resource)),
62        )
63        .install_batch(opentelemetry::runtime::Tokio)
64        .unwrap();
65    let layer = tracing_opentelemetry::layer().with_tracer(tracer).with_filter(filter);
66    (subscriber.with(layer), handle)
67}
68
69pub(crate) fn get_opentelemetry_filter(opentelemetry_level: OpenTelemetryLevel) -> LevelFilter {
70    match opentelemetry_level {
71        OpenTelemetryLevel::OFF => LevelFilter::OFF,
72        OpenTelemetryLevel::INFO => LevelFilter::INFO,
73        OpenTelemetryLevel::DEBUG => LevelFilter::DEBUG,
74        OpenTelemetryLevel::TRACE => LevelFilter::TRACE,
75    }
76}