observability-kit 0.1.0

Configuration and other common entities related to observability
Documentation
#![deny(
    explicit_outlives_requirements,
    macro_use_extern_crate,
    missing_debug_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unreachable_pub,
    unsafe_code,
    unused_qualifications,
    unused_results,
    variant_size_differences,
    unused_variables,
    clippy::complexity,
    clippy::nursery,
    clippy::pedantic,
    clippy::perf,
    clippy::style,
    clippy::suspicious,
    clippy::clone_on_ref_ptr,
    clippy::create_dir,
    clippy::dbg_macro,
    clippy::default_numeric_fallback,
    clippy::else_if_without_else,
    clippy::empty_structs_with_brackets,
    clippy::expect_used,
    clippy::get_unwrap,
    clippy::let_underscore_must_use,
    clippy::map_err_ignore,
    clippy::multiple_inherent_impl,
    clippy::panic,
    clippy::panic_in_result_fn,
    clippy::pub_use,
    clippy::rc_mutex,
    clippy::rest_pat_in_fully_bound_structs,
    clippy::same_name_method,
    clippy::self_named_module_files,
    clippy::shadow_reuse,
    clippy::shadow_same,
    clippy::shadow_unrelated,
    clippy::unseparated_literal_suffix,
    clippy::string_to_string,
    clippy::todo,
    clippy::unimplemented,
    clippy::unreachable,
    clippy::unwrap_in_result,
    clippy::unwrap_used,
    clippy::use_debug,
    clippy::verbose_file_reads,
    clippy::wildcard_enum_match_arm
)]

pub mod tracing {
    use eyre::{eyre, Result};
    use tracing_subscriber::EnvFilter;

    /// 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<()> {
        tracing_subscriber::fmt()
            .with_env_filter(EnvFilter::from_default_env())
            .try_init()
            .map_err(|error| eyre!("failed to initialize subscriber: {}", error))
    }
}

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

    use eyre::Result;
    use opentelemetry::KeyValue;
    use opentelemetry_otlp::WithExportConfig;
    use opentelemetry_sdk::{
        runtime,
        trace::{BatchConfig, RandomIdGenerator, Sampler},
        Resource,
    };
    use opentelemetry_semantic_conventions::{
        resource::{SERVICE_NAME, SERVICE_VERSION},
        SCHEMA_URL,
    };
    use serde::Deserialize;
    use tracing::instrument;
    use tracing_opentelemetry::OpenTelemetryLayer;
    use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

    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()?;
        tracing_subscriber::registry()
            .with(EnvFilter::from_default_env())
            .with(tracing_subscriber::fmt::layer())
            .with(OpenTelemetryLayer::new(
                opentelemetry_otlp::new_pipeline()
                    .tracing()
                    .with_trace_config(
                        opentelemetry_sdk::trace::Config::default()
                            .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(1.0))))
                            .with_id_generator(RandomIdGenerator::default())
                            .with_resource(Resource::from_schema_url(
                                [
                                    KeyValue::new(SERVICE_NAME, service_name),
                                    KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")),
                                ],
                                SCHEMA_URL,
                            )),
                    )
                    .with_batch_config(BatchConfig::default())
                    .with_exporter(
                        opentelemetry_otlp::new_exporter()
                            .tonic()
                            .with_endpoint(configuration.otlp_grpc_endpoint),
                    )
                    .install_batch(runtime::Tokio)?,
            ))
            .try_init()?;
        Ok(())
    }

    impl Configuration {
        #[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(),
            }
        }
    }
}