diff --git a/opentelemetry-otlp/Cargo.toml b/opentelemetry-otlp/Cargo.toml index 7224392543..70668cf2cd 100644 --- a/opentelemetry-otlp/Cargo.toml +++ b/opentelemetry-otlp/Cargo.toml @@ -42,6 +42,7 @@ reqwest = { workspace = true, optional = true } http = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } thiserror = { workspace = true } +serde_json = { workspace = true, optional = true } [dev-dependencies] tokio-stream = { workspace = true, features = ["net"] } @@ -58,7 +59,7 @@ metrics = ["opentelemetry/metrics", "opentelemetry_sdk/metrics", "opentelemetry- logs = ["opentelemetry/logs", "opentelemetry_sdk/logs", "opentelemetry-proto/logs"] # add ons -serialize = ["serde"] +serialize = ["serde", "serde_json"] default = ["grpc-tonic", "trace"] @@ -73,6 +74,8 @@ http-proto = ["prost", "opentelemetry-http", "opentelemetry-proto/gen-tonic-mess reqwest-blocking-client = ["reqwest/blocking", "opentelemetry-http/reqwest"] reqwest-client = ["reqwest", "opentelemetry-http/reqwest"] reqwest-rustls = ["reqwest", "reqwest/rustls-tls-native-roots"] +# http json +http-json = ["serde_json", "prost", "opentelemetry-http", "opentelemetry-proto/gen-tonic-messages", "http", "trace", "metrics"] # test integration-testing = ["tonic", "prost", "tokio/full", "trace"] diff --git a/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml b/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml index 496dfc70b1..4530074a79 100644 --- a/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml +++ b/opentelemetry-otlp/examples/basic-otlp-http/Cargo.toml @@ -9,7 +9,7 @@ publish = false once_cell = { workspace = true } opentelemetry = { path = "../../../opentelemetry" } opentelemetry_sdk = { path = "../../../opentelemetry-sdk", features = ["rt-tokio", "metrics", "logs"] } -opentelemetry-otlp = { path = "../..", features = ["http-proto", "reqwest-client", "logs"] } +opentelemetry-otlp = { path = "../..", features = ["http-proto", "http-json", "reqwest-client", "logs"] } opentelemetry-appender-tracing = { path = "../../../opentelemetry-appender-tracing", default-features = false} opentelemetry-semantic-conventions = { path = "../../../opentelemetry-semantic-conventions" } diff --git a/opentelemetry-otlp/src/exporter/http/mod.rs b/opentelemetry-otlp/src/exporter/http/mod.rs index 6cd90203a7..ef64662038 100644 --- a/opentelemetry-otlp/src/exporter/http/mod.rs +++ b/opentelemetry-otlp/src/exporter/http/mod.rs @@ -22,7 +22,7 @@ mod logs; mod trace; /// Configuration of the http transport -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] #[derive(Debug)] #[cfg_attr( all( diff --git a/opentelemetry-otlp/src/exporter/http/trace.rs b/opentelemetry-otlp/src/exporter/http/trace.rs index e824978b57..e9a8606c88 100644 --- a/opentelemetry-otlp/src/exporter/http/trace.rs +++ b/opentelemetry-otlp/src/exporter/http/trace.rs @@ -68,7 +68,7 @@ impl SpanExporter for OtlpHttpClient { } } -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] fn build_body(spans: Vec) -> TraceResult<(Vec, &'static str)> { use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest; use prost::Message; @@ -79,12 +79,12 @@ fn build_body(spans: Vec) -> TraceResult<(Vec, &'static str)> { let mut buf = vec![]; req.encode(&mut buf).map_err(crate::Error::from)?; - Ok((buf, "application/x-protobuf")) + Ok((buf, "application/x-protobuf")) } -#[cfg(not(feature = "http-proto"))] +#[cfg(not(any(feature = "http-proto", feature = "http-json")))] fn build_body(spans: Vec) -> TraceResult<(Vec, &'static str)> { Err(TraceError::Other( - "No http protocol configured. Enable one via `http-proto`".into(), + "No http protocol configured. Enable one via `http-proto` or `http-json`".into(), )) } diff --git a/opentelemetry-otlp/src/exporter/mod.rs b/opentelemetry-otlp/src/exporter/mod.rs index 668474d798..6403222446 100644 --- a/opentelemetry-otlp/src/exporter/mod.rs +++ b/opentelemetry-otlp/src/exporter/mod.rs @@ -2,7 +2,7 @@ //! //! OTLP supports sending data via different protocols and formats. -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] use crate::exporter::http::HttpExporterBuilder; #[cfg(feature = "grpc-tonic")] use crate::exporter::tonic::TonicExporterBuilder; @@ -31,15 +31,19 @@ pub const OTEL_EXPORTER_OTLP_COMPRESSION: &str = "OTEL_EXPORTER_OTLP_COMPRESSION #[cfg(feature = "http-proto")] /// Default protocol, using http-proto. pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF; -#[cfg(all(feature = "grpc-tonic", not(feature = "http-proto")))] -/// Default protocol, using grpc as http-proto feature is not enabled. +#[cfg(all(feature = "http-json", feature = "trace", not(any(feature = "http-proto", feature = "grpc-tonic"))))] +/// Default protocol, using http-json. +pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON; +#[cfg(all(feature = "grpc-tonic", not(any(feature = "http-proto", feature = "http-json"))))] +/// Default protocol, using grpc as http-proto or http-json feature is not enabled. pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = OTEL_EXPORTER_OTLP_PROTOCOL_GRPC; -#[cfg(not(any(any(feature = "grpc-tonic", feature = "http-proto"))))] +#[cfg(not(any(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))))] /// Default protocol if no features are enabled. pub const OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT: &str = ""; const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF: &str = "http/protobuf"; const OTEL_EXPORTER_OTLP_PROTOCOL_GRPC: &str = "grpc"; +const OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON: &str = "http/json"; /// Max waiting time for the backend to process each signal batch, defaults to 10 seconds. pub const OTEL_EXPORTER_OTLP_TIMEOUT: &str = "OTEL_EXPORTER_OTLP_TIMEOUT"; @@ -50,7 +54,7 @@ pub const OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT: u64 = 10; const OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT: &str = "http://localhost:4317"; const OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT: &str = "http://localhost:4318"; -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] pub(crate) mod http; #[cfg(feature = "grpc-tonic")] pub(crate) mod tonic; @@ -112,6 +116,7 @@ fn default_protocol() -> Protocol { match OTEL_EXPORTER_OTLP_PROTOCOL_DEFAULT { OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_PROTOBUF => Protocol::HttpBinary, OTEL_EXPORTER_OTLP_PROTOCOL_GRPC => Protocol::Grpc, + OTEL_EXPORTER_OTLP_PROTOCOL_HTTP_JSON => Protocol::HttpJson, _ => Protocol::HttpBinary, } } @@ -121,11 +126,12 @@ fn default_endpoint(protocol: Protocol) -> String { match protocol { Protocol::Grpc => OTEL_EXPORTER_OTLP_GRPC_ENDPOINT_DEFAULT.to_string(), Protocol::HttpBinary => OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT.to_string(), + Protocol::HttpJson => OTEL_EXPORTER_OTLP_HTTP_ENDPOINT_DEFAULT.to_string(), } } /// default user-agent headers -#[cfg(any(feature = "grpc-tonic", feature = "http-proto"))] +#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))] fn default_headers() -> std::collections::HashMap { let mut headers = std::collections::HashMap::new(); headers.insert( @@ -148,7 +154,7 @@ impl HasExportConfig for TonicExporterBuilder { } } -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] impl HasExportConfig for HttpExporterBuilder { fn export_config(&mut self) -> &mut ExportConfig { &mut self.exporter_config @@ -210,7 +216,7 @@ impl WithExportConfig for B { } } -#[cfg(any(feature = "grpc-tonic", feature = "http-proto"))] +#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))] fn parse_header_string(value: &str) -> impl Iterator { value .split_terminator(',') @@ -218,7 +224,7 @@ fn parse_header_string(value: &str) -> impl Iterator { .filter_map(parse_header_key_value_string) } -#[cfg(any(feature = "grpc-tonic", feature = "http-proto"))] +#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))] fn parse_header_key_value_string(key_value_string: &str) -> Option<(&str, &str)> { key_value_string .split_once('=') @@ -227,7 +233,7 @@ fn parse_header_key_value_string(key_value_string: &str) -> Option<(&str, &str)> } #[cfg(test)] -#[cfg(any(feature = "grpc-tonic", feature = "http-proto"))] +#[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))] mod tests { pub(crate) fn run_env_test(env_vars: T, f: F) @@ -245,7 +251,7 @@ mod tests { ) } - #[cfg(feature = "http-proto")] + #[cfg(any(feature = "http-proto", feature = "http-json"))] #[test] fn test_default_http_endpoint() { let exporter_builder = crate::new_exporter().http(); diff --git a/opentelemetry-otlp/src/lib.rs b/opentelemetry-otlp/src/lib.rs index 0b96ae039d..b04cc30a49 100644 --- a/opentelemetry-otlp/src/lib.rs +++ b/opentelemetry-otlp/src/lib.rs @@ -104,6 +104,7 @@ //! * `reqwest-blocking-client`: Use reqwest blocking http client. //! * `reqwest-client`: Use reqwest http client. //! * `reqwest-rustls`: Use reqwest with TLS. +//! * `http-json`: Use http as transport layer, JSON as body format. //! //! # Kitchen Sink Full Configuration //! @@ -247,7 +248,7 @@ pub use crate::exporter::{ use opentelemetry_sdk::export::ExportError; -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] pub use crate::exporter::http::HttpExporterBuilder; #[cfg(feature = "grpc-tonic")] @@ -278,7 +279,7 @@ impl OtlpExporterPipeline { /// and build the exporter. /// /// This exporter can be used in both `tracing` and `metrics` pipeline. - #[cfg(feature = "http-proto")] + #[cfg(any(feature = "http-proto", feature = "http-json"))] pub fn http(self) -> HttpExporterBuilder { HttpExporterBuilder::default() } @@ -314,7 +315,7 @@ pub enum Error { Transport(#[from] tonic::transport::Error), /// Wrap the [`tonic::codegen::http::uri::InvalidUri`] error - #[cfg(any(feature = "grpc-tonic", feature = "http-proto"))] + #[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))] #[error("invalid URI {0}")] InvalidUri(#[from] http::uri::InvalidUri), @@ -329,32 +330,32 @@ pub enum Error { }, /// Http requests failed because no http client is provided. - #[cfg(feature = "http-proto")] + #[cfg(any(feature = "http-proto", feature = "http-json"))] #[error( "no http client, you must select one from features or provide your own implementation" )] NoHttpClient, /// Http requests failed. - #[cfg(feature = "http-proto")] + #[cfg(any(feature = "http-proto", feature = "http-json"))] #[error("http request failed with {0}")] RequestFailed(#[from] opentelemetry_http::HttpError), /// The provided value is invalid in HTTP headers. - #[cfg(any(feature = "grpc-tonic", feature = "http-proto"))] + #[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))] #[error("http header value error {0}")] InvalidHeaderValue(#[from] http::header::InvalidHeaderValue), /// The provided name is invalid in HTTP headers. - #[cfg(any(feature = "grpc-tonic", feature = "http-proto"))] + #[cfg(any(feature = "grpc-tonic", feature = "http-proto", feature = "http-json"))] #[error("http header name error {0}")] InvalidHeaderName(#[from] http::header::InvalidHeaderName), /// Prost encode failed - #[cfg(feature = "http-proto")] + #[cfg(any(feature = "http-proto", all(feature = "http-json", not(feature = "trace"))))] #[error("prost encoding error {0}")] EncodeError(#[from] prost::EncodeError), - + /// The lock in exporters has been poisoned. #[cfg(feature = "metrics")] #[error("the lock of the {0} has been poisoned")] @@ -393,10 +394,10 @@ impl ExportError for Error { pub enum Protocol { /// GRPC protocol Grpc, - // TODO add support for other protocols - // HttpJson, /// HTTP protocol with binary protobuf HttpBinary, + /// HTTP protocol with JSON payload + HttpJson, } #[derive(Debug, Default)] diff --git a/opentelemetry-otlp/src/span.rs b/opentelemetry-otlp/src/span.rs index 76383f2f04..3b0a543e31 100644 --- a/opentelemetry-otlp/src/span.rs +++ b/opentelemetry-otlp/src/span.rs @@ -19,7 +19,7 @@ use sdk::runtime::RuntimeChannel; #[cfg(feature = "grpc-tonic")] use crate::exporter::tonic::TonicExporterBuilder; -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] use crate::exporter::http::HttpExporterBuilder; use crate::{NoExporterConfig, OtlpPipeline}; @@ -187,7 +187,7 @@ pub enum SpanExporterBuilder { #[cfg(feature = "grpc-tonic")] Tonic(TonicExporterBuilder), /// Http span exporter builder - #[cfg(feature = "http-proto")] + #[cfg(any(feature = "http-proto", feature = "http-json"))] Http(HttpExporterBuilder), } @@ -197,7 +197,7 @@ impl SpanExporterBuilder { match self { #[cfg(feature = "grpc-tonic")] SpanExporterBuilder::Tonic(builder) => builder.build_span_exporter(), - #[cfg(feature = "http-proto")] + #[cfg(any(feature = "http-proto", feature = "http-json"))] SpanExporterBuilder::Http(builder) => builder.build_span_exporter(), } } @@ -210,7 +210,7 @@ impl From for SpanExporterBuilder { } } -#[cfg(feature = "http-proto")] +#[cfg(any(feature = "http-proto", feature = "http-json"))] impl From for SpanExporterBuilder { fn from(exporter: HttpExporterBuilder) -> Self { SpanExporterBuilder::Http(exporter)