apollo-opentelemetry 0.8.0

OpenTelemetry configuration types for Apollo platform
Documentation
//! Datadog OTLP direct intake exporter builders.
//!
//! Sends telemetry directly to Datadog's OTLP intake endpoints without requiring the Datadog Agent.
//! Authentication is via the `DD-API-KEY` header.
//!
//! See [Datadog OTLP Ingest](https://docs.datadoghq.com/opentelemetry/interoperability/otlp_ingest_in_the_agent/)
//! for details on endpoints and authentication.

use crate::InitError;
use crate::config::exporters::{DatadogGrpcExporterConfig, DatadogHttpExporterConfig};
use crate::error::ExporterKind;

/// Build an HTTP span exporter for Datadog OTLP intake.
///
/// See [Datadog OTLP trace endpoint](https://docs.datadoghq.com/opentelemetry/interoperability/otlp_ingest_in_the_agent/#sending-traces-metrics-and-logs-over-otlphttp).
pub(crate) fn build_http_span_exporter(
    config: &DatadogHttpExporterConfig,
) -> Result<opentelemetry_otlp::SpanExporter, InitError> {
    use opentelemetry_otlp::{SpanExporter, WithExportConfig, WithHttpConfig};

    let mut headers = std::collections::HashMap::new();
    headers.insert(
        "DD-API-KEY".to_string(),
        config
            .api_key
            .unredact()
            .to_str()
            .map_err(|e| InitError::Exporter {
                exporter: ExporterKind::DatadogHttp,
                reason: e.to_string(),
            })?
            .to_string(),
    );

    SpanExporter::builder()
        .with_http()
        .with_endpoint(config.endpoint.as_str())
        .with_headers(headers)
        .build()
        .map_err(|e| InitError::Exporter {
            exporter: ExporterKind::DatadogHttp,
            reason: e.to_string(),
        })
}

/// Build a gRPC span exporter for Datadog OTLP intake.
///
/// See [Datadog OTLP trace endpoint](https://docs.datadoghq.com/opentelemetry/interoperability/otlp_ingest_in_the_agent/#sending-traces-metrics-and-logs-over-otlpgrpc).
pub(crate) fn build_grpc_span_exporter(
    config: &DatadogGrpcExporterConfig,
) -> Result<opentelemetry_otlp::SpanExporter, InitError> {
    use opentelemetry_otlp::{SpanExporter, WithExportConfig, WithTonicConfig};

    let mut metadata = tonic::metadata::MetadataMap::new();
    metadata.insert("dd-api-key", (**config.api_key.unredact()).clone());

    SpanExporter::builder()
        .with_tonic()
        .with_endpoint(config.endpoint.as_str())
        .with_metadata(metadata)
        .build()
        .map_err(|e| InitError::Exporter {
            exporter: ExporterKind::DatadogGrpc,
            reason: e.to_string(),
        })
}

/// Build an HTTP log exporter for Datadog OTLP intake.
///
/// See [Datadog OTLP logs endpoint](https://docs.datadoghq.com/opentelemetry/interoperability/otlp_ingest_in_the_agent/#sending-traces-metrics-and-logs-over-otlphttp).
pub(crate) fn build_http_log_exporter(
    config: &DatadogHttpExporterConfig,
) -> Result<opentelemetry_otlp::LogExporter, InitError> {
    use opentelemetry_otlp::{LogExporter, WithExportConfig, WithHttpConfig};

    let mut headers = std::collections::HashMap::new();
    headers.insert(
        "DD-API-KEY".to_string(),
        config
            .api_key
            .unredact()
            .to_str()
            .map_err(|e| InitError::Exporter {
                exporter: ExporterKind::DatadogHttp,
                reason: e.to_string(),
            })?
            .to_string(),
    );

    LogExporter::builder()
        .with_http()
        .with_endpoint(config.endpoint.as_str())
        .with_headers(headers)
        .build()
        .map_err(|e| InitError::Exporter {
            exporter: ExporterKind::DatadogHttp,
            reason: e.to_string(),
        })
}

/// Build a gRPC log exporter for Datadog OTLP intake.
///
/// See [Datadog OTLP logs endpoint](https://docs.datadoghq.com/opentelemetry/interoperability/otlp_ingest_in_the_agent/#sending-traces-metrics-and-logs-over-otlpgrpc).
pub(crate) fn build_grpc_log_exporter(
    config: &DatadogGrpcExporterConfig,
) -> Result<opentelemetry_otlp::LogExporter, InitError> {
    use opentelemetry_otlp::{LogExporter, WithExportConfig, WithTonicConfig};

    let mut metadata = tonic::metadata::MetadataMap::new();
    metadata.insert("dd-api-key", (**config.api_key.unredact()).clone());

    LogExporter::builder()
        .with_tonic()
        .with_endpoint(config.endpoint.as_str())
        .with_metadata(metadata)
        .build()
        .map_err(|e| InitError::Exporter {
            exporter: ExporterKind::DatadogGrpc,
            reason: e.to_string(),
        })
}

/// Build an HTTP metric exporter for Datadog OTLP intake.
///
/// See [Datadog OTLP metrics endpoint](https://docs.datadoghq.com/opentelemetry/interoperability/otlp_ingest_in_the_agent/#sending-traces-metrics-and-logs-over-otlphttp).
pub(crate) fn build_http_metric_exporter(
    config: &DatadogHttpExporterConfig,
) -> Result<opentelemetry_otlp::MetricExporter, InitError> {
    use opentelemetry_otlp::{MetricExporter, WithExportConfig, WithHttpConfig};

    let mut headers = std::collections::HashMap::new();
    headers.insert(
        "DD-API-KEY".to_string(),
        config
            .api_key
            .unredact()
            .to_str()
            .map_err(|e| InitError::Exporter {
                exporter: ExporterKind::DatadogHttp,
                reason: e.to_string(),
            })?
            .to_string(),
    );

    MetricExporter::builder()
        .with_http()
        .with_endpoint(config.endpoint.as_str())
        .with_headers(headers)
        .build()
        .map_err(|e| InitError::Exporter {
            exporter: ExporterKind::DatadogHttp,
            reason: e.to_string(),
        })
}

/// Build a gRPC metric exporter for Datadog OTLP intake.
///
/// See [Datadog OTLP metrics endpoint](https://docs.datadoghq.com/opentelemetry/interoperability/otlp_ingest_in_the_agent/#sending-traces-metrics-and-logs-over-otlpgrpc).
pub(crate) fn build_grpc_metric_exporter(
    config: &DatadogGrpcExporterConfig,
) -> Result<opentelemetry_otlp::MetricExporter, InitError> {
    use opentelemetry_otlp::{MetricExporter, WithExportConfig, WithTonicConfig};

    let mut metadata = tonic::metadata::MetadataMap::new();
    metadata.insert("dd-api-key", (**config.api_key.unredact()).clone());

    MetricExporter::builder()
        .with_tonic()
        .with_endpoint(config.endpoint.as_str())
        .with_metadata(metadata)
        .build()
        .map_err(|e| InitError::Exporter {
            exporter: ExporterKind::DatadogGrpc,
            reason: e.to_string(),
        })
}

#[cfg(test)]
mod tests {
    use apollo_configuration::parse_yaml;

    use super::*;
    use crate::config::OpenTelemetryConfig;
    use crate::config::SpanExporter;
    use crate::config::traces::SpanProcessor;

    fn get_datadog_otlp_http_config(config: &OpenTelemetryConfig) -> &DatadogHttpExporterConfig {
        match &config.tracer_provider.processors[0] {
            SpanProcessor::Batch(batch) => match &batch.exporter {
                SpanExporter::DatadogOtlpHttp(dd) => dd,
                _ => panic!("Expected DatadogOtlpHttp exporter"),
            },
            _ => panic!("Expected Batch processor"),
        }
    }

    fn get_datadog_otlp_grpc_config(config: &OpenTelemetryConfig) -> &DatadogGrpcExporterConfig {
        match &config.tracer_provider.processors[0] {
            SpanProcessor::Batch(batch) => match &batch.exporter {
                SpanExporter::DatadogOtlpGrpc(dd) => dd,
                _ => panic!("Expected DatadogOtlpGrpc exporter"),
            },
            _ => panic!("Expected Batch processor"),
        }
    }

    #[test]
    fn build_http_span_exporter_from_config() {
        let config: OpenTelemetryConfig = parse_yaml(
            indoc::indoc! {"
                tracer_provider:
                  processors:
                    - batch:
                        exporter:
                          datadog_otlp_http:
                            api_key: test-api-key
                            endpoint: https://trace-otlp.intake.datadoghq.com/api/v0.2/traces
            "},
            &Default::default(),
        )
        .unwrap();

        let dd = get_datadog_otlp_http_config(&config);
        let result = build_http_span_exporter(dd);
        assert!(result.is_ok());
    }

    #[test]
    fn build_http_span_exporter_eu_endpoint() {
        let config: OpenTelemetryConfig = parse_yaml(
            indoc::indoc! {"
                tracer_provider:
                  processors:
                    - batch:
                        exporter:
                          datadog_otlp_http:
                            api_key: test-api-key
                            endpoint: https://trace-otlp.intake.datadoghq.eu/api/v0.2/traces
            "},
            &Default::default(),
        )
        .unwrap();

        let dd = get_datadog_otlp_http_config(&config);
        let result = build_http_span_exporter(dd);
        assert!(result.is_ok());
    }

    #[test]
    fn build_http_log_exporter_from_config() {
        let config: OpenTelemetryConfig = parse_yaml(
            indoc::indoc! {"
                tracer_provider:
                  processors:
                    - batch:
                        exporter:
                          datadog_otlp_http:
                            api_key: test-api-key
                            endpoint: https://http-intake.logs.datadoghq.com/api/v2/otlp/v1/logs
            "},
            &Default::default(),
        )
        .unwrap();

        let dd = get_datadog_otlp_http_config(&config);
        let result = build_http_log_exporter(dd);
        assert!(result.is_ok());
    }

    #[test]
    fn build_http_metric_exporter_from_config() {
        let config: OpenTelemetryConfig = parse_yaml(
            indoc::indoc! {"
                tracer_provider:
                  processors:
                    - batch:
                        exporter:
                          datadog_otlp_http:
                            api_key: test-api-key
                            endpoint: https://api.datadoghq.com/api/v2/otlp/v1/metrics
            "},
            &Default::default(),
        )
        .unwrap();

        let dd = get_datadog_otlp_http_config(&config);
        let result = build_http_metric_exporter(dd);
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn build_grpc_span_exporter_from_config() {
        let config: OpenTelemetryConfig = parse_yaml(
            indoc::indoc! {"
                tracer_provider:
                  processors:
                    - batch:
                        exporter:
                          datadog_otlp_grpc:
                            api_key: test-api-key
                            endpoint: https://trace-otlp.intake.datadoghq.com:4317
            "},
            &Default::default(),
        )
        .unwrap();

        let dd = get_datadog_otlp_grpc_config(&config);
        let result = build_grpc_span_exporter(dd);
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn build_grpc_log_exporter_from_config() {
        let config: OpenTelemetryConfig = parse_yaml(
            indoc::indoc! {"
                tracer_provider:
                  processors:
                    - batch:
                        exporter:
                          datadog_otlp_grpc:
                            api_key: test-api-key
                            endpoint: https://trace-otlp.intake.datadoghq.com:4317
            "},
            &Default::default(),
        )
        .unwrap();

        let dd = get_datadog_otlp_grpc_config(&config);
        let result = build_grpc_log_exporter(dd);
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn build_grpc_metric_exporter_from_config() {
        let config: OpenTelemetryConfig = parse_yaml(
            indoc::indoc! {"
                tracer_provider:
                  processors:
                    - batch:
                        exporter:
                          datadog_otlp_grpc:
                            api_key: test-api-key
                            endpoint: https://trace-otlp.intake.datadoghq.com:4317
            "},
            &Default::default(),
        )
        .unwrap();

        let dd = get_datadog_otlp_grpc_config(&config);
        let result = build_grpc_metric_exporter(dd);
        assert!(result.is_ok());
    }
}