Skip to content

Commit 2269d1f

Browse files
authored
Configure tls and add example for grpcio (#450)
1 parent 8e0857d commit 2269d1f

File tree

7 files changed

+179
-3
lines changed

7 files changed

+179
-3
lines changed

.github/workflows/ci.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ jobs:
4040
args: --all -- --check
4141
- name: Lint
4242
run: ./scripts/lint.sh
43+
non-default-examples:
44+
strategy:
45+
matrix:
46+
example: [examples/external-otlp-grpcio-async-std]
47+
runs-on: ubuntu-latest
48+
steps:
49+
- uses: actions/checkout@v1
50+
with:
51+
submodules: true
52+
- uses: actions-rs/toolchain@v1
53+
with:
54+
toolchain: stable
55+
components: rustfmt
56+
profile: minimal
57+
- uses: arduino/setup-protoc@v1
58+
- name: Build
59+
run: |
60+
cd ${{ matrix.example }}
61+
cargo build --verbose
4362
msrv:
4463
runs-on: ubuntu-latest
4564
steps:

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ members = [
2424
"examples/tracing-grpc",
2525
"examples/zipkin",
2626
]
27+
exclude = ["examples/external-otlp-grpcio-async-std"]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "external-otlp-grpcio-async-std"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
[dependencies]
7+
async-std = { version = "1.9.0", features = ["attributes"] }
8+
env_logger = "0.8.2"
9+
futures = "0.3"
10+
opentelemetry = { path = "../../opentelemetry", features = [
11+
"async-std",
12+
"serialize"
13+
] }
14+
opentelemetry-otlp = { path = "../../opentelemetry-otlp", features = [
15+
"grpc-sys",
16+
"openssl-vendored"
17+
], default-features = false }
18+
serde_json = "1.0"
19+
url = "2.2.0"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# External OTLP collector with grpcio and async-std with TLS
2+
3+
This example shows basic span, and exports to OTLP enabled collectors, like honeycomb, lightstep and other services.
4+
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
5+
makes you use it as a runtime.
6+
As these services all reside outside your own infrastructure, they require TLS for encryption to ensure your data safety.
7+
With this example, you can export to any service that supports OTLP by using environment variables.
8+
The following example exports data to Honeycomb:
9+
10+
```shell
11+
cd examples/external-otlp-grpcio-async-std/
12+
OTLP_GRPCIO_ENDPOINT=https://api.honeycomb.io:443 \
13+
OTLP_GRPCIO_X_HONEYCOMB_TEAM=token \
14+
OTLP_GRPCIO_X_HONEYCOMB_DATASET=dataset \
15+
cargo run
16+
```
17+
18+
The only required variable is `OTLP_GRPCIO_ENDPOINT` and any other variable that beggins with the prefix `OTLP_GRPCIO_` will be sent as headers
19+
e.g.: `OTLP_GRPCIO_X_HONEYCOMB_TEAM` becomes `x-honeycomb-team` and `OTLP_GRPCIO_X_HONEYCOMB_DATASET` becomes `x-honeycomb-dataset`.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//! This should show how to connect to a third party collector like
2+
//! honeycomb or lightstep using tonic with tls and using tokio as reactor.
3+
//! To run this you have to specify a few environment variables like in the example:
4+
//! ```shell
5+
//! OTLP_GRPCIO_ENDPOINT=https://api.honeycomb.io:443 \
6+
//! OTLP_GRPCIO_X_HONEYCOMB_TEAM=token \
7+
//! OTLP_GRPCIO_X_HONEYCOMB_DATASET=dataset \
8+
//! cargo run --bin external-otlp-tonic-tokio
9+
//! ```
10+
use async_std::task::sleep;
11+
use opentelemetry::trace::TraceError;
12+
use opentelemetry::{global, sdk::trace as sdktrace};
13+
use opentelemetry::{
14+
trace::{TraceContextExt, Tracer},
15+
Key,
16+
};
17+
use url::Url;
18+
19+
use std::{
20+
collections::HashMap,
21+
env::{set_var, vars},
22+
time::Duration,
23+
};
24+
use std::{
25+
env::{remove_var, var},
26+
error::Error,
27+
};
28+
29+
// Use the variables to try and export the example to any external collector that accepts otlp
30+
// like: oltp itself, honeycomb or lightstep
31+
const ENDPOINT: &str = "OTLP_GRPCIO_ENDPOINT";
32+
const HEADER_PREFIX: &str = "OTLP_GRPCIO_";
33+
34+
fn init_tracer() -> Result<(sdktrace::Tracer, opentelemetry_otlp::Uninstall), TraceError> {
35+
let endpoint = var(ENDPOINT).unwrap_or_else(|_| {
36+
panic!(
37+
"You must specify and endpoint to connect to with the variable {:?}.",
38+
ENDPOINT
39+
)
40+
});
41+
let endpoint = Url::parse(&endpoint).expect("endpoint is not a valid url");
42+
43+
remove_var(ENDPOINT);
44+
let headers: HashMap<_, _> = vars()
45+
.filter(|(name, _)| name.starts_with(HEADER_PREFIX))
46+
.map(|(name, value)| {
47+
let header_name = name
48+
.strip_prefix(HEADER_PREFIX)
49+
.unwrap()
50+
.replace("_", "-")
51+
.to_ascii_lowercase();
52+
(header_name, value)
53+
})
54+
.collect();
55+
56+
let grpcio_endpoint = format!(
57+
"{}:{}",
58+
endpoint.host_str().unwrap(),
59+
endpoint.port_or_known_default().unwrap()
60+
);
61+
62+
opentelemetry_otlp::new_pipeline()
63+
.with_endpoint(grpcio_endpoint)
64+
.with_headers(headers)
65+
.with_tls(true)
66+
.install()
67+
}
68+
const LEMONS_KEY: Key = Key::from_static_str("ex.com/lemons");
69+
const ANOTHER_KEY: Key = Key::from_static_str("ex.com/another");
70+
71+
#[async_std::main]
72+
async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
73+
match var("RUST_LOG") {
74+
Err(std::env::VarError::NotPresent) => set_var("RUST_LOG", "trace"),
75+
_ => {}
76+
};
77+
env_logger::init();
78+
let _guard = init_tracer()?;
79+
80+
let tracer = global::tracer("ex.com/basic");
81+
82+
tracer.in_span("operation", |cx| {
83+
let span = cx.span();
84+
span.add_event(
85+
"Nice operation!".to_string(),
86+
vec![Key::new("bogons").i64(100)],
87+
);
88+
span.set_attribute(ANOTHER_KEY.string("yes"));
89+
90+
tracer.in_span("Sub operation...", |cx| {
91+
let span = cx.span();
92+
span.set_attribute(LEMONS_KEY.string("five"));
93+
94+
span.add_event("Sub span event".to_string(), vec![]);
95+
});
96+
});
97+
98+
// wait for 1 minutes so that we could see metrics being pushed via OTLP every 10 seconds.
99+
sleep(Duration::from_secs(60)).await;
100+
101+
Ok(())
102+
}

opentelemetry-otlp/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,13 @@ impl OtlpPipelineBuilder {
245245
self
246246
}
247247

248+
/// Enable TLS without any certificate pinning.
249+
#[cfg(all(feature = "grpc-sys", not(feature = "tonic")))]
250+
pub fn with_tls(mut self, use_tls: bool) -> Self {
251+
self.exporter_config.use_tls = Some(use_tls);
252+
self
253+
}
254+
248255
/// Set the timeout to the collector.
249256
pub fn with_timeout(mut self, timeout: Duration) -> Self {
250257
self.exporter_config.timeout = timeout;

opentelemetry-otlp/src/span.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ pub struct ExporterConfig {
9595
#[cfg(all(feature = "grpc-sys", not(feature = "tonic")))]
9696
pub compression: Option<Compression>,
9797

98+
/// Use TLS without any specific certificate pinning.
99+
#[cfg(all(feature = "grpc-sys", not(feature = "tonic")))]
100+
pub use_tls: Option<bool>,
101+
98102
/// The timeout to the collector.
99103
pub timeout: Duration,
100104

@@ -159,6 +163,7 @@ impl Default for ExporterConfig {
159163
credentials: None,
160164
headers: None,
161165
compression: None,
166+
use_tls: None,
162167
timeout: Duration::from_secs(60),
163168
completion_queue_count: 2,
164169
}
@@ -287,9 +292,13 @@ impl TraceExporter {
287292
builder = builder.default_compression_algorithm(compression.into());
288293
}
289294

290-
let channel: Channel = match config.credentials {
291-
None => builder.connect(config.endpoint.as_str()),
292-
Some(credentials) => builder.secure_connect(
295+
let channel: Channel = match (config.credentials, config.use_tls) {
296+
(None, Some(true)) => builder.secure_connect(
297+
config.endpoint.as_str(),
298+
ChannelCredentialsBuilder::new().build(),
299+
),
300+
(None, _) => builder.connect(config.endpoint.as_str()),
301+
(Some(credentials), _) => builder.secure_connect(
293302
config.endpoint.as_str(),
294303
ChannelCredentialsBuilder::new()
295304
.cert(credentials.cert.into(), credentials.key.into())

0 commit comments

Comments
 (0)