diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35052a650f..8117b9b0fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,25 @@ jobs: args: --all -- --check - name: Lint run: ./scripts/lint.sh + non-default-examples: + strategy: + matrix: + example: [examples/external-otlp-grpcio-async-std] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: rustfmt + profile: minimal + - uses: arduino/setup-protoc@v1 + - name: Build + run: | + cd ${{ matrix.example }} + cargo build --verbose msrv: runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index 668764226a..432c045ba3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,3 +24,4 @@ members = [ "examples/tracing-grpc", "examples/zipkin", ] +exclude = ["examples/external-otlp-grpcio-async-std"] diff --git a/examples/external-otlp-grpcio-async-std/Cargo.toml b/examples/external-otlp-grpcio-async-std/Cargo.toml new file mode 100644 index 0000000000..86ac897751 --- /dev/null +++ b/examples/external-otlp-grpcio-async-std/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "external-otlp-grpcio-async-std" +version = "0.1.0" +edition = "2018" + +[dependencies] +async-std = { version = "1.9.0", features = ["attributes"] } +env_logger = "0.8.2" +futures = "0.3" +opentelemetry = { path = "../../opentelemetry", features = [ + "async-std", + "serialize" +] } +opentelemetry-otlp = { path = "../../opentelemetry-otlp", features = [ + "grpc-sys", + "openssl-vendored" +], default-features = false } +serde_json = "1.0" +url = "2.2.0" diff --git a/examples/external-otlp-grpcio-async-std/README.md b/examples/external-otlp-grpcio-async-std/README.md new file mode 100644 index 0000000000..74c7852f21 --- /dev/null +++ b/examples/external-otlp-grpcio-async-std/README.md @@ -0,0 +1,19 @@ +# External OTLP collector with grpcio and async-std with TLS + +This example shows basic span, and exports to OTLP enabled collectors, like honeycomb, lightstep and other services. +Use this service in case you don't use `tokio`s runtime, for example with web frameworks like `tide` or any `async-std` library that +makes you use it as a runtime. +As these services all reside outside your own infrastructure, they require TLS for encryption to ensure your data safety. +With this example, you can export to any service that supports OTLP by using environment variables. +The following example exports data to Honeycomb: + +```shell +cd examples/external-otlp-grpcio-async-std/ +OTLP_GRPCIO_ENDPOINT=https://api.honeycomb.io:443 \ +OTLP_GRPCIO_X_HONEYCOMB_TEAM=token \ +OTLP_GRPCIO_X_HONEYCOMB_DATASET=dataset \ +cargo run +``` + +The only required variable is `OTLP_GRPCIO_ENDPOINT` and any other variable that beggins with the prefix `OTLP_GRPCIO_` will be sent as headers +e.g.: `OTLP_GRPCIO_X_HONEYCOMB_TEAM` becomes `x-honeycomb-team` and `OTLP_GRPCIO_X_HONEYCOMB_DATASET` becomes `x-honeycomb-dataset`. diff --git a/examples/external-otlp-grpcio-async-std/src/main.rs b/examples/external-otlp-grpcio-async-std/src/main.rs new file mode 100644 index 0000000000..69232d7c7f --- /dev/null +++ b/examples/external-otlp-grpcio-async-std/src/main.rs @@ -0,0 +1,102 @@ +//! This should show how to connect to a third party collector like +//! honeycomb or lightstep using tonic with tls and using tokio as reactor. +//! To run this you have to specify a few environment variables like in the example: +//! ```shell +//! OTLP_GRPCIO_ENDPOINT=https://api.honeycomb.io:443 \ +//! OTLP_GRPCIO_X_HONEYCOMB_TEAM=token \ +//! OTLP_GRPCIO_X_HONEYCOMB_DATASET=dataset \ +//! cargo run --bin external-otlp-tonic-tokio +//! ``` +use async_std::task::sleep; +use opentelemetry::trace::TraceError; +use opentelemetry::{global, sdk::trace as sdktrace}; +use opentelemetry::{ + trace::{TraceContextExt, Tracer}, + Key, +}; +use url::Url; + +use std::{ + collections::HashMap, + env::{set_var, vars}, + time::Duration, +}; +use std::{ + env::{remove_var, var}, + error::Error, +}; + +// Use the variables to try and export the example to any external collector that accepts otlp +// like: oltp itself, honeycomb or lightstep +const ENDPOINT: &str = "OTLP_GRPCIO_ENDPOINT"; +const HEADER_PREFIX: &str = "OTLP_GRPCIO_"; + +fn init_tracer() -> Result<(sdktrace::Tracer, opentelemetry_otlp::Uninstall), TraceError> { + let endpoint = var(ENDPOINT).unwrap_or_else(|_| { + panic!( + "You must specify and endpoint to connect to with the variable {:?}.", + ENDPOINT + ) + }); + let endpoint = Url::parse(&endpoint).expect("endpoint is not a valid url"); + + remove_var(ENDPOINT); + let headers: HashMap<_, _> = vars() + .filter(|(name, _)| name.starts_with(HEADER_PREFIX)) + .map(|(name, value)| { + let header_name = name + .strip_prefix(HEADER_PREFIX) + .unwrap() + .replace("_", "-") + .to_ascii_lowercase(); + (header_name, value) + }) + .collect(); + + let grpcio_endpoint = format!( + "{}:{}", + endpoint.host_str().unwrap(), + endpoint.port_or_known_default().unwrap() + ); + + opentelemetry_otlp::new_pipeline() + .with_endpoint(grpcio_endpoint) + .with_headers(headers) + .with_tls(true) + .install() +} +const LEMONS_KEY: Key = Key::from_static_str("ex.com/lemons"); +const ANOTHER_KEY: Key = Key::from_static_str("ex.com/another"); + +#[async_std::main] +async fn main() -> Result<(), Box> { + match var("RUST_LOG") { + Err(std::env::VarError::NotPresent) => set_var("RUST_LOG", "trace"), + _ => {} + }; + env_logger::init(); + let _guard = init_tracer()?; + + let tracer = global::tracer("ex.com/basic"); + + tracer.in_span("operation", |cx| { + let span = cx.span(); + span.add_event( + "Nice operation!".to_string(), + vec![Key::new("bogons").i64(100)], + ); + span.set_attribute(ANOTHER_KEY.string("yes")); + + tracer.in_span("Sub operation...", |cx| { + let span = cx.span(); + span.set_attribute(LEMONS_KEY.string("five")); + + span.add_event("Sub span event".to_string(), vec![]); + }); + }); + + // wait for 1 minutes so that we could see metrics being pushed via OTLP every 10 seconds. + sleep(Duration::from_secs(60)).await; + + Ok(()) +} diff --git a/opentelemetry-otlp/src/lib.rs b/opentelemetry-otlp/src/lib.rs index 1baafeb485..62ef9b7662 100644 --- a/opentelemetry-otlp/src/lib.rs +++ b/opentelemetry-otlp/src/lib.rs @@ -245,6 +245,13 @@ impl OtlpPipelineBuilder { self } + /// Enable TLS without any certificate pinning. + #[cfg(all(feature = "grpc-sys", not(feature = "tonic")))] + pub fn with_tls(mut self, use_tls: bool) -> Self { + self.exporter_config.use_tls = Some(use_tls); + self + } + /// Set the timeout to the collector. pub fn with_timeout(mut self, timeout: Duration) -> Self { self.exporter_config.timeout = timeout; diff --git a/opentelemetry-otlp/src/span.rs b/opentelemetry-otlp/src/span.rs index f9acdd38f1..6262bc8ddd 100644 --- a/opentelemetry-otlp/src/span.rs +++ b/opentelemetry-otlp/src/span.rs @@ -95,6 +95,10 @@ pub struct ExporterConfig { #[cfg(all(feature = "grpc-sys", not(feature = "tonic")))] pub compression: Option, + /// Use TLS without any specific certificate pinning. + #[cfg(all(feature = "grpc-sys", not(feature = "tonic")))] + pub use_tls: Option, + /// The timeout to the collector. pub timeout: Duration, @@ -159,6 +163,7 @@ impl Default for ExporterConfig { credentials: None, headers: None, compression: None, + use_tls: None, timeout: Duration::from_secs(60), completion_queue_count: 2, } @@ -287,9 +292,13 @@ impl TraceExporter { builder = builder.default_compression_algorithm(compression.into()); } - let channel: Channel = match config.credentials { - None => builder.connect(config.endpoint.as_str()), - Some(credentials) => builder.secure_connect( + let channel: Channel = match (config.credentials, config.use_tls) { + (None, Some(true)) => builder.secure_connect( + config.endpoint.as_str(), + ChannelCredentialsBuilder::new().build(), + ), + (None, _) => builder.connect(config.endpoint.as_str()), + (Some(credentials), _) => builder.secure_connect( config.endpoint.as_str(), ChannelCredentialsBuilder::new() .cert(credentials.cert.into(), credentials.key.into())