diff --git a/examples/self-diagnostics/src/main.rs b/examples/self-diagnostics/src/main.rs index 64d51a3772..1093b36b45 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, MetricsExporter, WithExportConfig}; +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 = MetricsExporter::builder() + .with_http() + .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/CHANGELOG.md b/opentelemetry-otlp/CHANGELOG.md index e5f6b92c19..fd5b782993 100644 --- a/opentelemetry-otlp/CHANGELOG.md +++ b/opentelemetry-otlp/CHANGELOG.md @@ -10,8 +10,8 @@ Released 2024-Sep-30 - Update `opentelemetry-http` dependency version to 0.26 - Update `opentelemetry-proto` dependency version to 0.26 - Bump MSRV to 1.71.1 [2140](https://github.com/open-telemetry/opentelemetry-rust/pull/2140) -- **BREAKING**: [#2217](https://github.com/open-telemetry/opentelemetry-rust/pull/2217) - - **Replaced**: The `MetricsExporterBuilder` interface is modified from `with_temporality_selector` to `with_temporality` example can be seen below: +- **BREAKING**: + - ([#2217](https://github.com/open-telemetry/opentelemetry-rust/pull/2217)) **Replaced**: The `MetricsExporterBuilder` interface is modified from `with_temporality_selector` to `with_temporality` example can be seen below: Previous Signature: ```rust MetricsExporterBuilder::default().with_temporality_selector(DeltaTemporalitySelector::new()) @@ -20,6 +20,34 @@ Released 2024-Sep-30 ```rust MetricsExporterBuilder::default().with_temporality(Temporality::Delta) ``` + - ([#2221](https://github.com/open-telemetry/opentelemetry-rust/pull/2221)) **Replaced**: + - The `opentelemetry_otlp::new_pipeline().{trace,logging,metrics}()` interface is now replaced with `{TracerProvider,SdkMeterProvider,LoggerProvider}::builder()`. + - The `opentelemetry_otlp::new_exporter()` interface is now replaced with `{SpanExporter,MetricsExporter,LogExporter}::builder()`. + + Pull request [#2221](https://github.com/open-telemetry/opentelemetry-rust/pull/2221) has a detailed migration guide in the description. See example below, + and [basic-otlp](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp/src/main.rs) for more details: + + Previous Signature: + ```rust + let logger_provider: LoggerProvider = opentelemetry_otlp::new_pipeline() + .logging() + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint("http://localhost:4317") + ) + .install_batch(runtime::Tokio)?; + ``` + Updated Signature: + ```rust + let logger_provider: LoggerProvider = LoggerProvider::builder() + .install_batch_exporter( + LogExporter::builder() + .with_tonic() + .with_endpoint("http://localhost:4317") + .build()?, + ).build(); + ``` ## v0.25.0 diff --git a/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs b/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs index c73c1207ba..387cf6e7c9 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::WithExportConfig; +use opentelemetry_otlp::{LogExporter, MetricsExporter, Protocol, SpanExporter}; +use opentelemetry_sdk::{ + logs::LoggerProvider, + metrics::{PeriodicReader, SdkMeterProvider}, + runtime, + trace::{self as sdktrace, Config, 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_config(Config::default().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 = MetricsExporter::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 5c428421b4..8da8dff7f2 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, MetricsExporter, SpanExporter, WithExportConfig}; +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,37 @@ 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("http://localhost: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 = MetricsExporter::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_endpoint("http://localhost:4317") + .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/mod.rs b/opentelemetry-otlp/src/exporter/http/mod.rs index 46fb9bdb12..9efd55c56b 100644 --- a/opentelemetry-otlp/src/exporter/http/mod.rs +++ b/opentelemetry-otlp/src/exporter/http/mod.rs @@ -34,7 +34,6 @@ mod logs; mod trace; /// Configuration of the http transport -#[cfg(any(feature = "http-proto", feature = "http-json"))] #[derive(Debug)] #[cfg_attr( all( @@ -43,7 +42,7 @@ mod trace; ), derive(Default) )] -pub(crate) struct HttpConfig { +pub struct HttpConfig { /// Select the HTTP client client: Option>, @@ -80,19 +79,18 @@ 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::MetricsExporter::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(()) /// # } /// ``` @@ -119,31 +117,6 @@ impl Default for HttpExporterBuilder { } impl HttpExporterBuilder { - /// Specify the OTLP protocol to be used by the exporter - 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, @@ -154,7 +127,7 @@ impl HttpExporterBuilder { let endpoint = resolve_http_endpoint( signal_endpoint_var, signal_endpoint_path, - self.exporter_config.endpoint.as_str(), + self.exporter_config.endpoint.clone(), )?; let timeout = match env::var(signal_timeout_var) @@ -367,7 +340,7 @@ fn build_endpoint_uri(endpoint: &str, path: &str) -> Result { fn resolve_http_endpoint( signal_endpoint_var: &str, signal_endpoint_path: &str, - provided_endpoint: &str, + provided_endpoint: Option, ) -> Result { // per signal env var is not modified if let Some(endpoint) = env::var(signal_endpoint_var) @@ -385,14 +358,14 @@ fn resolve_http_endpoint( return Ok(endpoint); } - if provided_endpoint.is_empty() { - build_endpoint_uri( - OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT, - signal_endpoint_path, - ) - } else { - provided_endpoint.parse().map_err(From::from) - } + provided_endpoint + .map(|e| e.parse().map_err(From::from)) + .unwrap_or_else(|| { + build_endpoint_uri( + OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT, + signal_endpoint_path, + ) + }) } #[allow(clippy::mutable_key_type)] // http headers are not mutated @@ -405,11 +378,64 @@ fn add_header_from_string(input: &str, headers: &mut HashMap &mut HttpConfig; +} + +/// Expose interface for modifying builder config. +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, WithExportConfig, WithHttpConfig, OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, }; @@ -423,7 +449,7 @@ mod tests { let endpoint = resolve_http_endpoint( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", - "http://localhost:4317", + Some("http://localhost:4317".to_string()), ) .unwrap(); assert_eq!(endpoint, "http://example.com/v1/traces"); @@ -439,7 +465,7 @@ mod tests { let endpoint = super::resolve_http_endpoint( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", - "http://localhost:4317", + Some("http://localhost:4317".to_string()), ) .unwrap(); assert_eq!(endpoint, "http://example.com"); @@ -458,7 +484,7 @@ mod tests { let endpoint = super::resolve_http_endpoint( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", - "http://localhost:4317", + Some("http://localhost:4317".to_string()), ) .unwrap(); assert_eq!(endpoint, "http://example.com"); @@ -472,7 +498,7 @@ mod tests { let endpoint = super::resolve_http_endpoint( "NON_EXISTENT_VAR", "/v1/traces", - "http://localhost:4317", + Some("http://localhost:4317".to_string()), ) .unwrap(); assert_eq!(endpoint, "http://localhost:4317/"); @@ -507,7 +533,7 @@ mod tests { let endpoint = super::resolve_http_endpoint( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", - "http://localhost:4317", + Some("http://localhost:4317".to_string()), ) .unwrap(); assert_eq!(endpoint, "http://example.com/v1/traces"); @@ -521,7 +547,7 @@ mod tests { let result = super::resolve_http_endpoint( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", - "-*/*-/*-//-/-/yet-another-invalid-uri", + Some("-*/*-/*-//-/-/yet-another-invalid-uri".to_string()), ); assert!(result.is_err()); // You may also want to assert on the specific error type if applicable @@ -614,16 +640,51 @@ mod tests { } } + #[test] + fn test_http_exporter_builder_with_header() { + use std::collections::HashMap; + // Arrange + let initial_headers = HashMap::from([("k1".to_string(), "v1".to_string())]); + let extra_headers = HashMap::from([("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(initial_headers), + }, + exporter_config: crate::ExportConfig::default(), + }; + + // Act + let builder = builder.with_headers(extra_headers); + + // Assert + 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, "/v1/traces", - exporter.exporter_config.endpoint.as_str(), + exporter.exporter_config.endpoint, ) .unwrap(); @@ -632,14 +693,13 @@ 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( OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, "/v1/traces", - exporter.exporter_config.endpoint.as_str(), + exporter.exporter_config.endpoint, ) .unwrap(); diff --git a/opentelemetry-otlp/src/exporter/mod.rs b/opentelemetry-otlp/src/exporter/mod.rs index d138527d8f..bec1c809bb 100644 --- a/opentelemetry-otlp/src/exporter/mod.rs +++ b/opentelemetry-otlp/src/exporter/mod.rs @@ -69,7 +69,7 @@ pub(crate) mod tonic; pub struct ExportConfig { /// 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, + pub endpoint: Option, /// The protocol to use when communicating with the collector. pub protocol: Protocol, @@ -83,7 +83,7 @@ impl Default for ExportConfig { let protocol = default_protocol(); ExportConfig { - endpoint: "".to_string(), + endpoint: None, // don't use default_endpoint(protocol) here otherwise we // won't know if user provided a value protocol, @@ -144,12 +144,13 @@ fn default_headers() -> std::collections::HashMap { headers } -/// Provide access to the export config field within the exporter builders. +/// Provide access to the [ExportConfig] field within the exporter builders. pub trait HasExportConfig { - /// Return a mutable reference to the export config within the exporter builders. + /// Return a mutable reference to the [ExportConfig] within the exporter builders. fn export_config(&mut self) -> &mut ExportConfig; } +/// Provide [ExportConfig] access to the [TonicExporterBuilder]. #[cfg(feature = "grpc-tonic")] impl HasExportConfig for TonicExporterBuilder { fn export_config(&mut self) -> &mut ExportConfig { @@ -157,6 +158,7 @@ impl HasExportConfig for TonicExporterBuilder { } } +/// Provide [ExportConfig] access to the [HttpExporterBuilder]. #[cfg(any(feature = "http-proto", feature = "http-json"))] impl HasExportConfig for HttpExporterBuilder { fn export_config(&mut self) -> &mut ExportConfig { @@ -164,7 +166,7 @@ impl HasExportConfig for HttpExporterBuilder { } } -/// Expose methods to override export configuration. +/// Expose methods to override [ExportConfig]. /// /// This trait will be implemented for every struct that implemented [`HasExportConfig`] trait. /// @@ -173,8 +175,8 @@ impl HasExportConfig for HttpExporterBuilder { /// # #[cfg(all(feature = "trace", feature = "grpc-tonic"))] /// # { /// use crate::opentelemetry_otlp::WithExportConfig; -/// let exporter_builder = opentelemetry_otlp::new_exporter() -/// .tonic() +/// let exporter_builder = opentelemetry_otlp::SpanExporter::builder() +/// .with_tonic() /// .with_endpoint("http://localhost:7201"); /// # } /// ``` @@ -183,11 +185,11 @@ pub trait WithExportConfig { fn with_endpoint>(self, endpoint: T) -> Self; /// Set the protocol to use when communicating with the collector. /// - /// Note that protocols that are not supported by exporters will be ignore. The exporter + /// Note that protocols that are not supported by exporters will be ignored. The exporter /// will use default protocol in this case. /// /// ## Note - /// All exporters in this crate are only support one protocol thus choosing the protocol is an no-op at the moment + /// All exporters in this crate only support one protocol, thus choosing the protocol is an no-op at the moment. fn with_protocol(self, protocol: Protocol) -> Self; /// Set the timeout to the collector. fn with_timeout(self, timeout: Duration) -> Self; @@ -197,7 +199,7 @@ pub trait WithExportConfig { impl WithExportConfig for B { fn with_endpoint>(mut self, endpoint: T) -> Self { - self.export_config().endpoint = endpoint.into(); + self.export_config().endpoint = Some(endpoint.into()); self } @@ -291,17 +293,17 @@ 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, ""); + assert_eq!(exporter_builder.exporter_config.endpoint, None); } #[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.exporter_config.endpoint, None); } #[test] diff --git a/opentelemetry-otlp/src/exporter/tonic/mod.rs b/opentelemetry-otlp/src/exporter/tonic/mod.rs index 75d6559b02..1c520c80b0 100644 --- a/opentelemetry-otlp/src/exporter/tonic/mod.rs +++ b/opentelemetry-otlp/src/exporter/tonic/mod.rs @@ -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::MetricsExporter::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) exporter_config: ExportConfig, } 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 { + 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(), + }, exporter_config: ExportConfig { 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,30 +182,13 @@ 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; - // resolving endpoint string - // grpc doesn't have a "path" like http(See https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) - // the path of grpc calls are based on the protobuf service definition - // so we won't append one for default grpc endpoints - // If users for some reason want to use a custom path, they can use env var or builder to pass it - let endpoint = match env::var(signal_endpoint_var) - .ok() - .or(env::var(OTEL_EXPORTER_OTLP_ENDPOINT).ok()) - { - Some(val) => val, - None => { - if config.endpoint.is_empty() { - OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT.to_string() - } else { - config.endpoint - } - } - }; + let endpoint = Self::resolve_endpoint(signal_endpoint_var, config.endpoint); let endpoint = Channel::from_shared(endpoint).map_err(crate::Error::from)?; let timeout = match env::var(signal_timeout_var) @@ -291,7 +203,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 +218,41 @@ impl TonicExporterBuilder { Ok((channel, interceptor, compression)) } + fn resolve_endpoint(default_endpoint_var: &str, provided_endpoint: Option) -> String { + // resolving endpoint string + // grpc doesn't have a "path" like http(See https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) + // the path of grpc calls are based on the protobuf service definition + // so we won't append one for default grpc endpoints + // If users for some reason want to use a custom path, they can use env var or builder to pass it + match env::var(default_endpoint_var) + .ok() + .or(env::var(OTEL_EXPORTER_OTLP_ENDPOINT).ok()) + { + Some(val) => val, + None => { + provided_endpoint.unwrap_or(OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT.to_string()) + } + } + } + + 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,7 +271,7 @@ 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 { @@ -348,7 +292,7 @@ impl TonicExporterBuilder { /// 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,12 +340,108 @@ 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::{WithExportConfig, 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 [`ExportConfig::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; + use crate::{TonicExporterBuilder, WithExportConfig, OTEL_EXPORTER_OTLP_TRACES_ENDPOINT}; use crate::{OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TRACES_HEADERS}; use http::{HeaderMap, HeaderName, HeaderValue}; use tonic::metadata::{MetadataMap, MetadataValue}; @@ -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()); @@ -454,7 +496,6 @@ mod tests { } #[test] - #[cfg(feature = "grpc-tonic")] fn test_convert_compression() { #[cfg(feature = "gzip-tonic")] assert!(tonic::codec::CompressionEncoding::try_from(Compression::Gzip).is_ok()); @@ -523,4 +564,31 @@ mod tests { }, ); } + + #[test] + fn test_tonic_exporter_endpoint() { + // default endpoint for grpc should not add signal path. + run_env_test(vec![], || { + let exporter = TonicExporterBuilder::default(); + + let url = TonicExporterBuilder::resolve_endpoint( + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, + exporter.exporter_config.endpoint, + ); + + assert_eq!(url, "http://localhost:4317"); + }); + + // if builder endpoint is set, it should not use default. + run_env_test(vec![], || { + let exporter = TonicExporterBuilder::default().with_endpoint("http://localhost:1234"); + + let url = TonicExporterBuilder::resolve_endpoint( + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, + exporter.exporter_config.endpoint, + ); + + assert_eq!(url, "http://localhost:1234"); + }); + } } diff --git a/opentelemetry-otlp/src/lib.rs b/opentelemetry-otlp/src/lib.rs index 336104672d..b0895600af 100644 --- a/opentelemetry-otlp/src/lib.rs +++ b/opentelemetry-otlp/src/lib.rs @@ -21,12 +21,15 @@ //! $ docker run -p 4317:4317 otel/opentelemetry-collector:latest //! ``` //! -//! Then install a new pipeline with the recommended defaults to start exporting -//! telemetry. You will have to build a OTLP exporter first. +//! Then create a new `Exporter`, and `Provider` with the recommended defaults to start exporting +//! telemetry. //! -//! Exporting pipelines can be started with `new_pipeline().tracing()` and -//! `new_pipeline().metrics()`, and `new_pipeline().logging()` respectively for -//! traces, metrics and logs. +//! You will have to build a OTLP exporter first. Create the correct exporter based on the signal +//! you are looking to export `SpanExporter::builder()`, `MetricsExporter::builder()`, +//! `LogExporter::builder()` respectively for traces, metrics, and logs. +//! +//! Once you have the exporter, you can create your `Provider` by starting with `TracerProvider::builder()`, +//! `SdkMeterProvider::builder()`, and `LoggerProvider::builder()` respectively for traces, metrics, and logs. //! //! ```no_run //! # #[cfg(all(feature = "trace", feature = "grpc-tonic"))] @@ -36,12 +39,11 @@ //! //! fn main() -> Result<(), Box> { //! // First, create a OTLP exporter builder. Configure it as you need. -//! let otlp_exporter = opentelemetry_otlp::new_exporter().tonic(); -//! // Then pass it into pipeline builder -//! let _ = opentelemetry_otlp::new_pipeline() -//! .tracing() -//! .with_exporter(otlp_exporter) -//! .install_simple()?; +//! let otlp_exporter = opentelemetry_otlp::SpanExporter::builder().with_tonic().build()?; +//! // Then pass it into provider builder +//! 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 +72,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::Tokio, +//! ) +//! .build(); +//! //! # Ok(()) //! # } //! # } @@ -115,18 +122,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 [MetricsExporter::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, WithExportConfig, WithTonicConfig}; //! use std::time::Duration; //! # #[cfg(feature = "grpc-tonic")] //! use tonic::metadata::*; @@ -139,26 +146,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 +171,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::MetricsExporter::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,35 +219,44 @@ 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; #[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, + MetricsExporter, 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, @@ -253,6 +266,24 @@ pub use crate::exporter::{ use opentelemetry_sdk::export::ExportError; +/// Type to indicate the builder does not have a client set. +#[derive(Debug, Default, Clone)] +pub struct NoExporterBuilderSet; + +/// Type to hold the [TonicExporterBuilder] and indicate it has been set. +/// +/// Allowing access to [TonicExporterBuilder] specific configuration methods. +#[cfg(feature = "grpc-tonic")] +#[derive(Debug, Default)] +pub struct TonicExporterBuilderSet(TonicExporterBuilder); + +/// Type to hold the [HttpExporterBuilder] and indicate it has been set. +/// +/// Allowing access to [HttpExporterBuilder] specific configuration methods. +#[cfg(any(feature = "http-proto", feature = "http-json"))] +#[derive(Debug, Default)] +pub struct HttpExporterBuilderSet(HttpExporterBuilder); + #[cfg(any(feature = "http-proto", feature = "http-json"))] pub use crate::exporter::http::HttpExporterBuilder; @@ -262,55 +293,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..42727a8d74 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::{HasExportConfig, NoExporterBuilderSet}; + +#[cfg(feature = "grpc-tonic")] +use crate::{HasTonicConfig, TonicExporterBuilder, TonicExporterBuilderSet}; + +#[cfg(any(feature = "http-proto", feature = "http-json"))] +use crate::{HasHttpConfig, HttpExporterBuilder, HttpExporterBuilderSet}; /// Compression algorithm to use, defaults to none. pub const OTEL_EXPORTER_OTLP_LOGS_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION"; @@ -32,53 +32,73 @@ 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, +} + +impl LogExporterBuilder { + pub fn new() -> Self { + LogExporterBuilder::default() + } + + #[cfg(feature = "grpc-tonic")] + pub fn with_tonic(self) -> LogExporterBuilder { + LogExporterBuilder { + client: TonicExporterBuilderSet(TonicExporterBuilder::default()), + endpoint: self.endpoint, + } + } + + #[cfg(any(feature = "http-proto", feature = "http-json"))] + pub fn with_http(self) -> LogExporterBuilder { + LogExporterBuilder { + client: HttpExporterBuilderSet(HttpExporterBuilder::default()), + endpoint: self.endpoint, } } } -/// OTLP log exporter builder -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -#[non_exhaustive] -pub enum LogExporterBuilder { - /// Tonic log exporter builder - #[cfg(feature = "grpc-tonic")] - Tonic(TonicExporterBuilder), - /// Http log exporter builder - #[cfg(feature = "http-proto")] - Http(HttpExporterBuilder), +#[cfg(feature = "grpc-tonic")] +impl LogExporterBuilder { + pub fn build(self) -> Result { + self.client.0.build_log_exporter() + } } -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(), - } +#[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 HasExportConfig for LogExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExportConfig { + &mut self.client.0.exporter_config + } +} + +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasExportConfig for LogExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExportConfig { + &mut self.client.0.exporter_config } } #[cfg(feature = "grpc-tonic")] -impl From for LogExporterBuilder { - fn from(exporter: TonicExporterBuilder) -> Self { - LogExporterBuilder::Tonic(exporter) +impl HasTonicConfig for LogExporterBuilder { + fn tonic_config(&mut self) -> &mut crate::TonicConfig { + &mut self.client.0.tonic_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 HasHttpConfig for LogExporterBuilder { + fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig { + &mut self.client.0.http_config } } @@ -89,6 +109,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 +132,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..70352e502a 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::HasExportConfig; + +#[cfg(any(feature = "http-proto", feature = "http-json"))] +use crate::{exporter::http::HttpExporterBuilder, HasHttpConfig, HttpExporterBuilderSet}; + +#[cfg(feature = "grpc-tonic")] +use crate::{exporter::tonic::TonicExporterBuilder, HasTonicConfig, TonicExporterBuilderSet}; + +use crate::NoExporterBuilderSet; + 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::PushMetricsExporter, }; 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,183 +37,85 @@ 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 MetricsExporterBuilder { + client: C, + temporality: Temporality, } -#[cfg(feature = "http-proto")] -impl From for MetricsExporterBuilder { - fn from(exporter: HttpExporterBuilder) -> Self { - MetricsExporterBuilder::Http(exporter) +impl MetricsExporterBuilder { + pub fn new() -> Self { + MetricsExporterBuilder::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 MetricsExporterBuilder { + #[cfg(feature = "grpc-tonic")] + pub fn with_tonic(self) -> MetricsExporterBuilder { + MetricsExporterBuilder { + client: TonicExporterBuilderSet(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) -> MetricsExporterBuilder { + MetricsExporterBuilder { + client: HttpExporterBuilderSet(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 + pub fn with_temporality(self, temporality: Temporality) -> MetricsExporterBuilder { + MetricsExporterBuilder { + 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 MetricsExporterBuilder { + 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(any(feature = "http-proto", feature = "http-json"))] +impl MetricsExporterBuilder { + pub fn build(self) -> Result { + let exporter = self.client.0.build_metrics_exporter(self.temporality)?; + Ok(exporter) } } -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); +#[cfg(feature = "grpc-tonic")] +impl HasExportConfig for MetricsExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExportConfig { + &mut self.client.0.exporter_config + } +} - if let Some(resource) = self.resource { - provider = provider.with_resource(resource); - } +#[cfg(any(feature = "http-proto", feature = "http-json"))] +impl HasExportConfig for MetricsExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExportConfig { + &mut self.client.0.exporter_config + } +} - let provider = provider.build(); - Ok(provider) +#[cfg(feature = "grpc-tonic")] +impl HasTonicConfig for MetricsExporterBuilder { + fn tonic_config(&mut self) -> &mut crate::TonicConfig { + &mut self.client.0.tonic_config } } -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 HasHttpConfig for MetricsExporterBuilder { + fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig { + &mut self.client.0.http_config } } @@ -258,6 +159,11 @@ impl PushMetricsExporter for MetricsExporter { } impl MetricsExporter { + /// Obtain a builder to configure a [MetricsExporter]. + pub fn builder() -> MetricsExporterBuilder { + MetricsExporterBuilder::default() + } + /// Create a new metrics exporter pub fn new(client: impl MetricsClient, temporality: Temporality) -> MetricsExporter { MetricsExporter { diff --git a/opentelemetry-otlp/src/span.rs b/opentelemetry-otlp/src/span.rs index 6e61cfd1a2..6cf40a3d99 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}, + TonicExporterBuilderSet, +}; #[cfg(any(feature = "http-proto", feature = "http-json"))] -use crate::exporter::http::HttpExporterBuilder; +use crate::{ + exporter::http::{HasHttpConfig, HttpExporterBuilder}, + HttpExporterBuilderSet, +}; -use crate::{NoExporterConfig, OtlpPipeline}; +use crate::{exporter::HasExportConfig, NoExporterBuilderSet}; /// 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: TonicExporterBuilderSet(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: HttpExporterBuilderSet(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 SpanExporterBuilder { + pub fn build(self) -> Result { + let span_exporter = self.client.0.build_span_exporter()?; + Ok(SpanExporter::new(span_exporter)) } } -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(any(feature = "http-proto", feature = "http-json"))] +impl SpanExporterBuilder { + pub fn build(self) -> Result { + let span_exporter = self.client.0.build_span_exporter()?; + Ok(SpanExporter::new(span_exporter)) } - - 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(feature = "grpc-tonic")] +impl HasExportConfig for SpanExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExportConfig { + &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 HasExportConfig for SpanExporterBuilder { + fn export_config(&mut self) -> &mut crate::ExportConfig { + &mut self.client.0.exporter_config } } #[cfg(feature = "grpc-tonic")] -impl From for SpanExporterBuilder { - fn from(exporter: TonicExporterBuilder) -> Self { - SpanExporterBuilder::Tonic(exporter) +impl HasTonicConfig for SpanExporterBuilder { + fn tonic_config(&mut self) -> &mut crate::TonicConfig { + &mut self.client.0.tonic_config } } #[cfg(any(feature = "http-proto", feature = "http-json"))] -impl From for SpanExporterBuilder { - fn from(exporter: HttpExporterBuilder) -> Self { - SpanExporterBuilder::Http(exporter) +impl HasHttpConfig for SpanExporterBuilder { + fn http_client_config(&mut self) -> &mut crate::exporter::http::HttpConfig { + &mut self.client.0.http_config } } @@ -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..4e437bb069 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, WithExportConfig}; +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..f2dcc3bbfd 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::{WithExportConfig, 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/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 14d3b162a5..515cd5633b 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -6,7 +6,7 @@ - Implement `LogRecord::set_trace_context` for `LogRecord`. Respect any trace context set on a `LogRecord` when emitting through a `Logger`. - Improved `LoggerProvider` shutdown handling to prevent redundant shutdown calls when `drop` is invoked. [#2195](https://github.com/open-telemetry/opentelemetry-rust/pull/2195) - **BREAKING**: [#2217](https://github.com/open-telemetry/opentelemetry-rust/pull/2217) - - **Replaced**: Removed `{Delta,Cumulative}TemporalitySelector::new()` in favor of directly using `Temporality` enum to simplify the configuration of MetricExporterBuilder with different temporalities. + - **Replaced**: Removed `{Delta,Cumulative}TemporalitySelector::new()` in favor of directly using `Temporality` enum to simplify the configuration of MetricsExporterBuilder with different temporalities. - When creating new metric instruments, SDK would return a no-op instrument if the validation fails. [#2166](https://github.com/open-telemetry/opentelemetry-rust/pull/2166) - **Breaking change for Metrics users:** The `init` method used to create instruments has been renamed to `build`. diff --git a/opentelemetry-sdk/src/metrics/exporter.rs b/opentelemetry-sdk/src/metrics/exporter.rs index 5fd00d7edd..ff7d9fb593 100644 --- a/opentelemetry-sdk/src/metrics/exporter.rs +++ b/opentelemetry-sdk/src/metrics/exporter.rs @@ -29,6 +29,6 @@ pub trait PushMetricsExporter: Send + Sync + 'static { /// instead will return an error indicating the shutdown state. fn shutdown(&self) -> Result<()>; - /// Access the [Temporality] of the MetricExporter. + /// Access the [Temporality] of the MetricsExporter. fn temporality(&self) -> Temporality; } diff --git a/opentelemetry-sdk/src/metrics/reader.rs b/opentelemetry-sdk/src/metrics/reader.rs index cf85de1474..d3530ec01f 100644 --- a/opentelemetry-sdk/src/metrics/reader.rs +++ b/opentelemetry-sdk/src/metrics/reader.rs @@ -18,7 +18,7 @@ use super::{ /// flow. /// /// Typically, push-based exporters that are periodic will implement -/// `MetricExporter` themselves and construct a `PeriodicReader` to satisfy this +/// `MetricsExporter` themselves and construct a `PeriodicReader` to satisfy this /// interface. /// /// Pull-based exporters will typically implement `MetricReader` themselves, diff --git a/opentelemetry-sdk/src/trace/provider.rs b/opentelemetry-sdk/src/trace/provider.rs index 3cc9441857..31a0ae3129 100644 --- a/opentelemetry-sdk/src/trace/provider.rs +++ b/opentelemetry-sdk/src/trace/provider.rs @@ -515,7 +515,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(