diff --git a/examples/self-diagnostics/src/main.rs b/examples/self-diagnostics/src/main.rs index 68716794aa..7063fcdc75 100644 --- a/examples/self-diagnostics/src/main.rs +++ b/examples/self-diagnostics/src/main.rs @@ -1,7 +1,8 @@ use opentelemetry::global::{self, set_error_handler, Error as OtelError}; use opentelemetry::KeyValue; use opentelemetry_appender_tracing::layer; -use opentelemetry_otlp::WithExportConfig; +use opentelemetry_otlp::{LogExporter, MetricExporter, WithExporterConfig}; +use opentelemetry_sdk::metrics::PeriodicReader; use tracing_subscriber::filter::{EnvFilter, LevelFilter}; use tracing_subscriber::fmt; use tracing_subscriber::prelude::*; @@ -51,15 +52,16 @@ fn custom_error_handler(err: OtelError) { } fn init_logger_provider() -> opentelemetry_sdk::logs::LoggerProvider { - let provider = opentelemetry_otlp::new_pipeline() - .logging() - .with_exporter( - opentelemetry_otlp::new_exporter() - .http() - .with_endpoint("http://localhost:4318/v1/logs"), - ) - .install_batch(opentelemetry_sdk::runtime::Tokio) + let exporter = LogExporter::builder() + .with_http() + .with_endpoint("http://localhost:4318/v1/logs") + .build() .unwrap(); + + let provider = opentelemetry_sdk::logs::LoggerProvider::builder() + .with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio) + .build(); + let cloned_provider = provider.clone(); // Add a tracing filter to filter events from crates used by opentelemetry-otlp. @@ -107,16 +109,20 @@ fn init_logger_provider() -> opentelemetry_sdk::logs::LoggerProvider { } fn init_meter_provider() -> opentelemetry_sdk::metrics::SdkMeterProvider { - let provider = opentelemetry_otlp::new_pipeline() - .metrics(opentelemetry_sdk::runtime::Tokio) - .with_period(std::time::Duration::from_secs(1)) - .with_exporter( - opentelemetry_otlp::new_exporter() - .http() - .with_endpoint("http://localhost:4318/v1/metrics"), - ) + let exporter = MetricExporter::builder() + .with_tonic() + .with_endpoint("http://localhost:4318/v1/metrics") .build() .unwrap(); + + let reader = PeriodicReader::builder(exporter, opentelemetry_sdk::runtime::Tokio) + .with_interval(std::time::Duration::from_secs(1)) + .build(); + + let provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder() + .with_reader(reader) + .build(); + let cloned_provider = provider.clone(); global::set_meter_provider(cloned_provider); provider diff --git a/examples/tracing-jaeger/src/main.rs b/examples/tracing-jaeger/src/main.rs index e6c3dfdb2b..a278dbef0a 100644 --- a/examples/tracing-jaeger/src/main.rs +++ b/examples/tracing-jaeger/src/main.rs @@ -4,27 +4,26 @@ use opentelemetry::{ trace::{TraceContextExt, TraceError, Tracer}, KeyValue, }; -use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::trace::TracerProvider; use opentelemetry_sdk::{runtime, trace as sdktrace, Resource}; use opentelemetry_semantic_conventions::resource::SERVICE_NAME; use std::error::Error; fn init_tracer_provider() -> Result { - opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter( - opentelemetry_otlp::new_exporter() - .tonic() - .with_endpoint("http://localhost:4317"), - ) - .with_trace_config( + let exporter = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .build()?; + + Ok(TracerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) + .with_config( sdktrace::Config::default().with_resource(Resource::new(vec![KeyValue::new( SERVICE_NAME, "tracing-jaeger", )])), ) - .install_batch(runtime::Tokio) + .build()) } #[tokio::main] diff --git a/opentelemetry-otlp/Cargo.toml b/opentelemetry-otlp/Cargo.toml index 272481053e..5de9e33e3c 100644 --- a/opentelemetry-otlp/Cargo.toml +++ b/opentelemetry-otlp/Cargo.toml @@ -51,6 +51,7 @@ opentelemetry_sdk = { features = ["trace", "rt-tokio", "testing"], path = "../op tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } futures-util = { workspace = true } temp-env = { workspace = true } +pretty_assertions = "1.4.1" [features] # telemetry pillars and functions diff --git a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs index 006d8e4e2e..6b844838b1 100644 --- a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs @@ -6,9 +6,14 @@ use opentelemetry::{ KeyValue, }; use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; -use opentelemetry_otlp::Protocol; -use opentelemetry_otlp::{HttpExporterBuilder, WithExportConfig}; -use opentelemetry_sdk::trace::{self as sdktrace, Config}; +use opentelemetry_otlp::WithExporterConfig; +use opentelemetry_otlp::{LogExporter, MetricExporter, Protocol, SpanExporter}; +use opentelemetry_sdk::{ + logs::LoggerProvider, + metrics::{PeriodicReader, SdkMeterProvider}, + runtime, + trace::{self as sdktrace, TracerProvider}, +}; use opentelemetry_sdk::{ logs::{self as sdklogs}, Resource, @@ -18,6 +23,9 @@ use tracing::info; use tracing_subscriber::prelude::*; use tracing_subscriber::EnvFilter; +#[cfg(feature = "hyper")] +use opentelemetry_otlp::WithHttpConfig; + #[cfg(feature = "hyper")] mod hyper; @@ -28,47 +36,46 @@ static RESOURCE: Lazy = Lazy::new(|| { )]) }); -fn http_exporter() -> HttpExporterBuilder { - let exporter = opentelemetry_otlp::new_exporter().http(); +fn init_logs() -> Result { + let exporter_builder = LogExporter::builder() + .with_http() + .with_endpoint("http://localhost:4318/v1/logs") + .with_protocol(Protocol::HttpBinary); + #[cfg(feature = "hyper")] - let exporter = exporter.with_http_client(hyper::HyperClient::default()); - exporter -} + let exporter_builder = exporter_builder.with_http_client(hyper::HyperClient::default()); -fn init_logs() -> Result { - opentelemetry_otlp::new_pipeline() - .logging() + let exporter = exporter_builder.build()?; + + Ok(LoggerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) .with_resource(RESOURCE.clone()) - .with_exporter( - http_exporter() - .with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format - .with_endpoint("http://localhost:4318/v1/logs"), - ) - .install_batch(opentelemetry_sdk::runtime::Tokio) + .build()) } fn init_tracer_provider() -> Result { - opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter( - http_exporter() - .with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format - .with_endpoint("http://localhost:4318/v1/traces"), - ) - .with_trace_config(Config::default().with_resource(RESOURCE.clone())) - .install_batch(opentelemetry_sdk::runtime::Tokio) + let exporter = SpanExporter::builder() + .with_http() + .with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format + .with_endpoint("http://localhost:4318/v1/traces") + .build()?; + Ok(TracerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) + .with_resource(RESOURCE.clone()) + .build()) } fn init_metrics() -> Result { - opentelemetry_otlp::new_pipeline() - .metrics(opentelemetry_sdk::runtime::Tokio) - .with_exporter( - http_exporter() - .with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format - .with_endpoint("http://localhost:4318/v1/metrics"), - ) + let exporter = MetricExporter::builder() + .with_http() + .with_protocol(Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format + .with_endpoint("http://localhost:4318/v1/metrics") + .build()?; + + Ok(SdkMeterProvider::builder() + .with_reader(PeriodicReader::builder(exporter, runtime::Tokio).build()) .with_resource(RESOURCE.clone()) - .build() + .build()) } #[tokio::main] diff --git a/opentelemetry-otlp/examples/basic-otlp/src/main.rs b/opentelemetry-otlp/examples/basic-otlp/src/main.rs index f931e592e2..29c94b95e4 100644 --- a/opentelemetry-otlp/examples/basic-otlp/src/main.rs +++ b/opentelemetry-otlp/examples/basic-otlp/src/main.rs @@ -8,7 +8,9 @@ use opentelemetry::{ KeyValue, }; use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; -use opentelemetry_otlp::{ExportConfig, WithExportConfig}; +use opentelemetry_otlp::{LogExporter, MetricExporter, SpanExporter, WithExporterConfig}; +use opentelemetry_sdk::logs::LoggerProvider; +use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider}; use opentelemetry_sdk::trace::Config; use opentelemetry_sdk::{runtime, trace as sdktrace, Resource}; use std::error::Error; @@ -24,43 +26,38 @@ static RESOURCE: Lazy = Lazy::new(|| { }); fn init_tracer_provider() -> Result { - opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter( - opentelemetry_otlp::new_exporter() - .tonic() - .with_endpoint("http://localhost:4317"), - ) - .with_trace_config(Config::default().with_resource(RESOURCE.clone())) - .install_batch(runtime::Tokio) + let exporter = SpanExporter::builder() + .with_tonic() + .with_endpoint("0.0.0.0:4317") + .build()?; + Ok(sdktrace::TracerProvider::builder() + .with_config(Config::default().with_resource(RESOURCE.clone())) + .with_batch_exporter(exporter, runtime::Tokio) + .build()) } fn init_metrics() -> Result { - let export_config = ExportConfig { - endpoint: "http://localhost:4317".to_string(), - ..ExportConfig::default() - }; - opentelemetry_otlp::new_pipeline() - .metrics(runtime::Tokio) - .with_exporter( - opentelemetry_otlp::new_exporter() - .tonic() - .with_export_config(export_config), - ) + let exporter = MetricExporter::builder().with_tonic().build()?; + + let reader = PeriodicReader::builder(exporter, runtime::Tokio).build(); + + Ok(SdkMeterProvider::builder() + .with_reader(reader) .with_resource(RESOURCE.clone()) - .build() + .build()) } fn init_logs() -> Result { - opentelemetry_otlp::new_pipeline() - .logging() + let exporter = LogExporter::builder() + .with_tonic() + .with_protocol(opentelemetry_otlp::Protocol::HttpBinary) //can be changed to `Protocol::HttpJson` to export in JSON format + .with_endpoint("http://localhost:4318/v1/logs") + .build()?; + + Ok(LoggerProvider::builder() .with_resource(RESOURCE.clone()) - .with_exporter( - opentelemetry_otlp::new_exporter() - .tonic() - .with_endpoint("http://localhost:4317"), - ) - .install_batch(runtime::Tokio) + .with_batch_exporter(exporter, runtime::Tokio) + .build()) } #[tokio::main] diff --git a/opentelemetry-otlp/src/exporter/http/metrics.rs b/opentelemetry-otlp/src/exporter/http/metrics.rs index 8fcf1bc362..a1b78ccb41 100644 --- a/opentelemetry-otlp/src/exporter/http/metrics.rs +++ b/opentelemetry-otlp/src/exporter/http/metrics.rs @@ -5,12 +5,12 @@ use http::{header::CONTENT_TYPE, Method}; use opentelemetry::metrics::{MetricsError, Result}; use opentelemetry_sdk::metrics::data::ResourceMetrics; -use crate::{metric::MetricsClient, Error}; +use crate::{metric::MetricClient, Error}; use super::OtlpHttpClient; #[async_trait] -impl MetricsClient for OtlpHttpClient { +impl MetricClient for OtlpHttpClient { async fn export(&self, metrics: &mut ResourceMetrics) -> Result<()> { let client = self .client diff --git a/opentelemetry-otlp/src/exporter/http/mod.rs b/opentelemetry-otlp/src/exporter/http/mod.rs index 46fb9bdb12..0f1686a715 100644 --- a/opentelemetry-otlp/src/exporter/http/mod.rs +++ b/opentelemetry-otlp/src/exporter/http/mod.rs @@ -3,7 +3,7 @@ use super::{ OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT, }; use crate::{ - ExportConfig, Protocol, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, + ExporterConfig, Protocol, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, }; use http::{HeaderName, HeaderValue, Uri}; @@ -43,7 +43,7 @@ mod trace; ), derive(Default) )] -pub(crate) struct HttpConfig { +pub struct HttpConfig { /// Select the HTTP client client: Option>, @@ -80,35 +80,34 @@ impl Default for HttpConfig { /// # fn main() -> Result<(), Box> { /// // Create a span exporter you can use to when configuring tracer providers /// # #[cfg(feature="trace")] -/// let span_exporter = opentelemetry_otlp::new_exporter().http().build_span_exporter()?; +/// let span_exporter = opentelemetry_otlp::SpanExporter::builder().with_http().build()?; /// /// // Create a metrics exporter you can use when configuring meter providers /// # #[cfg(feature="metrics")] -/// let metrics_exporter = opentelemetry_otlp::new_exporter() -/// .http() -/// .build_metrics_exporter( -/// Temporality::default(), -/// )?; +/// let metrics_exporter = opentelemetry_otlp::MetricExporter::builder() +/// .with_http() +/// .with_temporality(Temporality::default()) +/// .build()?; /// /// // Create a log exporter you can use when configuring logger providers /// # #[cfg(feature="logs")] -/// let log_exporter = opentelemetry_otlp::new_exporter().http().build_log_exporter()?; +/// let log_exporter = opentelemetry_otlp::LogExporter::builder().with_http().build()?; /// # Ok(()) /// # } /// ``` /// #[derive(Debug)] pub struct HttpExporterBuilder { - pub(crate) exporter_config: ExportConfig, + pub(crate) exporter_config: ExporterConfig, pub(crate) http_config: HttpConfig, } impl Default for HttpExporterBuilder { fn default() -> Self { HttpExporterBuilder { - exporter_config: ExportConfig { + exporter_config: ExporterConfig { protocol: default_protocol(), - ..ExportConfig::default() + ..ExporterConfig::default() }, http_config: HttpConfig { headers: Some(default_headers()), @@ -120,30 +119,13 @@ impl Default for HttpExporterBuilder { impl HttpExporterBuilder { /// Specify the OTLP protocol to be used by the exporter + /// + /// TODO: Are we supposed to be allowing Grpc here? pub fn with_protocol(mut self, protocol: Protocol) -> Self { self.exporter_config.protocol = protocol; self } - /// Assign client implementation - pub fn with_http_client(mut self, client: T) -> Self { - self.http_config.client = Some(Arc::new(client)); - self - } - - /// Set additional headers to send to the collector. - pub fn with_headers(mut self, headers: HashMap) -> Self { - // headers will be wrapped, so we must do some logic to unwrap first. - let mut inst_headers = self.http_config.headers.unwrap_or_default(); - inst_headers.extend( - headers - .into_iter() - .map(|(key, value)| (key, super::url_decode(&value).unwrap_or(value))), - ); - self.http_config.headers = Some(inst_headers); - self - } - fn build_client( &mut self, signal_endpoint_var: &str, @@ -248,7 +230,7 @@ impl HttpExporterBuilder { pub fn build_metrics_exporter( mut self, temporality: opentelemetry_sdk::metrics::data::Temporality, - ) -> opentelemetry::metrics::Result { + ) -> opentelemetry::metrics::Result { use crate::{ OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_HEADERS, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT, @@ -261,7 +243,7 @@ impl HttpExporterBuilder { OTEL_EXPORTER_OTLP_METRICS_HEADERS, )?; - Ok(crate::MetricsExporter::new(client, temporality)) + Ok(crate::MetricExporter::new(client, temporality)) } } @@ -405,11 +387,66 @@ fn add_header_from_string(input: &str, headers: &mut HashMap &mut HttpConfig; +} + +/// Expose interface for modifying builder config. +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasHttpConfig for HttpExporterBuilder { + fn http_client_config(&mut self) -> &mut HttpConfig { + &mut self.http_config + } +} + +/// This trait will be implemented for every struct that implemented [`HasHttpConfig`] trait. +/// +/// ## Examples +/// ``` +/// # #[cfg(all(feature = "trace", feature = "grpc-tonic"))] +/// # { +/// use crate::opentelemetry_otlp::WithHttpConfig; +/// let exporter_builder = opentelemetry_otlp::SpanExporter::builder() +/// .with_http() +/// .with_headers(std::collections::HashMap::new()); +/// # } +/// ``` +pub trait WithHttpConfig { + /// Assign client implementation + fn with_http_client(self, client: T) -> Self; + + /// Set additional headers to send to the collector. + fn with_headers(self, headers: HashMap) -> Self; +} + +impl WithHttpConfig for B { + fn with_http_client(mut self, client: T) -> Self { + self.http_client_config().client = Some(Arc::new(client)); + self + } + + fn with_headers(mut self, headers: HashMap) -> Self { + // headers will be wrapped, so we must do some logic to unwrap first. + self.http_client_config() + .headers + .iter_mut() + .zip(headers) + .for_each(|(http_client_headers, (key, value))| { + http_client_headers.insert(key, super::url_decode(&value).unwrap_or(value)); + }); + self + } +} + #[cfg(test)] mod tests { + use crate::exporter::http::HttpConfig; use crate::exporter::tests::run_env_test; use crate::{ - new_exporter, WithExportConfig, OTEL_EXPORTER_OTLP_ENDPOINT, + HttpExporterBuilder, WithExporterConfig, WithHttpConfig, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, }; @@ -614,11 +651,46 @@ mod tests { } } + #[test] + fn test_with_header() { + use std::collections::HashMap; + // Arrange + let initial_headers = [("k1".to_string(), "v1".to_string())]; + let extra_headers = [("k2".to_string(), "v2".to_string())]; + let expected_headers = initial_headers.iter().chain(extra_headers.iter()).fold( + HashMap::new(), + |mut acc, (k, v)| { + acc.insert(k.clone(), v.clone()); + acc + }, + ); + let builder = HttpExporterBuilder { + http_config: HttpConfig { + client: None, + headers: Some(HashMap::from([("k1".to_string(), "v1".to_string())])), + }, + exporter_config: crate::ExporterConfig::default(), + }; + + // Act + let builder = builder.with_headers(HashMap::from([("k2".to_string(), "v2".to_string())])); + + // Assert + pretty_assertions::assert_eq!( + builder + .http_config + .headers + .clone() + .expect("headers should always be Some"), + expected_headers, + ); + } + #[test] fn test_http_exporter_endpoint() { // default endpoint should add signal path run_env_test(vec![], || { - let exporter = new_exporter().http(); + let exporter = HttpExporterBuilder::default(); let url = resolve_http_endpoint( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, @@ -632,8 +704,7 @@ mod tests { // if builder endpoint is set, it should not add signal path run_env_test(vec![], || { - let exporter = new_exporter() - .http() + let exporter = HttpExporterBuilder::default() .with_endpoint("http://localhost:4318/v1/tracesbutnotreally"); let url = resolve_http_endpoint( diff --git a/opentelemetry-otlp/src/exporter/mod.rs b/opentelemetry-otlp/src/exporter/mod.rs index d138527d8f..1f386f1bc5 100644 --- a/opentelemetry-otlp/src/exporter/mod.rs +++ b/opentelemetry-otlp/src/exporter/mod.rs @@ -66,7 +66,7 @@ pub(crate) mod tonic; /// Configuration for the OTLP exporter. #[derive(Debug)] -pub struct ExportConfig { +pub struct ExporterConfig { /// The address of the OTLP collector. If it's not provided via builder or environment variables. /// Default address will be used based on the protocol. pub endpoint: String, @@ -78,11 +78,11 @@ pub struct ExportConfig { pub timeout: Duration, } -impl Default for ExportConfig { +impl Default for ExporterConfig { fn default() -> Self { let protocol = default_protocol(); - ExportConfig { + ExporterConfig { endpoint: "".to_string(), // don't use default_endpoint(protocol) here otherwise we // won't know if user provided a value @@ -144,41 +144,43 @@ fn default_headers() -> std::collections::HashMap { headers } -/// Provide access to the export config field within the exporter builders. -pub trait HasExportConfig { - /// Return a mutable reference to the export config within the exporter builders. - fn export_config(&mut self) -> &mut ExportConfig; +/// Provide access to the [ExporterConfig] field within the exporter builders. +pub trait HasExporterConfig { + /// Return a mutable reference to the [ExporterConfig] within the exporter builders. + fn export_config(&mut self) -> &mut ExporterConfig; } +/// Provide [ExporterConfig] access to the [TonicExporterBuilder]. #[cfg(feature = "grpc-tonic")] -impl HasExportConfig for TonicExporterBuilder { - fn export_config(&mut self) -> &mut ExportConfig { - &mut self.exporter_config +impl HasExporterConfig for TonicExporterBuilder { + fn export_config(&mut self) -> &mut ExporterConfig { + &mut self.client_config } } +/// Provide [ExporterConfig] access to the [HttpExporterBuilder]. #[cfg(any(feature = "http-proto", feature = "http-json"))] -impl HasExportConfig for HttpExporterBuilder { - fn export_config(&mut self) -> &mut ExportConfig { +impl HasExporterConfig for HttpExporterBuilder { + fn export_config(&mut self) -> &mut ExporterConfig { &mut self.exporter_config } } -/// Expose methods to override export configuration. +/// Expose methods to override [ExporterConfig]. /// -/// This trait will be implemented for every struct that implemented [`HasExportConfig`] trait. +/// This trait will be implemented for every struct that implemented [`HasExporterConfig`] trait. /// /// ## Examples /// ``` /// # #[cfg(all(feature = "trace", feature = "grpc-tonic"))] /// # { -/// use crate::opentelemetry_otlp::WithExportConfig; -/// let exporter_builder = opentelemetry_otlp::new_exporter() -/// .tonic() +/// use crate::opentelemetry_otlp::WithExporterConfig; +/// let exporter_builder = opentelemetry_otlp::SpanExporter::builder() +/// .with_tonic() /// .with_endpoint("http://localhost:7201"); /// # } /// ``` -pub trait WithExportConfig { +pub trait WithExporterConfig { /// Set the address of the OTLP collector. If not set or set to empty string, the default address is used. fn with_endpoint>(self, endpoint: T) -> Self; /// Set the protocol to use when communicating with the collector. @@ -192,10 +194,10 @@ pub trait WithExportConfig { /// Set the timeout to the collector. fn with_timeout(self, timeout: Duration) -> Self; /// Set export config. This will override all previous configuration. - fn with_export_config(self, export_config: ExportConfig) -> Self; + fn with_export_config(self, export_config: ExporterConfig) -> Self; } -impl WithExportConfig for B { +impl WithExporterConfig for B { fn with_endpoint>(mut self, endpoint: T) -> Self { self.export_config().endpoint = endpoint.into(); self @@ -211,7 +213,7 @@ impl WithExportConfig for B { self } - fn with_export_config(mut self, exporter_config: ExportConfig) -> Self { + fn with_export_config(mut self, exporter_config: ExporterConfig) -> Self { self.export_config().endpoint = exporter_config.endpoint; self.export_config().protocol = exporter_config.protocol; self.export_config().timeout = exporter_config.timeout; @@ -291,7 +293,7 @@ mod tests { #[cfg(any(feature = "http-proto", feature = "http-json"))] #[test] fn test_default_http_endpoint() { - let exporter_builder = crate::new_exporter().http(); + let exporter_builder = crate::HttpExporterBuilder::default(); assert_eq!(exporter_builder.exporter_config.endpoint, ""); } @@ -299,9 +301,9 @@ mod tests { #[cfg(feature = "grpc-tonic")] #[test] fn test_default_tonic_endpoint() { - let exporter_builder = crate::new_exporter().tonic(); + let exporter_builder = crate::TonicExporterBuilder::default(); - assert_eq!(exporter_builder.exporter_config.endpoint, ""); + assert_eq!(exporter_builder.client_config.endpoint, ""); } #[test] diff --git a/opentelemetry-otlp/src/exporter/tonic/metrics.rs b/opentelemetry-otlp/src/exporter/tonic/metrics.rs index 97040d3201..6d17c782f9 100644 --- a/opentelemetry-otlp/src/exporter/tonic/metrics.rs +++ b/opentelemetry-otlp/src/exporter/tonic/metrics.rs @@ -10,7 +10,7 @@ use opentelemetry_sdk::metrics::data::ResourceMetrics; use tonic::{codegen::CompressionEncoding, service::Interceptor, transport::Channel, Request}; use super::BoxInterceptor; -use crate::metric::MetricsClient; +use crate::metric::MetricClient; pub(crate) struct TonicMetricsClient { inner: Mutex>, @@ -50,7 +50,7 @@ impl TonicMetricsClient { } #[async_trait] -impl MetricsClient for TonicMetricsClient { +impl MetricClient for TonicMetricsClient { async fn export(&self, metrics: &mut ResourceMetrics) -> Result<()> { let (mut client, metadata, extensions) = self.inner diff --git a/opentelemetry-otlp/src/exporter/tonic/mod.rs b/opentelemetry-otlp/src/exporter/tonic/mod.rs index 75d6559b02..f831252c65 100644 --- a/opentelemetry-otlp/src/exporter/tonic/mod.rs +++ b/opentelemetry-otlp/src/exporter/tonic/mod.rs @@ -14,7 +14,7 @@ use tonic::transport::ClientTlsConfig; use super::{default_headers, parse_header_string, OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT}; use crate::exporter::Compression; use crate::{ - ExportConfig, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, + ExporterConfig, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, }; @@ -25,7 +25,7 @@ mod logs; mod metrics; #[cfg(feature = "trace")] -mod trace; +pub(crate) mod trace; /// Configuration for [tonic] /// @@ -34,14 +34,14 @@ mod trace; #[non_exhaustive] pub struct TonicConfig { /// Custom metadata entries to send to the collector. - pub metadata: Option, - + pub(crate) metadata: Option, /// TLS settings for the collector endpoint. #[cfg(feature = "tls")] - pub tls_config: Option, - + pub(crate) tls_config: Option, /// The compression algorithm to use when communicating with the collector. - pub compression: Option, + pub(crate) compression: Option, + pub(crate) channel: Option, + pub(crate) interceptor: Option, } impl TryFrom for tonic::codec::CompressionEncoding { @@ -67,21 +67,6 @@ impl TryFrom for tonic::codec::CompressionEncoding { } } -fn resolve_compression( - tonic_config: &TonicConfig, - env_override: &str, -) -> Result, crate::Error> { - if let Some(compression) = tonic_config.compression { - Ok(Some(compression.try_into()?)) - } else if let Ok(compression) = env::var(env_override) { - Ok(Some(compression.parse::()?.try_into()?)) - } else if let Ok(compression) = env::var(OTEL_EXPORTER_OTLP_COMPRESSION) { - Ok(Some(compression.parse::()?.try_into()?)) - } else { - Ok(None) - } -} - /// Configuration for the [tonic] OTLP GRPC exporter. /// /// It allows you to @@ -101,28 +86,25 @@ fn resolve_compression( /// # fn main() -> Result<(), Box> { /// // Create a span exporter you can use to when configuring tracer providers /// # #[cfg(feature="trace")] -/// let span_exporter = opentelemetry_otlp::new_exporter().tonic().build_span_exporter()?; +/// let span_exporter = opentelemetry_otlp::SpanExporter::builder().with_tonic().build()?; /// -/// // Create a metrics exporter you can use when configuring meter providers +/// // Create a metric exporter you can use when configuring meter providers /// # #[cfg(feature="metrics")] -/// let metrics_exporter = opentelemetry_otlp::new_exporter() -/// .tonic() -/// .build_metrics_exporter( -/// Temporality::default(), -/// )?; +/// let metric_exporter = opentelemetry_otlp::MetricExporter::builder() +/// .with_tonic() +/// .with_temporality(Temporality::default()) +/// .build()?; /// /// // Create a log exporter you can use when configuring logger providers /// # #[cfg(feature="logs")] -/// let log_exporter = opentelemetry_otlp::new_exporter().tonic().build_log_exporter()?; +/// let log_exporter = opentelemetry_otlp::LogExporter::builder().with_tonic().build()?; /// # Ok(()) /// # } /// ``` #[derive(Debug)] pub struct TonicExporterBuilder { - pub(crate) exporter_config: ExportConfig, pub(crate) tonic_config: TonicConfig, - pub(crate) channel: Option, - pub(crate) interceptor: Option, + pub(crate) client_config: ExporterConfig, } pub(crate) struct BoxInterceptor(Box); @@ -140,80 +122,28 @@ impl Debug for BoxInterceptor { impl Default for TonicExporterBuilder { fn default() -> Self { - let tonic_config = TonicConfig { - metadata: Some(MetadataMap::from_headers( - (&default_headers()) - .try_into() - .expect("Invalid tonic headers"), - )), - #[cfg(feature = "tls")] - tls_config: None, - compression: None, - }; - TonicExporterBuilder { - exporter_config: ExportConfig { + tonic_config: TonicConfig { + metadata: Some(MetadataMap::from_headers( + (&default_headers()) + .try_into() + .expect("Invalid tonic headers"), + )), + #[cfg(feature = "tls")] + tls_config: None, + compression: None, + channel: Option::default(), + interceptor: Option::default(), + }, + client_config: ExporterConfig { protocol: crate::Protocol::Grpc, ..Default::default() }, - tonic_config, - channel: Option::default(), - interceptor: Option::default(), } } } impl TonicExporterBuilder { - /// Set the TLS settings for the collector endpoint. - #[cfg(feature = "tls")] - pub fn with_tls_config(mut self, tls_config: ClientTlsConfig) -> Self { - self.tonic_config.tls_config = Some(tls_config); - self - } - - /// Set custom metadata entries to send to the collector. - pub fn with_metadata(mut self, metadata: MetadataMap) -> Self { - // extending metadata maps is harder than just casting back/forth - let incoming_headers = metadata.into_headers(); - let mut existing_headers = self - .tonic_config - .metadata - .unwrap_or_default() - .into_headers(); - existing_headers.extend(incoming_headers); - - self.tonic_config.metadata = Some(MetadataMap::from_headers(existing_headers)); - self - } - - /// Set the compression algorithm to use when communicating with the collector. - pub fn with_compression(mut self, compression: Compression) -> Self { - self.tonic_config.compression = Some(compression); - self - } - - /// Use `channel` as tonic's transport channel. - /// this will override tls config and should only be used - /// when working with non-HTTP transports. - /// - /// Users MUST make sure the [`ExportConfig::timeout`] is - /// the same as the channel's timeout. - pub fn with_channel(mut self, channel: tonic::transport::Channel) -> Self { - self.channel = Some(channel); - self - } - - /// Use a custom `interceptor` to modify each outbound request. - /// this can be used to modify the grpc metadata, for example - /// to inject auth tokens. - pub fn with_interceptor(mut self, interceptor: I) -> Self - where - I: tonic::service::Interceptor + Clone + Send + Sync + 'static, - { - self.interceptor = Some(BoxInterceptor(Box::new(interceptor))); - self - } - fn build_channel( self, signal_endpoint_var: &str, @@ -221,12 +151,11 @@ impl TonicExporterBuilder { signal_compression_var: &str, signal_headers_var: &str, ) -> Result<(Channel, BoxInterceptor, Option), crate::Error> { - let tonic_config = self.tonic_config; - let compression = resolve_compression(&tonic_config, signal_compression_var)?; + let compression = self.resolve_compression(signal_compression_var)?; let headers_from_env = parse_headers_from_env(signal_headers_var); let metadata = merge_metadata_with_headers_from_env( - tonic_config.metadata.unwrap_or_default(), + self.tonic_config.metadata.unwrap_or_default(), headers_from_env, ); @@ -245,7 +174,7 @@ impl TonicExporterBuilder { Ok(req) }; - let interceptor = match self.interceptor { + let interceptor = match self.tonic_config.interceptor { Some(mut interceptor) => { BoxInterceptor(Box::new(move |req| interceptor.call(add_metadata(req)?))) } @@ -253,11 +182,11 @@ impl TonicExporterBuilder { }; // If a custom channel was provided, use that channel instead of creating one - if let Some(channel) = self.channel { + if let Some(channel) = self.tonic_config.channel { return Ok((channel, interceptor, compression)); } - let config = self.exporter_config; + let config = self.client_config; // resolving endpoint string // grpc doesn't have a "path" like http(See https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) @@ -291,7 +220,7 @@ impl TonicExporterBuilder { }; #[cfg(feature = "tls")] - let channel = match tonic_config.tls_config { + let channel = match self.tonic_config.tls_config { Some(tls_config) => endpoint .tls_config(tls_config) .map_err(crate::Error::from)?, @@ -306,9 +235,24 @@ impl TonicExporterBuilder { Ok((channel, interceptor, compression)) } + fn resolve_compression( + &self, + env_override: &str, + ) -> Result, crate::Error> { + if let Some(compression) = self.tonic_config.compression { + Ok(Some(compression.try_into()?)) + } else if let Ok(compression) = env::var(env_override) { + Ok(Some(compression.parse::()?.try_into()?)) + } else if let Ok(compression) = env::var(OTEL_EXPORTER_OTLP_COMPRESSION) { + Ok(Some(compression.parse::()?.try_into()?)) + } else { + Ok(None) + } + } + /// Build a new tonic log exporter #[cfg(feature = "logs")] - pub fn build_log_exporter( + pub(crate) fn build_log_exporter( self, ) -> Result { use crate::exporter::tonic::logs::TonicLogsClient; @@ -327,11 +271,11 @@ impl TonicExporterBuilder { /// Build a new tonic metrics exporter #[cfg(feature = "metrics")] - pub fn build_metrics_exporter( + pub(crate) fn build_metrics_exporter( self, temporality: opentelemetry_sdk::metrics::data::Temporality, - ) -> opentelemetry::metrics::Result { - use crate::MetricsExporter; + ) -> opentelemetry::metrics::Result { + use crate::MetricExporter; use metrics::TonicMetricsClient; let (channel, interceptor, compression) = self.build_channel( @@ -343,12 +287,12 @@ impl TonicExporterBuilder { let client = TonicMetricsClient::new(channel, interceptor, compression); - Ok(MetricsExporter::new(client, temporality)) + Ok(MetricExporter::new(client, temporality)) } /// Build a new tonic span exporter #[cfg(feature = "trace")] - pub fn build_span_exporter( + pub(crate) fn build_span_exporter( self, ) -> Result { use crate::exporter::tonic::trace::TonicTracesClient; @@ -396,9 +340,105 @@ fn parse_headers_from_env(signal_headers_var: &str) -> HeaderMap { .unwrap_or_default() } +/// Expose interface for modifying [TonicConfig] fields within the exporter builders. +pub trait HasTonicConfig { + /// Return a mutable reference to the export config within the exporter builders. + fn tonic_config(&mut self) -> &mut TonicConfig; +} + +/// Expose interface for modifying [TonicConfig] fields within the [TonicExporterBuilder]. +impl HasTonicConfig for TonicExporterBuilder { + fn tonic_config(&mut self) -> &mut TonicConfig { + &mut self.tonic_config + } +} + +/// Expose methods to override [TonicConfig]. +/// +/// This trait will be implemented for every struct that implemented [`HasTonicConfig`] trait. +/// +/// ## Examples +/// ``` +/// # #[cfg(all(feature = "trace", feature = "grpc-tonic"))] +/// # { +/// use crate::opentelemetry_otlp::{WithExporterConfig, WithTonicConfig}; +/// let exporter_builder = opentelemetry_otlp::SpanExporter::builder() +/// .with_tonic() +/// .with_compression(opentelemetry_otlp::Compression::Gzip); +/// # } +/// ``` +pub trait WithTonicConfig { + /// Set the TLS settings for the collector endpoint. + #[cfg(feature = "tls")] + fn with_tls_config(self, tls_config: ClientTlsConfig) -> Self; + + /// Set custom metadata entries to send to the collector. + fn with_metadata(self, metadata: MetadataMap) -> Self; + + /// Set the compression algorithm to use when communicating with the collector. + fn with_compression(self, compression: Compression) -> Self; + + /// Use `channel` as tonic's transport channel. + /// this will override tls config and should only be used + /// when working with non-HTTP transports. + /// + /// Users MUST make sure the [`ExporterConfig::timeout`] is + /// the same as the channel's timeout. + fn with_channel(self, channel: tonic::transport::Channel) -> Self; + + /// Use a custom `interceptor` to modify each outbound request. + /// this can be used to modify the grpc metadata, for example + /// to inject auth tokens. + fn with_interceptor(self, interceptor: I) -> Self + where + I: tonic::service::Interceptor + Clone + Send + Sync + 'static; +} + +impl WithTonicConfig for B { + #[cfg(feature = "tls")] + fn with_tls_config(mut self, tls_config: ClientTlsConfig) -> Self { + self.tonic_config().tls_config = Some(tls_config); + self + } + + /// Set custom metadata entries to send to the collector. + fn with_metadata(mut self, metadata: MetadataMap) -> Self { + // extending metadata maps is harder than just casting back/forth + let mut existing_headers = self + .tonic_config() + .metadata + .clone() + .unwrap_or_default() + .into_headers(); + existing_headers.extend(metadata.into_headers()); + + self.tonic_config().metadata = Some(MetadataMap::from_headers(existing_headers)); + self + } + + fn with_compression(mut self, compression: Compression) -> Self { + self.tonic_config().compression = Some(compression); + self + } + + fn with_channel(mut self, channel: tonic::transport::Channel) -> Self { + self.tonic_config().channel = Some(channel); + self + } + + fn with_interceptor(mut self, interceptor: I) -> Self + where + I: tonic::service::Interceptor + Clone + Send + Sync + 'static, + { + self.tonic_config().interceptor = Some(BoxInterceptor(Box::new(interceptor))); + self + } +} + #[cfg(test)] mod tests { use crate::exporter::tests::run_env_test; + use crate::exporter::tonic::WithTonicConfig; #[cfg(feature = "grpc-tonic")] use crate::exporter::Compression; use crate::TonicExporterBuilder; @@ -413,7 +453,9 @@ mod tests { metadata.insert("foo", "bar".parse().unwrap()); let builder = TonicExporterBuilder::default().with_metadata(metadata); let result = builder.tonic_config.metadata.unwrap(); - let foo = result.get("foo").unwrap(); + let foo = result + .get("foo") + .expect("there to always be an entry for foo"); assert_eq!(foo, &MetadataValue::try_from("bar").unwrap()); assert!(result.get("User-Agent").is_some()); diff --git a/opentelemetry-otlp/src/lib.rs b/opentelemetry-otlp/src/lib.rs index 336104672d..c87a856d4a 100644 --- a/opentelemetry-otlp/src/lib.rs +++ b/opentelemetry-otlp/src/lib.rs @@ -36,12 +36,11 @@ //! //! fn main() -> Result<(), Box> { //! // First, create a OTLP exporter builder. Configure it as you need. -//! let otlp_exporter = opentelemetry_otlp::new_exporter().tonic(); +//! let otlp_exporter = opentelemetry_otlp::SpanExporter::builder().with_tonic().build()?; //! // Then pass it into pipeline builder -//! let _ = opentelemetry_otlp::new_pipeline() -//! .tracing() -//! .with_exporter(otlp_exporter) -//! .install_simple()?; +//! let _ = opentelemetry_sdk::trace::TracerProvider::builder() +//! .with_simple_exporter(otlp_exporter) +//! .build(); //! let tracer = global::tracer("my_tracer"); //! tracer.in_span("doing_work", |cx| { //! // Traced app logic here... @@ -70,10 +69,15 @@ //! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))] //! # { //! # fn main() -> Result<(), opentelemetry::trace::TraceError> { -//! let tracer = opentelemetry_otlp::new_pipeline() -//! .tracing() -//! .with_exporter(opentelemetry_otlp::new_exporter().tonic()) -//! .install_batch(opentelemetry_sdk::runtime::AsyncStd)?; +//! let tracer = opentelemetry_sdk::trace::TracerProvider::builder() +//! .with_batch_exporter( +//! opentelemetry_otlp::SpanExporter::builder() +//! .with_tonic() +//! .build()?, +//! opentelemetry_sdk::runtime::AsyncStd, +//! ) +//! .build(); +//! //! # Ok(()) //! # } //! # } @@ -115,18 +119,18 @@ //! //! Example showing how to override all configuration options. //! -//! Generally there are two parts of configuration. One is metrics config -//! or tracing config. Users can config it via [`OtlpTracePipeline`] -//! or [`OtlpMetricPipeline`]. The other is exporting configuration. -//! Users can set those configurations using [`OtlpExporterPipeline`] based -//! on the choice of exporters. +//! Generally there are two parts of configuration. One is the exporter, the other is the provider. +//! Users can configure the exporter using [SpanExporter::builder()] for traces, +//! and [MetricExporter::builder()] + [opentelemetry_sdk::metrics::PeriodicReader::builder()] for metrics. +//! Once you have an exporter, you can add it to either a [opentelemetry_sdk::trace::TracerProvider::builder()] for traces, +//! or [opentelemetry_sdk::metrics::SdkMeterProvider::builder()] for metrics. //! //! ```no_run //! use opentelemetry::{global, KeyValue, trace::Tracer}; //! use opentelemetry_sdk::{trace::{self, RandomIdGenerator, Sampler}, Resource}; //! # #[cfg(feature = "metrics")] //! use opentelemetry_sdk::metrics::data::Temporality; -//! use opentelemetry_otlp::{Protocol, WithExportConfig, ExportConfig}; +//! use opentelemetry_otlp::{Protocol, WithExporterConfig, WithTonicConfig}; //! use std::time::Duration; //! # #[cfg(feature = "grpc-tonic")] //! use tonic::metadata::*; @@ -139,26 +143,24 @@ //! map.insert("x-host", "example.com".parse().unwrap()); //! map.insert("x-number", "123".parse().unwrap()); //! map.insert_bin("trace-proto-bin", MetadataValue::from_bytes(b"[binary data]")); -//! -//! let tracer_provider = opentelemetry_otlp::new_pipeline() -//! .tracing() -//! .with_exporter( -//! opentelemetry_otlp::new_exporter() -//! .tonic() -//! .with_endpoint("http://localhost:4317") -//! .with_timeout(Duration::from_secs(3)) -//! .with_metadata(map) -//! ) -//! .with_trace_config( -//! trace::config() +//! let exporter = opentelemetry_otlp::SpanExporter::builder() +//! .with_tonic() +//! .with_endpoint("http://localhost:4317") +//! .with_timeout(Duration::from_secs(3)) +//! .with_metadata(map) +//! .build()?; +//! +//! let tracer_provider = opentelemetry_sdk::trace::TracerProvider::builder() +//! .with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio) +//! .with_config( +//! trace::Config::default() //! .with_sampler(Sampler::AlwaysOn) //! .with_id_generator(RandomIdGenerator::default()) //! .with_max_events_per_span(64) //! .with_max_attributes_per_span(16) //! .with_max_events_per_span(16) //! .with_resource(Resource::new(vec![KeyValue::new("service.name", "example")])), -//! ) -//! .install_batch(opentelemetry_sdk::runtime::Tokio)?; +//! ).build(); //! global::set_tracer_provider(tracer_provider); //! let tracer = global::tracer("tracer-name"); //! # tracer @@ -166,23 +168,22 @@ //! //! # #[cfg(all(feature = "metrics", feature = "grpc-tonic"))] //! # { -//! let export_config = ExportConfig { -//! endpoint: "http://localhost:4317".to_string(), -//! timeout: Duration::from_secs(3), -//! protocol: Protocol::Grpc -//! }; -//! -//! let meter = opentelemetry_otlp::new_pipeline() -//! .metrics(opentelemetry_sdk::runtime::Tokio) -//! .with_exporter( -//! opentelemetry_otlp::new_exporter() -//! .tonic() -//! .with_export_config(export_config), -//! // can also config it using with_* functions like the tracing part above. -//! ) -//! .with_resource(Resource::new(vec![KeyValue::new("service.name", "example")])) -//! .with_period(Duration::from_secs(3)) +//! let exporter = opentelemetry_otlp::MetricExporter::builder() +//! .with_tonic() +//! .with_endpoint("http://localhost:4318/v1/metrics") +//! .with_protocol(Protocol::Grpc) +//! .with_timeout(Duration::from_secs(3)) +//! .build() +//! .unwrap(); +//! +//! let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(exporter, opentelemetry_sdk::runtime::Tokio) +//! .with_interval(std::time::Duration::from_secs(3)) //! .with_timeout(Duration::from_secs(10)) +//! .build(); +//! +//! let provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder() +//! .with_reader(reader) +//! .with_resource(Resource::new(vec![KeyValue::new("service.name", "example")])) //! .build(); //! # } //! @@ -215,44 +216,67 @@ mod exporter; #[cfg(feature = "logs")] +#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] mod logs; #[cfg(feature = "metrics")] +#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] mod metric; #[cfg(feature = "trace")] +#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] mod span; pub use crate::exporter::Compression; -pub use crate::exporter::ExportConfig; +pub use crate::exporter::ExporterConfig; #[cfg(feature = "trace")] +#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] pub use crate::span::{ - OtlpTracePipeline, SpanExporter, SpanExporterBuilder, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_HEADERS, - OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, + SpanExporter, OTEL_EXPORTER_OTLP_TRACES_COMPRESSION, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, + OTEL_EXPORTER_OTLP_TRACES_HEADERS, OTEL_EXPORTER_OTLP_TRACES_TIMEOUT, }; #[cfg(feature = "metrics")] +#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] pub use crate::metric::{ - MetricsExporter, MetricsExporterBuilder, OtlpMetricPipeline, - OTEL_EXPORTER_OTLP_METRICS_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, + MetricExporter, OTEL_EXPORTER_OTLP_METRICS_COMPRESSION, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_HEADERS, OTEL_EXPORTER_OTLP_METRICS_TIMEOUT, }; #[cfg(feature = "logs")] +#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] pub use crate::logs::{ - LogExporter, LogExporterBuilder, OtlpLogPipeline, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS, - OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, + LogExporter, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, + OTEL_EXPORTER_OTLP_LOGS_HEADERS, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, }; +#[cfg(any(feature = "http-proto", feature = "http-json"))] +pub use crate::exporter::http::{HasHttpConfig, WithHttpConfig}; + +#[cfg(feature = "grpc-tonic")] +pub use crate::exporter::tonic::{HasTonicConfig, WithTonicConfig}; + pub use crate::exporter::{ - HasExportConfig, WithExportConfig, OTEL_EXPORTER_OTLP_COMPRESSION, OTEL_EXPORTER_OTLP_ENDPOINT, - OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_PROTOCOL, - OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT, OTEL_EXPORTER_OTLP_TIMEOUT, + HasExporterConfig, WithExporterConfig, OTEL_EXPORTER_OTLP_COMPRESSION, + OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_ENDPOINT_DEFAULT, OTEL_EXPORTER_OTLP_HEADERS, + OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT, OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT, }; use opentelemetry_sdk::export::ExportError; +/// Type to indicate the builder does not have a client set. +#[derive(Debug, Default, Clone)] +pub struct NoClientBuilderSet; + +/// Type hold the builder export client and indicate it has been set. +#[cfg(feature = "grpc-tonic")] +#[derive(Debug, Default)] +pub struct TonicClientBuilderSet(TonicExporterBuilder); + +/// Type hold the builder export client and indicate it has been set. +#[cfg(any(feature = "http-proto", feature = "http-json"))] +#[derive(Debug, Default)] +pub struct HttpClientBuilderSet(HttpExporterBuilder); + #[cfg(any(feature = "http-proto", feature = "http-json"))] pub use crate::exporter::http::HttpExporterBuilder; @@ -262,55 +286,6 @@ pub use crate::exporter::tonic::{TonicConfig, TonicExporterBuilder}; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; -/// General builder for both tracing and metrics. -#[derive(Debug)] -pub struct OtlpPipeline; - -/// Build a OTLP metrics or tracing exporter builder. See functions below to understand -/// what's currently supported. -#[derive(Debug)] -pub struct OtlpExporterPipeline; - -impl OtlpExporterPipeline { - /// Use tonic as grpc layer, return a `TonicExporterBuilder` to config tonic and build the exporter. - /// - /// This exporter can be used in both `tracing` and `metrics` pipeline. - #[cfg(feature = "grpc-tonic")] - pub fn tonic(self) -> TonicExporterBuilder { - TonicExporterBuilder::default() - } - - /// Use HTTP as transport layer, return a `HttpExporterBuilder` to config the http transport - /// and build the exporter. - /// - /// This exporter can be used in both `tracing` and `metrics` pipeline. - #[cfg(any(feature = "http-proto", feature = "http-json"))] - pub fn http(self) -> HttpExporterBuilder { - HttpExporterBuilder::default() - } -} - -/// Create a new pipeline builder with the recommended configuration. -/// -/// ## Examples -/// -/// ```no_run -/// fn main() -> Result<(), Box> { -/// # #[cfg(feature = "trace")] -/// let tracing_builder = opentelemetry_otlp::new_pipeline().tracing(); -/// -/// Ok(()) -/// } -/// ``` -pub fn new_pipeline() -> OtlpPipeline { - OtlpPipeline -} - -/// Create a builder to build OTLP metrics exporter or tracing exporter. -pub fn new_exporter() -> OtlpExporterPipeline { - OtlpExporterPipeline -} - /// Wrap type for errors from this crate. #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/opentelemetry-otlp/src/logs.rs b/opentelemetry-otlp/src/logs.rs index 71f5a34b3d..d1fc1bbd6c 100644 --- a/opentelemetry-otlp/src/logs.rs +++ b/opentelemetry-otlp/src/logs.rs @@ -2,20 +2,20 @@ //! //! Defines a [LogExporter] to send logs via the OpenTelemetry Protocol (OTLP) -#[cfg(feature = "grpc-tonic")] -use crate::exporter::tonic::TonicExporterBuilder; - -#[cfg(feature = "http-proto")] -use crate::exporter::http::HttpExporterBuilder; - -use crate::{NoExporterConfig, OtlpPipeline}; use async_trait::async_trait; use std::fmt::Debug; -use opentelemetry::logs::{LogError, LogResult}; +use opentelemetry::logs::LogResult; use opentelemetry_sdk::export::logs::LogBatch; -use opentelemetry_sdk::{runtime::RuntimeChannel, Resource}; + +use crate::{HasExporterConfig, NoClientBuilderSet}; + +#[cfg(feature = "grpc-tonic")] +use crate::{HasTonicConfig, TonicClientBuilderSet, TonicExporterBuilder}; + +#[cfg(any(feature = "http-proto", feature = "http-json"))] +use crate::{HasHttpConfig, HttpClientBuilderSet, HttpExporterBuilder}; /// Compression algorithm to use, defaults to none. pub const OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION"; @@ -32,53 +32,82 @@ pub const OTEL_EXPORTER_OTLP_LOGS_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_LOGS_TIMEO /// Note: this is only supported for HTTP. pub const OTEL_EXPORTER_OTLP_LOGS_HEADERS: &str = "OTEL_EXPORTER_OTLP_LOGS_HEADERS"; -impl OtlpPipeline { - /// Create a OTLP logging pipeline. - pub fn logging(self) -> OtlpLogPipeline { - OtlpLogPipeline { - resource: None, - exporter_builder: NoExporterConfig(()), - batch_config: None, - } - } +#[derive(Debug, Default, Clone)] +pub struct LogExporterBuilder { + client: C, + endpoint: Option, } -/// OTLP log exporter builder -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -#[non_exhaustive] -pub enum LogExporterBuilder { - /// Tonic log exporter builder +impl LogExporterBuilder { + pub fn new() -> Self { + LogExporterBuilder::default() + } + #[cfg(feature = "grpc-tonic")] - Tonic(TonicExporterBuilder), - /// Http log exporter builder - #[cfg(feature = "http-proto")] - Http(HttpExporterBuilder), + pub fn with_tonic(self) -> LogExporterBuilder { + LogExporterBuilder { + client: TonicClientBuilderSet(TonicExporterBuilder::default()), + endpoint: self.endpoint, + } + } + + #[cfg(any(feature = "http-proto", feature = "http-json"))] + pub fn with_http(self) -> LogExporterBuilder { + LogExporterBuilder { + client: HttpClientBuilderSet(HttpExporterBuilder::default()), + endpoint: self.endpoint, + } + } } -impl LogExporterBuilder { - /// Build a OTLP log exporter using the given configuration. - pub fn build_log_exporter(self) -> Result { - match self { - #[cfg(feature = "grpc-tonic")] - LogExporterBuilder::Tonic(builder) => builder.build_log_exporter(), - #[cfg(feature = "http-proto")] - LogExporterBuilder::Http(builder) => builder.build_log_exporter(), +impl LogExporterBuilder { + pub fn with_endpoint(self, endpoint: impl Into) -> LogExporterBuilder { + LogExporterBuilder { + client: self.client, + endpoint: Some(endpoint.into()), } } } #[cfg(feature = "grpc-tonic")] -impl From for LogExporterBuilder { - fn from(exporter: TonicExporterBuilder) -> Self { - LogExporterBuilder::Tonic(exporter) +impl LogExporterBuilder { + pub fn build(self) -> Result { + self.client.0.build_log_exporter() + } +} + +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl LogExporterBuilder { + pub fn build(self) -> Result { + self.client.0.build_log_exporter() + } +} + +#[cfg(feature = "grpc-tonic")] +impl HasExporterConfig for LogExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExporterConfig { + &mut self.client.0.client_config } } -#[cfg(feature = "http-proto")] -impl From for LogExporterBuilder { - fn from(exporter: HttpExporterBuilder) -> Self { - LogExporterBuilder::Http(exporter) +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasExporterConfig for LogExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExporterConfig { + &mut self.client.0.exporter_config + } +} + +#[cfg(feature = "grpc-tonic")] +impl HasTonicConfig for LogExporterBuilder { + fn tonic_config(&mut self) -> &mut crate::TonicConfig { + &mut self.client.0.tonic_config + } +} + +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasHttpConfig for LogExporterBuilder { + fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig { + &mut self.client.0.http_config } } @@ -89,6 +118,11 @@ pub struct LogExporter { } impl LogExporter { + /// Obtain a builder to configure a [LogExporter]. + pub fn builder() -> LogExporterBuilder { + LogExporterBuilder::default() + } + /// Create a new log exporter pub fn new(client: impl opentelemetry_sdk::export::logs::LogExporter + 'static) -> Self { LogExporter { @@ -107,106 +141,3 @@ impl opentelemetry_sdk::export::logs::LogExporter for LogExporter { self.client.set_resource(resource); } } - -/// Recommended configuration for an OTLP exporter pipeline. -#[derive(Debug)] -pub struct OtlpLogPipeline { - exporter_builder: EB, - resource: Option, - batch_config: Option, -} - -impl OtlpLogPipeline { - /// Set the Resource associated with log provider. - pub fn with_resource(self, resource: Resource) -> Self { - OtlpLogPipeline { - resource: Some(resource), - ..self - } - } - - /// Set the batch log processor configuration, and it will override the env vars. - pub fn with_batch_config(mut self, batch_config: opentelemetry_sdk::logs::BatchConfig) -> Self { - self.batch_config = Some(batch_config); - self - } -} - -impl OtlpLogPipeline { - /// Set the OTLP log exporter builder. - pub fn with_exporter>( - self, - pipeline: B, - ) -> OtlpLogPipeline { - OtlpLogPipeline { - exporter_builder: pipeline.into(), - resource: self.resource, - batch_config: self.batch_config, - } - } -} - -impl OtlpLogPipeline { - /// Install the configured log exporter. - /// - /// Returns a [`LoggerProvider`]. - /// - /// [`LoggerProvider`]: opentelemetry_sdk::logs::LoggerProvider - pub fn install_simple(self) -> Result { - Ok(build_simple_with_exporter( - self.exporter_builder.build_log_exporter()?, - self.resource, - )) - } - - /// Install the configured log exporter and a batch log processor using the - /// specified runtime. - /// - /// Returns a [`LoggerProvider`]. - /// - /// [`LoggerProvider`]: opentelemetry_sdk::logs::LoggerProvider - pub fn install_batch( - self, - runtime: R, - ) -> Result { - Ok(build_batch_with_exporter( - self.exporter_builder.build_log_exporter()?, - self.resource, - runtime, - self.batch_config, - )) - } -} - -fn build_simple_with_exporter( - exporter: LogExporter, - resource: Option, -) -> opentelemetry_sdk::logs::LoggerProvider { - let mut provider_builder = - opentelemetry_sdk::logs::LoggerProvider::builder().with_simple_exporter(exporter); - if let Some(resource) = resource { - provider_builder = provider_builder.with_resource(resource); - } - // logger would be created in the appenders like - // opentelemetry-appender-tracing, opentelemetry-appender-log etc. - provider_builder.build() -} - -fn build_batch_with_exporter( - exporter: LogExporter, - resource: Option, - runtime: R, - batch_config: Option, -) -> opentelemetry_sdk::logs::LoggerProvider { - let mut provider_builder = opentelemetry_sdk::logs::LoggerProvider::builder(); - let batch_processor = opentelemetry_sdk::logs::BatchLogProcessor::builder(exporter, runtime) - .with_batch_config(batch_config.unwrap_or_default()) - .build(); - provider_builder = provider_builder.with_log_processor(batch_processor); - - if let Some(resource) = resource { - provider_builder = provider_builder.with_resource(resource); - } - // logger would be created in the tracing appender - provider_builder.build() -} diff --git a/opentelemetry-otlp/src/metric.rs b/opentelemetry-otlp/src/metric.rs index 05646659ed..dbb74f1db3 100644 --- a/opentelemetry-otlp/src/metric.rs +++ b/opentelemetry-otlp/src/metric.rs @@ -3,27 +3,26 @@ //! Defines a [MetricsExporter] to send metric data to backend via OTLP protocol. //! -use crate::{NoExporterConfig, OtlpPipeline}; +#[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] +use crate::HasExporterConfig; + +#[cfg(feature = "http-proto")] +use crate::{exporter::http::HttpExporterBuilder, HttpClientBuilderSet}; + +#[cfg(feature = "grpc-tonic")] +use crate::{exporter::tonic::TonicExporterBuilder, TonicClientBuilderSet}; + +use crate::NoClientBuilderSet; + use async_trait::async_trait; use core::fmt; use opentelemetry::metrics::Result; -#[cfg(feature = "grpc-tonic")] -use crate::exporter::tonic::TonicExporterBuilder; -use opentelemetry_sdk::{ - metrics::{ - data::{ResourceMetrics, Temporality}, - exporter::PushMetricsExporter, - PeriodicReader, SdkMeterProvider, - }, - runtime::Runtime, - Resource, +use opentelemetry_sdk::metrics::{ + data::{ResourceMetrics, Temporality}, + exporter::PushMetricExporter, }; use std::fmt::{Debug, Formatter}; -use std::time; - -#[cfg(feature = "http-proto")] -use crate::exporter::http::HttpExporterBuilder; /// Target to which the exporter is going to send metrics, defaults to https://localhost:4317/v1/metrics. /// Learn about the relationship between this constant and default/spans/logs at @@ -38,207 +37,96 @@ pub const OTEL_EXPORTER_OTLP_METRICS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_MET /// Example: `k1=v1,k2=v2` /// Note: this is only supported for HTTP. pub const OTEL_EXPORTER_OTLP_METRICS_HEADERS: &str = "OTEL_EXPORTER_OTLP_METRICS_HEADERS"; -impl OtlpPipeline { - /// Create a OTLP metrics pipeline. - pub fn metrics(self, rt: RT) -> OtlpMetricPipeline - where - RT: Runtime, - { - OtlpMetricPipeline { - rt, - temporality: None, - exporter_pipeline: NoExporterConfig(()), - resource: None, - period: None, - timeout: None, - } - } -} -/// OTLP metrics exporter builder. -#[derive(Debug)] -#[non_exhaustive] -pub enum MetricsExporterBuilder { - /// Tonic metrics exporter builder - #[cfg(feature = "grpc-tonic")] - Tonic(TonicExporterBuilder), - /// Http metrics exporter builder - #[cfg(feature = "http-proto")] - Http(HttpExporterBuilder), - - /// Missing exporter builder - #[doc(hidden)] - #[cfg(not(any(feature = "http-proto", feature = "grpc-tonic")))] - Unconfigured, -} - -impl MetricsExporterBuilder { - /// Build a OTLP metrics exporter with given configuration. - pub fn build_metrics_exporter(self, temporality: Temporality) -> Result { - match self { - #[cfg(feature = "grpc-tonic")] - MetricsExporterBuilder::Tonic(builder) => builder.build_metrics_exporter(temporality), - #[cfg(feature = "http-proto")] - MetricsExporterBuilder::Http(builder) => builder.build_metrics_exporter(temporality), - #[cfg(not(any(feature = "http-proto", feature = "grpc-tonic")))] - MetricsExporterBuilder::Unconfigured => { - let _ = temporality; - Err(opentelemetry::metrics::MetricsError::Other( - "no configured metrics exporter, enable `http-proto` or `grpc-tonic` feature to configure a metrics exporter".into(), - )) - } - } - } -} - -#[cfg(feature = "grpc-tonic")] -impl From for MetricsExporterBuilder { - fn from(exporter: TonicExporterBuilder) -> Self { - MetricsExporterBuilder::Tonic(exporter) - } +#[derive(Debug, Default, Clone)] +pub struct MetricExporterBuilder { + client: C, + temporality: Temporality, } -#[cfg(feature = "http-proto")] -impl From for MetricsExporterBuilder { - fn from(exporter: HttpExporterBuilder) -> Self { - MetricsExporterBuilder::Http(exporter) +impl MetricExporterBuilder { + pub fn new() -> Self { + MetricExporterBuilder::default() } } -/// Pipeline to build OTLP metrics exporter -/// -/// Note that currently the OTLP metrics exporter only supports tonic as it's grpc layer and tokio as -/// runtime. -pub struct OtlpMetricPipeline { - rt: RT, - temporality: Option, - exporter_pipeline: EB, - resource: Option, - period: Option, - timeout: Option, -} - -impl OtlpMetricPipeline -where - RT: Runtime, -{ - /// Build with resource key value pairs. - pub fn with_resource(self, resource: Resource) -> Self { - OtlpMetricPipeline { - resource: Some(resource), - ..self +impl MetricExporterBuilder { + #[cfg(feature = "grpc-tonic")] + pub fn with_tonic(self) -> MetricExporterBuilder { + MetricExporterBuilder { + client: TonicClientBuilderSet(TonicExporterBuilder::default()), + temporality: self.temporality, } } - /// Build with timeout - pub fn with_timeout(self, timeout: time::Duration) -> Self { - OtlpMetricPipeline { - timeout: Some(timeout), - ..self + #[cfg(any(feature = "http-proto", feature = "http-json"))] + pub fn with_http(self) -> MetricExporterBuilder { + MetricExporterBuilder { + client: HttpClientBuilderSet(HttpExporterBuilder::default()), + temporality: self.temporality, } } - /// Build with period, your metrics will be exported with this period - pub fn with_period(self, period: time::Duration) -> Self { - OtlpMetricPipeline { - period: Some(period), - ..self + #[cfg(any(feature = "http-proto", feature = "http-json", feature = "grpc-tonic"))] + pub fn with_temporality(self, temporality: Temporality) -> MetricExporterBuilder { + MetricExporterBuilder { + client: self.client, + temporality, } } +} - /// Set the [Temporality] of the exporter. - pub fn with_temporality(self, temporality: Temporality) -> Self { - OtlpMetricPipeline { - temporality: Some(temporality), - ..self - } +#[cfg(feature = "grpc-tonic")] +impl MetricExporterBuilder { + pub fn build(self) -> Result { + let exporter = self.client.0.build_metrics_exporter(self.temporality)?; + Ok(exporter) } } -impl OtlpMetricPipeline -where - RT: Runtime, -{ - /// Build with the exporter - pub fn with_exporter>( - self, - pipeline: B, - ) -> OtlpMetricPipeline { - OtlpMetricPipeline { - exporter_pipeline: pipeline.into(), - rt: self.rt, - temporality: self.temporality, - resource: self.resource, - period: self.period, - timeout: self.timeout, - } +#[cfg(feature = "grpc-tonic")] +impl HasExporterConfig for MetricExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExporterConfig { + &mut self.client.0.client_config } } -impl OtlpMetricPipeline -where - RT: Runtime, -{ - /// Build MeterProvider - pub fn build(self) -> Result { - let exporter = self - .exporter_pipeline - .build_metrics_exporter(self.temporality.unwrap_or_default())?; - - let mut builder = PeriodicReader::builder(exporter, self.rt); - - if let Some(period) = self.period { - builder = builder.with_interval(period); - } - if let Some(timeout) = self.timeout { - builder = builder.with_timeout(timeout) - } - - let reader = builder.build(); - - let mut provider = SdkMeterProvider::builder().with_reader(reader); - - if let Some(resource) = self.resource { - provider = provider.with_resource(resource); - } - - let provider = provider.build(); - Ok(provider) +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl MetricExporterBuilder { + pub fn build(self) -> Result { + let exporter = self.client.0.build_metrics_exporter(self.temporality)?; + Ok(exporter) } } -impl Debug for OtlpMetricPipeline { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("OtlpMetricPipeline") - .field("exporter_pipeline", &self.exporter_pipeline) - .field("resource", &self.resource) - .field("period", &self.period) - .field("timeout", &self.timeout) - .finish() +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasExporterConfig for MetricExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExporterConfig { + &mut self.client.0.exporter_config } } /// An interface for OTLP metrics clients #[async_trait] -pub trait MetricsClient: fmt::Debug + Send + Sync + 'static { +pub trait MetricClient: fmt::Debug + Send + Sync + 'static { async fn export(&self, metrics: &mut ResourceMetrics) -> Result<()>; fn shutdown(&self) -> Result<()>; } /// Export metrics in OTEL format. -pub struct MetricsExporter { - client: Box, +pub struct MetricExporter { + client: Box, temporality: Temporality, } -impl Debug for MetricsExporter { +impl Debug for MetricExporter { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("MetricsExporter").finish() } } #[async_trait] -impl PushMetricsExporter for MetricsExporter { +impl PushMetricExporter for MetricExporter { async fn export(&self, metrics: &mut ResourceMetrics) -> Result<()> { self.client.export(metrics).await } @@ -257,10 +145,15 @@ impl PushMetricsExporter for MetricsExporter { } } -impl MetricsExporter { +impl MetricExporter { + /// Obtain a builder to configure a [MetricExporter]. + pub fn builder() -> MetricExporterBuilder { + MetricExporterBuilder::default() + } + /// Create a new metrics exporter - pub fn new(client: impl MetricsClient, temporality: Temporality) -> MetricsExporter { - MetricsExporter { + pub fn new(client: impl MetricClient, temporality: Temporality) -> MetricExporter { + MetricExporter { client: Box::new(client), temporality, } diff --git a/opentelemetry-otlp/src/span.rs b/opentelemetry-otlp/src/span.rs index 6e61cfd1a2..461199cc45 100644 --- a/opentelemetry-otlp/src/span.rs +++ b/opentelemetry-otlp/src/span.rs @@ -5,20 +5,21 @@ use std::fmt::Debug; use futures_core::future::BoxFuture; -use opentelemetry::trace::TraceError; -use opentelemetry_sdk::{ - self as sdk, - export::trace::{ExportResult, SpanData}, -}; -use sdk::runtime::RuntimeChannel; +use opentelemetry_sdk::export::trace::{ExportResult, SpanData}; #[cfg(feature = "grpc-tonic")] -use crate::exporter::tonic::TonicExporterBuilder; +use crate::{ + exporter::tonic::{HasTonicConfig, TonicExporterBuilder}, + TonicClientBuilderSet, +}; #[cfg(any(feature = "http-proto", feature = "http-json"))] -use crate::exporter::http::HttpExporterBuilder; +use crate::{ + exporter::http::{HasHttpConfig, HttpExporterBuilder}, + HttpClientBuilderSet, +}; -use crate::{NoExporterConfig, OtlpPipeline}; +use crate::{exporter::HasExporterConfig, NoClientBuilderSet}; /// Target to which the exporter is going to send spans, defaults to https://localhost:4317/v1/traces. /// Learn about the relationship between this constant and default/metrics/logs at @@ -34,167 +35,72 @@ pub const OTEL_EXPORTER_OTLP_TRACES_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_TRAC /// Note: this is only supported for HTTP. pub const OTEL_EXPORTER_OTLP_TRACES_HEADERS: &str = "OTEL_EXPORTER_OTLP_TRACES_HEADERS"; -impl OtlpPipeline { - /// Create a OTLP tracing pipeline. - pub fn tracing(self) -> OtlpTracePipeline { - OtlpTracePipeline { - exporter_builder: NoExporterConfig(()), - trace_config: None, - batch_config: None, - } - } -} - -/// Recommended configuration for an OTLP exporter pipeline. -/// -/// ## Examples -/// -/// ```no_run -/// let tracing_pipeline = opentelemetry_otlp::new_pipeline().tracing(); -/// ``` -#[derive(Debug)] -pub struct OtlpTracePipeline { - exporter_builder: EB, - trace_config: Option, - batch_config: Option, +#[derive(Debug, Default, Clone)] +pub struct SpanExporterBuilder { + client: C, } -impl OtlpTracePipeline { - /// Set the trace provider configuration. - pub fn with_trace_config(mut self, trace_config: sdk::trace::Config) -> Self { - self.trace_config = Some(trace_config); - self +impl SpanExporterBuilder { + pub fn new() -> Self { + SpanExporterBuilder::default() } - /// Set the batch span processor configuration, and it will override the env vars. - pub fn with_batch_config(mut self, batch_config: sdk::trace::BatchConfig) -> Self { - self.batch_config = Some(batch_config); - self + #[cfg(feature = "grpc-tonic")] + pub fn with_tonic(self) -> SpanExporterBuilder { + SpanExporterBuilder { + client: TonicClientBuilderSet(TonicExporterBuilder::default()), + } } -} -impl OtlpTracePipeline { - /// Set the OTLP span exporter builder. - /// - /// Note that the pipeline will not build the exporter until [`install_batch`] or [`install_simple`] - /// is called. - /// - /// [`install_batch`]: OtlpTracePipeline::install_batch - /// [`install_simple`]: OtlpTracePipeline::install_simple - pub fn with_exporter>( - self, - pipeline: B, - ) -> OtlpTracePipeline { - OtlpTracePipeline { - exporter_builder: pipeline.into(), - trace_config: self.trace_config, - batch_config: self.batch_config, + #[cfg(any(feature = "http-proto", feature = "http-json"))] + pub fn with_http(self) -> SpanExporterBuilder { + SpanExporterBuilder { + client: HttpClientBuilderSet(HttpExporterBuilder::default()), } } } -impl OtlpTracePipeline { - /// Install the configured span exporter. - /// - /// Returns a [`TracerProvider`]. - /// - /// [`TracerProvider`]: opentelemetry::trace::TracerProvider - pub fn install_simple(self) -> Result { - Ok(build_simple_with_exporter( - self.exporter_builder.build_span_exporter()?, - self.trace_config, - )) - } - - /// Install the configured span exporter and a batch span processor using the - /// specified runtime. - /// - /// Returns a [`TracerProvider`]. - /// - /// `install_batch` will panic if not called within a tokio runtime - /// - /// [`TracerProvider`]: opentelemetry::trace::TracerProvider - pub fn install_batch( - self, - runtime: R, - ) -> Result { - Ok(build_batch_with_exporter( - self.exporter_builder.build_span_exporter()?, - self.trace_config, - runtime, - self.batch_config, - )) +#[cfg(feature = "grpc-tonic")] +impl HasExporterConfig for SpanExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExporterConfig { + &mut self.client.0.client_config } } -fn build_simple_with_exporter( - exporter: SpanExporter, - trace_config: Option, -) -> sdk::trace::TracerProvider { - let mut provider_builder = sdk::trace::TracerProvider::builder().with_simple_exporter(exporter); - if let Some(config) = trace_config { - provider_builder = provider_builder.with_config(config); +#[cfg(feature = "grpc-tonic")] +impl HasTonicConfig for SpanExporterBuilder { + fn tonic_config(&mut self) -> &mut crate::TonicConfig { + &mut self.client.0.tonic_config } - - provider_builder.build() } -fn build_batch_with_exporter( - exporter: SpanExporter, - trace_config: Option, - runtime: R, - batch_config: Option, -) -> sdk::trace::TracerProvider { - let mut provider_builder = sdk::trace::TracerProvider::builder(); - let batch_processor = sdk::trace::BatchSpanProcessor::builder(exporter, runtime) - .with_batch_config(batch_config.unwrap_or_default()) - .build(); - provider_builder = provider_builder.with_span_processor(batch_processor); - - if let Some(config) = trace_config { - provider_builder = provider_builder.with_config(config); +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasExporterConfig for SpanExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExporterConfig { + &mut self.client.0.exporter_config } - provider_builder.build() } -/// OTLP span exporter builder. -#[derive(Debug)] -// This enum only used during initialization stage of application. The overhead should be OK. -// Users can also disable the unused features to make the overhead on object size smaller. -#[allow(clippy::large_enum_variant)] -#[non_exhaustive] -pub enum SpanExporterBuilder { - /// Tonic span exporter builder - #[cfg(feature = "grpc-tonic")] - Tonic(TonicExporterBuilder), - /// Http span exporter builder - #[cfg(any(feature = "http-proto", feature = "http-json"))] - Http(HttpExporterBuilder), -} - -impl SpanExporterBuilder { - /// Build a OTLP span exporter using the given tonic configuration and exporter configuration. - pub fn build_span_exporter(self) -> Result { - match self { - #[cfg(feature = "grpc-tonic")] - SpanExporterBuilder::Tonic(builder) => builder.build_span_exporter(), - #[cfg(any(feature = "http-proto", feature = "http-json"))] - SpanExporterBuilder::Http(builder) => builder.build_span_exporter(), - } +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasHttpConfig for SpanExporterBuilder { + fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig { + &mut self.client.0.http_config } } #[cfg(feature = "grpc-tonic")] -impl From for SpanExporterBuilder { - fn from(exporter: TonicExporterBuilder) -> Self { - SpanExporterBuilder::Tonic(exporter) +impl SpanExporterBuilder { + pub fn build(self) -> Result { + let span_exporter = self.client.0.build_span_exporter()?; + Ok(SpanExporter::new(span_exporter)) } } #[cfg(any(feature = "http-proto", feature = "http-json"))] -impl From for SpanExporterBuilder { - fn from(exporter: HttpExporterBuilder) -> Self { - SpanExporterBuilder::Http(exporter) +impl SpanExporterBuilder { + pub fn build(self) -> Result { + let span_exporter = self.client.0.build_span_exporter()?; + Ok(SpanExporter::new(span_exporter)) } } @@ -203,6 +109,11 @@ impl From for SpanExporterBuilder { pub struct SpanExporter(Box); impl SpanExporter { + /// Obtain a builder to configure a [SpanExporter]. + pub fn builder() -> SpanExporterBuilder { + SpanExporterBuilder::default() + } + /// Build a new span exporter from a client pub fn new(client: impl opentelemetry_sdk::export::trace::SpanExporter + 'static) -> Self { SpanExporter(Box::new(client)) diff --git a/opentelemetry-otlp/tests/integration_test/tests/logs.rs b/opentelemetry-otlp/tests/integration_test/tests/logs.rs index 0c4fb773e9..8d2941e3fa 100644 --- a/opentelemetry-otlp/tests/integration_test/tests/logs.rs +++ b/opentelemetry-otlp/tests/integration_test/tests/logs.rs @@ -5,20 +5,26 @@ use log::{info, Level}; use opentelemetry::logs::LogError; use opentelemetry::KeyValue; use opentelemetry_appender_log::OpenTelemetryLogBridge; +use opentelemetry_otlp::LogExporter; +use opentelemetry_sdk::logs::LoggerProvider; use opentelemetry_sdk::{logs as sdklogs, runtime, Resource}; use std::error::Error; use std::fs::File; use std::os::unix::fs::MetadataExt; fn init_logs() -> Result { - opentelemetry_otlp::new_pipeline() - .logging() - .with_exporter(opentelemetry_otlp::new_exporter().tonic()) + let exporter = LogExporter::builder() + .with_tonic() + .with_endpoint("0.0.0.0:4317") + .build()?; + + Ok(LoggerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) .with_resource(Resource::new(vec![KeyValue::new( opentelemetry_semantic_conventions::resource::SERVICE_NAME, "logs-integration-test", )])) - .install_batch(runtime::Tokio) + .build()) } pub async fn logs() -> Result<(), Box> { diff --git a/opentelemetry-otlp/tests/integration_test/tests/traces.rs b/opentelemetry-otlp/tests/integration_test/tests/traces.rs index f7e3b3a510..1f0e06dd25 100644 --- a/opentelemetry-otlp/tests/integration_test/tests/traces.rs +++ b/opentelemetry-otlp/tests/integration_test/tests/traces.rs @@ -16,16 +16,18 @@ use std::io::Write; use std::os::unix::fs::MetadataExt; fn init_tracer_provider() -> Result { - opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter(opentelemetry_otlp::new_exporter().tonic()) - .with_trace_config( + let exporter = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .build()?; + Ok(opentelemetry_sdk::trace::TracerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) + .with_config( sdktrace::Config::default().with_resource(Resource::new(vec![KeyValue::new( opentelemetry_semantic_conventions::resource::SERVICE_NAME, "basic-otlp-tracing-example", )])), ) - .install_batch(runtime::Tokio) + .build()) } const LEMONS_KEY: Key = Key::from_static_str("lemons"); diff --git a/opentelemetry-otlp/tests/smoke.rs b/opentelemetry-otlp/tests/smoke.rs index c217f8f9d6..605771242d 100644 --- a/opentelemetry-otlp/tests/smoke.rs +++ b/opentelemetry-otlp/tests/smoke.rs @@ -2,7 +2,7 @@ use futures_util::StreamExt; use opentelemetry::global; use opentelemetry::global::shutdown_tracer_provider; use opentelemetry::trace::{Span, SpanKind, Tracer}; -use opentelemetry_otlp::WithExportConfig; +use opentelemetry_otlp::{WithExporterConfig, WithTonicConfig}; use opentelemetry_proto::tonic::collector::trace::v1::{ trace_service_server::{TraceService, TraceServiceServer}, ExportTraceServiceRequest, ExportTraceServiceResponse, @@ -84,23 +84,26 @@ async fn smoke_tracer() { println!("Installing tracer provider..."); let mut metadata = tonic::metadata::MetadataMap::new(); metadata.insert("x-header-key", "header-value".parse().unwrap()); - let tracer_provider = opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter( + let tracer_provider = opentelemetry_sdk::trace::TracerProvider::builder() + .with_batch_exporter( #[cfg(feature = "gzip-tonic")] - opentelemetry_otlp::new_exporter() - .tonic() + opentelemetry_otlp::SpanExporter::builder() + .with_tonic() .with_compression(opentelemetry_otlp::Compression::Gzip) .with_endpoint(format!("http://{}", addr)) - .with_metadata(metadata), + .with_metadata(metadata) + .build() + .expect("gzip-tonic SpanExporter failed to build"), #[cfg(not(feature = "gzip-tonic"))] - opentelemetry_otlp::new_exporter() - .tonic() + opentelemetry_otlp::SpanExporter::builder() + .with_tonic() .with_endpoint(format!("http://{}", addr)) - .with_metadata(metadata), + .with_metadata(metadata) + .build() + .expect("NON gzip-tonic SpanExporter failed to build"), + opentelemetry_sdk::runtime::Tokio, ) - .install_batch(opentelemetry_sdk::runtime::Tokio) - .expect("failed to install"); + .build(); global::set_tracer_provider(tracer_provider); diff --git a/opentelemetry-sdk/src/metrics/exporter.rs b/opentelemetry-sdk/src/metrics/exporter.rs index 5fd00d7edd..a4ec47836b 100644 --- a/opentelemetry-sdk/src/metrics/exporter.rs +++ b/opentelemetry-sdk/src/metrics/exporter.rs @@ -11,7 +11,7 @@ use super::data::Temporality; /// /// This is the final component in the metric push pipeline. #[async_trait] -pub trait PushMetricsExporter: Send + Sync + 'static { +pub trait PushMetricExporter: Send + Sync + 'static { /// Export serializes and transmits metric data to a receiver. /// /// All retry logic must be contained in this function. The SDK does not diff --git a/opentelemetry-sdk/src/metrics/periodic_reader.rs b/opentelemetry-sdk/src/metrics/periodic_reader.rs index 67e887d1eb..eca31f704a 100644 --- a/opentelemetry-sdk/src/metrics/periodic_reader.rs +++ b/opentelemetry-sdk/src/metrics/periodic_reader.rs @@ -19,7 +19,7 @@ use opentelemetry::{ use crate::runtime::Runtime; use crate::{ - metrics::{exporter::PushMetricsExporter, reader::SdkProducer}, + metrics::{exporter::PushMetricExporter, reader::SdkProducer}, Resource, }; @@ -55,7 +55,7 @@ pub struct PeriodicReaderBuilder { impl PeriodicReaderBuilder where - E: PushMetricsExporter, + E: PushMetricExporter, RT: Runtime, { fn new(exporter: E, runtime: RT) -> Self { @@ -156,7 +156,7 @@ where /// /// The [runtime] can be selected based on feature flags set for this crate. /// -/// The exporter can be any exporter that implements [PushMetricsExporter] such +/// The exporter can be any exporter that implements [PushMetricExporter] such /// as [opentelemetry-otlp]. /// /// [collect]: MetricReader::collect @@ -169,7 +169,7 @@ where /// use opentelemetry_sdk::metrics::PeriodicReader; /// # fn example(get_exporter: impl Fn() -> E, get_runtime: impl Fn() -> R) /// # where -/// # E: opentelemetry_sdk::metrics::exporter::PushMetricsExporter, +/// # E: opentelemetry_sdk::metrics::exporter::PushMetricExporter, /// # R: opentelemetry_sdk::runtime::Runtime, /// # { /// @@ -182,7 +182,7 @@ where /// ``` #[derive(Clone)] pub struct PeriodicReader { - exporter: Arc, + exporter: Arc, inner: Arc>, } @@ -190,7 +190,7 @@ impl PeriodicReader { /// Configuration options for a periodic reader pub fn builder(exporter: E, runtime: RT) -> PeriodicReaderBuilder where - E: PushMetricsExporter, + E: PushMetricExporter, RT: Runtime, { PeriodicReaderBuilder::new(exporter, runtime) diff --git a/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs b/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs index 372a0836fa..e27ad5f728 100644 --- a/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs +++ b/opentelemetry-sdk/src/testing/metrics/in_memory_exporter.rs @@ -1,6 +1,6 @@ use crate::metrics::data; use crate::metrics::data::{Histogram, Metric, ResourceMetrics, ScopeMetrics, Temporality}; -use crate::metrics::exporter::PushMetricsExporter; +use crate::metrics::exporter::PushMetricExporter; use async_trait::async_trait; use opentelemetry::metrics::MetricsError; use opentelemetry::metrics::Result; @@ -244,7 +244,7 @@ impl InMemoryMetricsExporter { } #[async_trait] -impl PushMetricsExporter for InMemoryMetricsExporter { +impl PushMetricExporter for InMemoryMetricsExporter { async fn export(&self, metrics: &mut ResourceMetrics) -> Result<()> { self.metrics .lock() diff --git a/opentelemetry-sdk/src/trace/provider.rs b/opentelemetry-sdk/src/trace/provider.rs index 9550ce11d2..d5db7c7d63 100644 --- a/opentelemetry-sdk/src/trace/provider.rs +++ b/opentelemetry-sdk/src/trace/provider.rs @@ -261,6 +261,17 @@ impl Builder { Builder { config, ..self } } + /// The sdk [`crate::resource::Resource`] that this provider will use. + pub fn with_resource(self, resource: Resource) -> Self { + Builder { + config: Config { + resource: Cow::Owned(resource), + ..self.config + }, + ..self + } + } + /// Create a new provider from this configuration. pub fn build(self) -> TracerProvider { let mut config = self.config; @@ -440,7 +451,7 @@ mod tests { assert_telemetry_resource(&default_config_provider); }); - // If user provided a resource, use that. + // If user provided config, use that. let custom_config_provider = super::TracerProvider::builder() .with_config(Config { resource: Cow::Owned(Resource::new(vec![KeyValue::new( @@ -453,6 +464,20 @@ mod tests { assert_resource(&custom_config_provider, SERVICE_NAME, Some("test_service")); assert_eq!(custom_config_provider.config().resource.len(), 1); + // If user provided a resource, use that. + let custom_resource_provider = super::TracerProvider::builder() + .with_resource(Resource::new(vec![KeyValue::new( + SERVICE_NAME, + "with_resource_test_service", + )])) + .build(); + assert_resource( + &custom_resource_provider, + SERVICE_NAME, + Some("with_resource_test_service"), + ); + assert_eq!(custom_resource_provider.config().resource.len(), 1); + // If `OTEL_RESOURCE_ATTRIBUTES` is set, read them automatically temp_env::with_var( "OTEL_RESOURCE_ATTRIBUTES", diff --git a/opentelemetry-stdout/examples/basic.rs b/opentelemetry-stdout/examples/basic.rs index 4289c74ec4..7d6b741e96 100644 --- a/opentelemetry-stdout/examples/basic.rs +++ b/opentelemetry-stdout/examples/basic.rs @@ -28,7 +28,7 @@ static RESOURCE: Lazy = Lazy::new(|| { fn init_trace() { let exporter = opentelemetry_stdout::SpanExporter::default(); let provider = TracerProvider::builder() - .with_simple_exporter(exporter) + .with_batch_exporter(exporter, runtime::Tokio) .with_config(Config::default().with_resource(RESOURCE.clone())) .build(); global::set_tracer_provider(provider); diff --git a/opentelemetry-stdout/src/metrics/exporter.rs b/opentelemetry-stdout/src/metrics/exporter.rs index 093748a997..d658b5038b 100644 --- a/opentelemetry-stdout/src/metrics/exporter.rs +++ b/opentelemetry-stdout/src/metrics/exporter.rs @@ -4,7 +4,7 @@ use core::{f64, fmt}; use opentelemetry::metrics::{MetricsError, Result}; use opentelemetry_sdk::metrics::{ data::{self, ScopeMetrics, Temporality}, - exporter::PushMetricsExporter, + exporter::PushMetricExporter, }; use std::fmt::Debug; use std::sync::atomic; @@ -34,7 +34,7 @@ impl fmt::Debug for MetricsExporter { } #[async_trait] -impl PushMetricsExporter for MetricsExporter { +impl PushMetricExporter for MetricsExporter { /// Write Metrics to stdout async fn export(&self, metrics: &mut data::ResourceMetrics) -> Result<()> { if self.is_shutdown.load(atomic::Ordering::SeqCst) {