From 3f585e87dc060b07e39a0ec539e0cf0edd8f15b5 Mon Sep 17 00:00:00 2001 From: Mark Ingram Date: Tue, 4 Jun 2024 08:49:58 +0100 Subject: [PATCH] [opentelemetry-otlp] adds an example HTTP exporter backed by a Hyper 0.2 Client Resolves #1659 --- opentelemetry-otlp/CHANGELOG.md | 1 + .../examples/basic-otlp-http-hyper/Cargo.toml | 20 +++ .../examples/basic-otlp-http-hyper/README.md | 116 ++++++++++++++++++ .../basic-otlp-http-hyper/docker-compose.yaml | 15 +++ .../otel-collector-config.yaml | 21 ++++ .../basic-otlp-http-hyper/src/main.rs | 107 ++++++++++++++++ 6 files changed, 280 insertions(+) create mode 100644 opentelemetry-otlp/examples/basic-otlp-http-hyper/Cargo.toml create mode 100644 opentelemetry-otlp/examples/basic-otlp-http-hyper/README.md create mode 100644 opentelemetry-otlp/examples/basic-otlp-http-hyper/docker-compose.yaml create mode 100644 opentelemetry-otlp/examples/basic-otlp-http-hyper/otel-collector-config.yaml create mode 100644 opentelemetry-otlp/examples/basic-otlp-http-hyper/src/main.rs diff --git a/opentelemetry-otlp/CHANGELOG.md b/opentelemetry-otlp/CHANGELOG.md index 5ec7699d2a..0d7976b49c 100644 --- a/opentelemetry-otlp/CHANGELOG.md +++ b/opentelemetry-otlp/CHANGELOG.md @@ -16,6 +16,7 @@ now use `.with_resource(RESOURCE::default())` to configure Resource when using 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. - **Breaking** Correct the misspelling of "webkpi" to "webpki" in features [#1842](https://github.com/open-telemetry/opentelemetry-rust/pull/1842) - Bump MSRV to 1.70 [#1840](https://github.com/open-telemetry/opentelemetry-rust/pull/1840) +- Adds `basic-otlp-http-hyper` example showing how to export with a custom Hyper Client ## v0.16.0 diff --git a/opentelemetry-otlp/examples/basic-otlp-http-hyper/Cargo.toml b/opentelemetry-otlp/examples/basic-otlp-http-hyper/Cargo.toml new file mode 100644 index 0000000000..044e1f3fa4 --- /dev/null +++ b/opentelemetry-otlp/examples/basic-otlp-http-hyper/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "basic-otlp-http-hyper" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +publish = false + +[dependencies] +async-trait = { workspace = true } +bytes = { workspace = true } +once_cell = { workspace = true } +opentelemetry = { path = "../../../opentelemetry" } +opentelemetry_sdk = { path = "../../../opentelemetry-sdk", features = ["rt-tokio"] } +opentelemetry-http = { path = "../../../opentelemetry-http" } +opentelemetry-otlp = { path = "../..", features = ["http-proto"] } +opentelemetry-semantic-conventions = { path = "../../../opentelemetry-semantic-conventions" } + +http = { workspace = true } +hyper = { workspace = true, features = ["client"] } +tokio = { workspace = true, features = ["full"] } diff --git a/opentelemetry-otlp/examples/basic-otlp-http-hyper/README.md b/opentelemetry-otlp/examples/basic-otlp-http-hyper/README.md new file mode 100644 index 0000000000..a23bd92dad --- /dev/null +++ b/opentelemetry-otlp/examples/basic-otlp-http-hyper/README.md @@ -0,0 +1,116 @@ +# Basic OTLP exporter Example - Hyper 0.14 + +This example shows how to setup OpenTelemetry OTLP exporter for traces to exports them to the [OpenTelemetry +Collector](https://github.com/open-telemetry/opentelemetry-collector) via OTLP +over HTTP/protobuf. The Collector then sends the data to the appropriate +backend, in this case, the logging Exporter, which displays data to console. + +This example uses a simple implementation of the `HttpClient` backed by a Hyper Client. + +## Usage + +### `docker-compose` + +By default runs against the `otel/opentelemetry-collector:latest` image, and uses `reqwest-client` +as the http client, using http as the transport. + +```shell +docker-compose up +``` + +In another terminal run the application `cargo run` + +The docker-compose terminal will display traces. + +Press Ctrl+C to stop the collector, and then tear it down: + +```shell +docker-compose down +``` + +### Manual + +If you don't want to use `docker-compose`, you can manually run the `otel/opentelemetry-collector` container +and inspect the logs to see traces being transferred. + +On Unix based systems use: + +```shell +# From the current directory, run `opentelemetry-collector` +docker run --rm -it -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:latest --config=/cfg/otel-collector-config.yaml +``` + +On Windows use: + +```shell +# From the current directory, run `opentelemetry-collector` +docker run --rm -it -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:latest --config=/cfg/otel-collector-config.yaml +``` + +Run the app which exports traces via OTLP to the collector + +```shell +cargo run +``` + +## View results + +You should be able to see something similar below with different time and ID in the same console that docker runs. + +### Span + +```text +... +2024-06-04T07:39:05.722Z info ResourceSpans #0 +Resource SchemaURL: +Resource attributes: + -> service.name: Str(basic-otlp-http-hyper) +ScopeSpans #0 +ScopeSpans SchemaURL: +InstrumentationScope basic +Span #0 + Trace ID : 0e833514074391284d809e71afd34931 + Parent ID : b5ce8f2bdedd0698 + ID : c2555b67b2072134 + Name : Sub operation... + Kind : Internal + Start time : 2024-06-04 07:39:05.698201 +0000 UTC + End time : 2024-06-04 07:39:05.698213 +0000 UTC + Status code : Unset + Status message : +Attributes: + -> another.key: Str(yes) +Events: +SpanEvent #0 + -> Name: Sub span event + -> Timestamp: 2024-06-04 07:39:05.698206 +0000 UTC + -> DroppedAttributesCount: 0 +ResourceSpans #1 +Resource SchemaURL: +Resource attributes: + -> service.name: Str(basic-otlp-http-hyper) +ScopeSpans #0 +ScopeSpans SchemaURL: +InstrumentationScope basic +Span #0 + Trace ID : 0e833514074391284d809e71afd34931 + Parent ID : + ID : b5ce8f2bdedd0698 + Name : Main operation + Kind : Internal + Start time : 2024-06-04 07:39:05.698168 +0000 UTC + End time : 2024-06-04 07:39:05.698223 +0000 UTC + Status code : Unset + Status message : +Attributes: + -> another.key: Str(yes) +Events: +SpanEvent #0 + -> Name: Nice operation! + -> Timestamp: 2024-06-04 07:39:05.698186 +0000 UTC + -> DroppedAttributesCount: 0 + -> Attributes:: + -> bogons: Int(100) + {"kind": "exporter", "data_type": "traces", "name": "logging"} +... +``` diff --git a/opentelemetry-otlp/examples/basic-otlp-http-hyper/docker-compose.yaml b/opentelemetry-otlp/examples/basic-otlp-http-hyper/docker-compose.yaml new file mode 100644 index 0000000000..dc9d1e7a5d --- /dev/null +++ b/opentelemetry-otlp/examples/basic-otlp-http-hyper/docker-compose.yaml @@ -0,0 +1,15 @@ +version: "2" +services: + + # Collector + otel-collector: + image: otel/opentelemetry-collector:latest + command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"] + volumes: + - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml + ports: + - "4318:4318" # OTLP HTTP receiver + + + + diff --git a/opentelemetry-otlp/examples/basic-otlp-http-hyper/otel-collector-config.yaml b/opentelemetry-otlp/examples/basic-otlp-http-hyper/otel-collector-config.yaml new file mode 100644 index 0000000000..dd7d7ad40f --- /dev/null +++ b/opentelemetry-otlp/examples/basic-otlp-http-hyper/otel-collector-config.yaml @@ -0,0 +1,21 @@ +# This is a configuration file for the OpenTelemetry Collector intended to be +# used in conjunction with the opentelemetry-otlp example. +# +# For more information about the OpenTelemetry Collector see: +# https://github.com/open-telemetry/opentelemetry-collector +# +receivers: + otlp: + protocols: + grpc: + http: + +exporters: + logging: + loglevel: debug + +service: + pipelines: + traces: + receivers: [otlp] + exporters: [logging] diff --git a/opentelemetry-otlp/examples/basic-otlp-http-hyper/src/main.rs b/opentelemetry-otlp/examples/basic-otlp-http-hyper/src/main.rs new file mode 100644 index 0000000000..fc64ac97c8 --- /dev/null +++ b/opentelemetry-otlp/examples/basic-otlp-http-hyper/src/main.rs @@ -0,0 +1,107 @@ +use async_trait::async_trait; +use bytes::Bytes; +use http::{Request, Response}; +use hyper::client::{connect::Connect, HttpConnector}; +use hyper::{Body, Client}; +use once_cell::sync::Lazy; +use opentelemetry::{ + global, + trace::{TraceContextExt, TraceError, Tracer, TracerProvider as _}, + Key, KeyValue, +}; +use opentelemetry_http::{HttpClient, HttpError, ResponseExt}; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::trace::{self as sdktrace, Config}; +use opentelemetry_sdk::Resource; + +use std::error::Error; + +static RESOURCE: Lazy = Lazy::new(|| { + Resource::new(vec![KeyValue::new( + opentelemetry_semantic_conventions::resource::SERVICE_NAME, + "basic-otlp-http-hyper", + )]) +}); + +struct HyperClient { + inner: hyper::Client, +} + +impl Default for HyperClient { + fn default() -> Self { + Self { + inner: Client::new(), + } + } +} + +impl std::fmt::Debug for HyperClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("HyperClient") + .field("inner", &self.inner) + .finish() + } +} + +#[async_trait] +impl HttpClient for HyperClient { + async fn send(&self, request: Request>) -> Result, HttpError> { + let request = request.map(Body::from); + + let (parts, body) = self + .inner + .request(request) + .await? + .error_for_status()? + .into_parts(); + let body = hyper::body::to_bytes(body).await?; + + Ok(Response::from_parts(parts, body)) + } +} + +fn init_tracer_provider() -> Result { + opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter( + opentelemetry_otlp::new_exporter() + .http() + .with_http_client(HyperClient::default()) + .with_endpoint("http://localhost:4318/v1/traces"), + ) + .with_trace_config(Config::default().with_resource(RESOURCE.clone())) + .install_batch(opentelemetry_sdk::runtime::Tokio) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let result = init_tracer_provider(); + assert!( + result.is_ok(), + "Init tracer failed with error: {:?}", + result.err() + ); + + let tracer_provider = result.unwrap(); + global::set_tracer_provider(tracer_provider.clone()); + + let tracer = global::tracer_provider().tracer_builder("basic").build(); + + tracer.in_span("Main operation", |cx| { + let span = cx.span(); + span.add_event( + "Nice operation!".to_string(), + vec![Key::new("bogons").i64(100)], + ); + span.set_attribute(KeyValue::new("another.key", "yes")); + + tracer.in_span("Sub operation...", |cx| { + let span = cx.span(); + span.set_attribute(KeyValue::new("another.key", "yes")); + span.add_event("Sub span event", vec![]); + }); + }); + + global::shutdown_tracer_provider(); + Ok(()) +}