apollo-opentelemetry 0.8.0

OpenTelemetry configuration types for Apollo platform
Documentation
//! Error types for telemetry initialization.

use std::fmt;

use apollo_errors::Error;
use miette::Diagnostic;

pub use crate::config::processor::RateLimitProcessorConfigError;

/// Exporter type for consistent error reporting and metrics.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum ExporterKind {
    OtlpHttp,
    OtlpGrpc,
    DatadogNative,
    DatadogHttp,
    DatadogGrpc,
    NewRelicHttp,
    NewRelicGrpc,
    HoneycombHttp,
    HoneycombGrpc,
    GrafanaCloudHttp,
    Console,
}

impl ExporterKind {
    pub fn as_str(&self) -> &'static str {
        match self {
            ExporterKind::OtlpHttp => "otlp_http",
            ExporterKind::OtlpGrpc => "otlp_grpc",
            ExporterKind::DatadogNative => "datadog_native",
            ExporterKind::DatadogHttp => "datadog_http",
            ExporterKind::DatadogGrpc => "datadog_grpc",
            ExporterKind::NewRelicHttp => "new_relic_http",
            ExporterKind::NewRelicGrpc => "new_relic_grpc",
            ExporterKind::HoneycombHttp => "honeycomb_http",
            ExporterKind::HoneycombGrpc => "honeycomb_grpc",
            ExporterKind::GrafanaCloudHttp => "grafana_cloud_http",
            ExporterKind::Console => "console",
        }
    }
}

impl fmt::Display for ExporterKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.as_str())
    }
}

/// Errors that can occur during telemetry initialization.
#[derive(Debug, Error, Diagnostic)]
pub enum InitError {
    /// Failed to create an exporter.
    #[error("failed to create {exporter} exporter: {reason}")]
    #[diagnostic(code(telemetry::exporter))]
    Exporter {
        #[extension]
        exporter: ExporterKind,
        #[extension]
        reason: String,
    },

    /// Failed to create a processor.
    #[error("failed to create {processor} processor: {reason}")]
    #[diagnostic(code(telemetry::processor))]
    Processor {
        #[extension]
        processor: String,
        #[extension]
        reason: String,
    },

    /// Failed to set up a bridge (e.g., log or tracing bridge).
    #[error("failed to set up {bridge} bridge: {reason}")]
    #[diagnostic(code(telemetry::bridge))]
    Bridge {
        #[extension]
        bridge: String,
        #[extension]
        reason: String,
    },

    /// Rate-limited processor configuration is invalid.
    #[error(transparent)]
    #[diagnostic(transparent)]
    RateLimitProcessorConfig(#[from] RateLimitProcessorConfigError),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn exporter_error_display() {
        let err = InitError::Exporter {
            exporter: ExporterKind::OtlpHttp,
            reason: "connection failed".to_string(),
        };
        let display = format!("{}", err);
        assert!(display.contains("otlp_http"));
        assert!(display.contains("connection failed"));
    }

    #[test]
    fn processor_error_display() {
        let err = InitError::Processor {
            processor: "batch".to_string(),
            reason: "invalid config".to_string(),
        };
        let display = format!("{}", err);
        assert!(display.contains("batch"));
        assert!(display.contains("invalid config"));
    }

    #[test]
    fn bridge_error_display() {
        let err = InitError::Bridge {
            bridge: "tracing".to_string(),
            reason: "already initialized".to_string(),
        };
        let display = format!("{}", err);
        assert!(display.contains("tracing"));
        assert!(display.contains("already initialized"));
    }

    #[test]
    fn error_is_debug() {
        let err = InitError::Exporter {
            exporter: ExporterKind::OtlpHttp,
            reason: "test reason".to_string(),
        };
        let debug = format!("{:?}", err);
        assert!(debug.contains("Exporter"));
    }
}