observability-kit 0.3.0

Configuration and other common entities related to observability
Documentation
pub mod tracing {
    use tracing_subscriber::EnvFilter;

    use crate::errors::Result;

    /// Initialize default tracing infrastructure for the service.
    ///
    /// # Errors
    ///
    /// This function will return an error if some error occurs or initialization was already done before
    /// (it may be the case if you run several tests in parallel).
    pub fn init() -> Result<()> {
        Ok(tracing_subscriber::fmt()
            .with_env_filter(EnvFilter::from_default_env())
            .try_init()?)
    }
}

pub mod otlp_tracing {
    //! Open Telemetry Protocol configured tracing.

    use opentelemetry::{global, trace::TracerProvider, KeyValue};
    use opentelemetry_otlp::{Protocol, SpanExporter, WithExportConfig};
    use opentelemetry_sdk::{
        trace::{RandomIdGenerator, Sampler, SdkTracerProvider},
        Resource,
    };
    use opentelemetry_semantic_conventions::resource::SERVICE_VERSION;
    use serde::Deserialize;
    use tracing::instrument;
    use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

    use crate::errors::Result;

    pub const DEFAULT_OTLP_GRPC_ENDPOINT: &str = "http://localhost:4317";

    #[derive(Clone, Debug, Deserialize)]
    #[serde(default)]
    pub struct Configuration {
        pub otlp_grpc_endpoint: String,
    }

    /// Initialize Open Telemetry Protocol tracing infrastructure for the service.
    ///
    /// It should be supported by modern tracing tools, like [Jaeger](https://medium.com/jaegertracing/introducing-native-support-for-opentelemetry-in-jaeger-eb661be8183c).
    ///
    /// # Errors
    ///
    /// This function will return an error if some error occurs or initialization was already done before
    /// (it may be the case if you run several tests in parallel).
    pub fn try_init(service_name: &'static str) -> Result<()> {
        let configuration = Configuration::from_env()?;
        let new_provider = SdkTracerProvider::builder()
            .with_id_generator(RandomIdGenerator::default())
            .with_sampler(Sampler::ParentBased(Box::new(Sampler::AlwaysOn)))
            .with_resource(
                Resource::builder()
                    .with_service_name(service_name)
                    .with_attribute(KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")))
                    .build(),
            )
            .with_batch_exporter(
                SpanExporter::builder()
                    .with_tonic()
                    .with_protocol(Protocol::Grpc)
                    .with_endpoint(configuration.otlp_grpc_endpoint)
                    .build()?,
            )
            .build();
        let tracer = new_provider.tracer("tracing-otel-subscriber");
        let _previous_global_tracer_provider = global::set_tracer_provider(new_provider);
        tracing_subscriber::registry()
            .with(EnvFilter::from_default_env())
            .with(tracing_subscriber::fmt::layer())
            .with(tracing_opentelemetry::layer().with_tracer(tracer))
            .try_init()?;
        Ok(())
    }

    impl Configuration {
        /// Read configuration from environment.
        ///
        /// # Errors
        ///
        /// Usually doesn't return any errors as default value is used for `OBSERVABILITY_OTLP_GRPC_ENDPOINT`
        /// configuration parameter.
        #[instrument(err(Debug), ret, level = "trace")]
        pub fn from_env() -> Result<Self> {
            Ok(envy::prefixed("OBSERVABILITY_").from_env::<Self>()?)
        }
    }

    impl Default for Configuration {
        fn default() -> Self {
            Self {
                otlp_grpc_endpoint: DEFAULT_OTLP_GRPC_ENDPOINT.to_string(),
            }
        }
    }
}

pub mod errors {
    use std::{error::Error as StdError, result::Result as StdResult};

    use derive_more::{Display, Error, From};
    use envy::Error as EnvyError;
    use opentelemetry_otlp::ExporterBuildError;
    use tracing_subscriber::util::TryInitError;

    #[derive(Debug, Display, Error, From)]
    pub enum Error {
        ExporterBuildError(ExporterBuildError),
        ConfigurationDeserializationFromEnvironmentError(EnvyError),
        SubscriberInitError(TryInitError),
        Other(Box<dyn StdError + Send + Sync>),
    }

    pub type Result<T> = StdResult<T, Error>;
}