Skip to content

Commit 38a596f

Browse files
committed
[opentelemetry-otlp] adds an example HTTP exporter backed by a Hyper 0.2 Client
Resolves open-telemetry#1659
1 parent 406cc31 commit 38a596f

File tree

6 files changed

+284
-0
lines changed

6 files changed

+284
-0
lines changed

opentelemetry-otlp/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ now use `.with_resource(RESOURCE::default())` to configure Resource when using
1616
These methods would also no longer set the global tracer provider. It would now be the responsibility of users to set it by calling `global::set_tracer_provider(tracer_provider.clone());`. Refer to the [basic-otlp](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp/src/main.rs) and [basic-otlp-http](https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-otlp/examples/basic-otlp-http/src/main.rs) examples on how to initialize OTLP Trace Exporter.
1717
- **Breaking** Correct the misspelling of "webkpi" to "webpki" in features [#1842](https://github.com/open-telemetry/opentelemetry-rust/pull/1842)
1818
- Bump MSRV to 1.70 [#1840](https://github.com/open-telemetry/opentelemetry-rust/pull/1840)
19+
- Adds `basic-otlp-http-hyper` example showing how to export with a custom Hyper Client
1920

2021
## v0.16.0
2122

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "basic-otlp-http-hyper"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
publish = false
7+
8+
[dependencies]
9+
async-trait = { workspace = true }
10+
bytes = { workspace = true }
11+
once_cell = { workspace = true }
12+
opentelemetry = { path = "../../../opentelemetry" }
13+
opentelemetry_sdk = { path = "../../../opentelemetry-sdk", features = ["rt-tokio"] }
14+
opentelemetry-http = { path = "../../../opentelemetry-http" }
15+
opentelemetry-otlp = { path = "../..", features = ["http-proto"] }
16+
opentelemetry-semantic-conventions = { path = "../../../opentelemetry-semantic-conventions" }
17+
18+
http = { workspace = true }
19+
hyper = { workspace = true, features = ["client"] }
20+
tokio = { workspace = true, features = ["full"] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Basic OTLP exporter Example - Hyper 0.2
2+
3+
This example shows how to setup OpenTelemetry OTLP exporter for traces to exports them to the [OpenTelemetry
4+
Collector](https://github.com/open-telemetry/opentelemetry-collector) via OTLP
5+
over HTTP/protobuf. The Collector then sends the data to the appropriate
6+
backend, in this case, the logging Exporter, which displays data to console.
7+
8+
This example uses a simple implementation of the `HttpClient` backed by a Hyper Client.
9+
10+
## Usage
11+
12+
### `docker-compose`
13+
14+
By default runs against the `otel/opentelemetry-collector:latest` image, and uses `reqwest-client`
15+
as the http client, using http as the transport.
16+
17+
```shell
18+
docker-compose up
19+
```
20+
21+
In another terminal run the application `cargo run`
22+
23+
The docker-compose terminal will display traces.
24+
25+
Press Ctrl+C to stop the collector, and then tear it down:
26+
27+
```shell
28+
docker-compose down
29+
```
30+
31+
### Manual
32+
33+
If you don't want to use `docker-compose`, you can manually run the `otel/opentelemetry-collector` container
34+
and inspect the logs to see traces being transferred.
35+
36+
On Unix based systems use:
37+
38+
```shell
39+
# From the current directory, run `opentelemetry-collector`
40+
docker run --rm -it -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:latest --config=/cfg/otel-collector-config.yaml
41+
```
42+
43+
On Windows use:
44+
45+
```shell
46+
# From the current directory, run `opentelemetry-collector`
47+
docker run --rm -it -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:latest --config=/cfg/otel-collector-config.yaml
48+
```
49+
50+
Run the app which exports traces via OTLP to the collector
51+
52+
```shell
53+
cargo run
54+
```
55+
56+
## View results
57+
58+
You should be able to see something similar below with different time and ID in the same console that docker runs.
59+
60+
### Span
61+
62+
```text
63+
...
64+
2024-05-14T02:15:56.827Z info ResourceSpans #0
65+
Resource SchemaURL:
66+
Resource attributes:
67+
-> service.name: Str(basic-otlp-example)
68+
ScopeSpans #0
69+
ScopeSpans SchemaURL:
70+
InstrumentationScope basic
71+
InstrumentationScope attributes:
72+
-> scope-key: Str(scope-value)
73+
Span #0
74+
Trace ID : 4467894e2d8d0c4165df1218160bc260
75+
Parent ID : 589ea953b6ec03a9
76+
ID : b2aa3c3a9c21e0d0
77+
Name : Sub operation...
78+
Kind : Internal
79+
Start time : 2024-05-14 02:15:56.824239163 +0000 UTC
80+
End time : 2024-05-14 02:15:56.824244315 +0000 UTC
81+
Status code : Unset
82+
Status message :
83+
Attributes:
84+
-> another.key: Str(yes)
85+
Events:
86+
SpanEvent #0
87+
-> Name: Sub span event
88+
-> Timestamp: 2024-05-14 02:15:56.82424188 +0000 UTC
89+
-> DroppedAttributesCount: 0
90+
ResourceSpans #1
91+
Resource SchemaURL:
92+
Resource attributes:
93+
-> service.name: Str(basic-otlp-example)
94+
ScopeSpans #0
95+
ScopeSpans SchemaURL:
96+
InstrumentationScope basic
97+
InstrumentationScope attributes:
98+
-> scope-key: Str(scope-value)
99+
Span #0
100+
Trace ID : 4467894e2d8d0c4165df1218160bc260
101+
Parent ID :
102+
ID : 589ea953b6ec03a9
103+
Name : Main operation
104+
Kind : Internal
105+
Start time : 2024-05-14 02:15:56.824194899 +0000 UTC
106+
End time : 2024-05-14 02:15:56.824251136 +0000 UTC
107+
Status code : Unset
108+
Status message :
109+
Attributes:
110+
-> another.key: Str(yes)
111+
Events:
112+
SpanEvent #0
113+
-> Name: Nice operation!
114+
-> Timestamp: 2024-05-14 02:15:56.824201397 +0000 UTC
115+
-> DroppedAttributesCount: 0
116+
-> Attributes::
117+
-> bogons: Int(100)
118+
{"kind": "exporter", "data_type": "traces", "name": "logging"}
119+
...
120+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: "2"
2+
services:
3+
4+
# Collector
5+
otel-collector:
6+
image: otel/opentelemetry-collector:latest
7+
command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"]
8+
volumes:
9+
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
10+
ports:
11+
- "4318:4318" # OTLP HTTP receiver
12+
13+
14+
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# This is a configuration file for the OpenTelemetry Collector intended to be
2+
# used in conjunction with the opentelemetry-otlp example.
3+
#
4+
# For more information about the OpenTelemetry Collector see:
5+
# https://github.com/open-telemetry/opentelemetry-collector
6+
#
7+
receivers:
8+
otlp:
9+
protocols:
10+
grpc:
11+
http:
12+
13+
exporters:
14+
logging:
15+
loglevel: debug
16+
17+
service:
18+
pipelines:
19+
traces:
20+
receivers: [otlp]
21+
exporters: [logging]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use async_trait::async_trait;
2+
use bytes::Bytes;
3+
use http::{Request, Response};
4+
use hyper::client::{connect::Connect, HttpConnector};
5+
use hyper::{Body, Client};
6+
use once_cell::sync::Lazy;
7+
use opentelemetry::{
8+
global,
9+
trace::{TraceContextExt, TraceError, Tracer, TracerProvider as _},
10+
Key, KeyValue,
11+
};
12+
use opentelemetry_http::{HttpClient, HttpError, ResponseExt};
13+
use opentelemetry_otlp::WithExportConfig;
14+
use opentelemetry_sdk::trace::{self as sdktrace, Config};
15+
use opentelemetry_sdk::Resource;
16+
17+
use std::error::Error;
18+
19+
static RESOURCE: Lazy<Resource> = Lazy::new(|| {
20+
Resource::new(vec![KeyValue::new(
21+
opentelemetry_semantic_conventions::resource::SERVICE_NAME,
22+
"basic-otlp-http-hyper",
23+
)])
24+
});
25+
26+
struct HyperClient<C> {
27+
inner: hyper::Client<C>,
28+
}
29+
30+
impl Default for HyperClient<HttpConnector> {
31+
fn default() -> Self {
32+
Self {
33+
inner: Client::new(),
34+
}
35+
}
36+
}
37+
38+
impl<C> std::fmt::Debug for HyperClient<C> {
39+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40+
f.debug_struct("HyperClient")
41+
.field("inner", &self.inner)
42+
.finish()
43+
}
44+
}
45+
46+
#[async_trait]
47+
impl<C: Connect + Clone + Send + Sync + 'static> HttpClient for HyperClient<C> {
48+
async fn send(&self, request: Request<Vec<u8>>) -> Result<Response<Bytes>, HttpError> {
49+
let request = request.map(Body::from);
50+
51+
let (parts, body) = self
52+
.inner
53+
.request(request)
54+
.await?
55+
.error_for_status()?
56+
.into_parts();
57+
let body = hyper::body::to_bytes(body).await?;
58+
59+
Ok(Response::from_parts(parts, body))
60+
}
61+
}
62+
63+
fn init_tracer_provider() -> Result<sdktrace::TracerProvider, TraceError> {
64+
opentelemetry_otlp::new_pipeline()
65+
.tracing()
66+
.with_exporter(
67+
opentelemetry_otlp::new_exporter()
68+
.http()
69+
.with_http_client(HyperClient::default())
70+
.with_endpoint("http://localhost:4318/v1/traces"),
71+
)
72+
.with_trace_config(Config::default().with_resource(RESOURCE.clone()))
73+
.install_batch(opentelemetry_sdk::runtime::Tokio)
74+
}
75+
76+
#[tokio::main]
77+
async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
78+
let result = init_tracer_provider();
79+
assert!(
80+
result.is_ok(),
81+
"Init tracer failed with error: {:?}",
82+
result.err()
83+
);
84+
85+
let tracer_provider = result.unwrap();
86+
global::set_tracer_provider(tracer_provider.clone());
87+
88+
let tracer = global::tracer_provider().tracer_builder("basic").build();
89+
90+
tracer.in_span("Main operation", |cx| {
91+
let span = cx.span();
92+
span.add_event(
93+
"Nice operation!".to_string(),
94+
vec![Key::new("bogons").i64(100)],
95+
);
96+
span.set_attribute(KeyValue::new("another.key", "yes"));
97+
98+
tracer.in_span("Sub operation...", |cx| {
99+
let span = cx.span();
100+
span.set_attribute(KeyValue::new("another.key", "yes"));
101+
span.add_event("Sub span event", vec![]);
102+
});
103+
});
104+
105+
global::shutdown_tracer_provider();
106+
Ok(())
107+
}

0 commit comments

Comments
 (0)