forked from open-telemetry/opentelemetry-rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tracing-http example (open-telemetry#1404)
Modified version of the [tracingresponse](https://github.com/open-telemetry/opentelemetry-rust-contrib/tree/main/examples/traceresponse) example (in `contrib` repo) to demonstrate context propagation from client to server. The example - Removes the code to propagate trace-context as part of response headers from server to client, as the W3C specs is still in draft (https://w3c.github.io/trace-context/#trace-context-http-response-headers-format), and also the propagator is part of contrib repo. - Modify the HTTP server and client code to look more complete and also demonstrate the context propagation across async delegates. **_Server_** - Enhance the server's request handling, by adding support for `/echo` and `/health` endpoints. Upon receiving the request, the server now creates a child span, linked to the originating remote span, and the request is forwarded to its respective delegate async task. Furthermore, within each async task, a subsequent child span is spawned, parented by the initial child span. This nested span creation exemplifies the effective propagation of tracing context through the multiple layers of async execution. **_Client_** - The client sends requests for `/echo` and `/health` within the context of the client root span.
- Loading branch information
Showing
6 changed files
with
225 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
[package] | ||
name = "tracing-http-propagator" | ||
version = "0.1.0" | ||
edition = "2021" | ||
license = "Apache-2.0" | ||
publish = false | ||
|
||
[[bin]] # Bin to run the http server | ||
name = "http-server" | ||
path = "src/server.rs" | ||
doc = false | ||
|
||
[[bin]] # Bin to run the client | ||
name = "http-client" | ||
path = "src/client.rs" | ||
doc = false | ||
|
||
[dependencies] | ||
hyper = { version = "0.14", features = ["full"] } | ||
tokio = { version = "1.0", features = ["full"] } | ||
opentelemetry = { path = "../../opentelemetry" } | ||
opentelemetry_sdk = { path = "../../opentelemetry-sdk" } | ||
opentelemetry-http = { path = "../../opentelemetry-http" } | ||
opentelemetry-stdout = { path = "../../opentelemetry-stdout", features = ["trace"] } | ||
opentelemetry-semantic-conventions = { path = "../../opentelemetry-semantic-conventions" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# HTTP Example | ||
|
||
This is a simple example using [hyper] that demonstrates tracing http request | ||
from client to server. The example shows key aspects of tracing | ||
such as: | ||
|
||
- Root Span (on Client) | ||
- Child Span from a Remote Parent (on Server) | ||
- Child Span created on the async function parented by the first level child (on Server) | ||
- SpanContext Propagation (from Client to Server) | ||
- Span Events | ||
- Span Attributes | ||
- Context propagation across async task boundaries. | ||
|
||
[hyper]: https://hyper.rs/ | ||
|
||
## Usage | ||
|
||
```shell | ||
# Run server | ||
$ cargo run --bin http-server | ||
|
||
# In another tab, run client | ||
$ cargo run --bin http-client | ||
|
||
# The spans should be visible in stdout in the order that they were exported. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use hyper::{body::Body, Client}; | ||
use opentelemetry::{ | ||
global, | ||
trace::{SpanKind, TraceContextExt, Tracer}, | ||
Context, KeyValue, | ||
}; | ||
use opentelemetry_http::HeaderInjector; | ||
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::TracerProvider}; | ||
use opentelemetry_stdout::SpanExporter; | ||
|
||
fn init_tracer() { | ||
global::set_text_map_propagator(TraceContextPropagator::new()); | ||
// Install stdout exporter pipeline to be able to retrieve the collected spans. | ||
// For the demonstration, use `Sampler::AlwaysOn` sampler to sample all traces. | ||
let provider = TracerProvider::builder() | ||
.with_simple_exporter(SpanExporter::default()) | ||
.build(); | ||
|
||
global::set_tracer_provider(provider); | ||
} | ||
|
||
async fn send_request( | ||
url: &str, | ||
body_content: &str, | ||
span_name: &str, | ||
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { | ||
let client = Client::new(); | ||
let tracer = global::tracer("example/client"); | ||
let span = tracer | ||
.span_builder(String::from(span_name)) | ||
.with_kind(SpanKind::Client) | ||
.start(&tracer); | ||
let cx = Context::current_with_span(span); | ||
|
||
let mut req = hyper::Request::builder().uri(url); | ||
global::get_text_map_propagator(|propagator| { | ||
propagator.inject_context(&cx, &mut HeaderInjector(req.headers_mut().unwrap())) | ||
}); | ||
let res = client | ||
.request(req.body(Body::from(String::from(body_content)))?) | ||
.await?; | ||
|
||
cx.span().add_event( | ||
"Got response!".to_string(), | ||
vec![KeyValue::new("status", res.status().to_string())], | ||
); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { | ||
init_tracer(); | ||
|
||
send_request( | ||
"http://127.0.0.1:3000/health", | ||
"Health Request!", | ||
"server_health_check", | ||
) | ||
.await?; | ||
send_request( | ||
"http://127.0.0.1:3000/echo", | ||
"Echo Request!", | ||
"server_echo_check", | ||
) | ||
.await?; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use hyper::{ | ||
service::{make_service_fn, service_fn}, | ||
Body, Request, Response, Server, StatusCode, | ||
}; | ||
use opentelemetry::{ | ||
global, | ||
trace::{FutureExt, Span, SpanKind, TraceContextExt, Tracer}, | ||
Context, KeyValue, | ||
}; | ||
use opentelemetry_http::HeaderExtractor; | ||
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::TracerProvider}; | ||
use opentelemetry_semantic_conventions::trace; | ||
use opentelemetry_stdout::SpanExporter; | ||
use std::{convert::Infallible, net::SocketAddr}; | ||
|
||
// Utility function to extract the context from the incoming request headers | ||
fn extract_context_from_request(req: &Request<Body>) -> Context { | ||
global::get_text_map_propagator(|propagator| { | ||
propagator.extract(&HeaderExtractor(req.headers())) | ||
}) | ||
} | ||
|
||
// Separate async function for the handle endpoint | ||
async fn handle_health_check(_req: Request<Body>) -> Result<Response<Body>, Infallible> { | ||
let tracer = global::tracer("example/server"); | ||
let mut span = tracer | ||
.span_builder("health_check") | ||
.with_kind(SpanKind::Internal) | ||
.start(&tracer); | ||
span.add_event("Health check accessed", vec![]); | ||
let res = Response::new(Body::from("Server is up and running!")); | ||
Ok(res) | ||
} | ||
|
||
// Separate async function for the echo endpoint | ||
async fn handle_echo(req: Request<Body>) -> Result<Response<Body>, Infallible> { | ||
let tracer = global::tracer("example/server"); | ||
let mut span = tracer | ||
.span_builder("echo") | ||
.with_kind(SpanKind::Internal) | ||
.start(&tracer); | ||
span.add_event("Echoing back the request", vec![]); | ||
let res = Response::new(req.into_body()); | ||
Ok(res) | ||
} | ||
|
||
async fn router(req: Request<Body>) -> Result<Response<Body>, Infallible> { | ||
// Extract the context from the incoming request headers | ||
let parent_cx = extract_context_from_request(&req); | ||
let response = { | ||
// Create a span parenting the remote client span. | ||
let tracer = global::tracer("example/server"); | ||
let mut span = tracer | ||
.span_builder("router") | ||
.with_kind(SpanKind::Server) | ||
.start_with_context(&tracer, &parent_cx); | ||
|
||
span.add_event("dispatching request", vec![]); | ||
|
||
let cx = Context::default().with_span(span); | ||
match (req.method(), req.uri().path()) { | ||
(&hyper::Method::GET, "/health") => handle_health_check(req).with_context(cx).await, | ||
(&hyper::Method::GET, "/echo") => handle_echo(req).with_context(cx).await, | ||
_ => { | ||
cx.span() | ||
.set_attribute(KeyValue::new(trace::HTTP_RESPONSE_STATUS_CODE, 404)); | ||
let mut not_found = Response::default(); | ||
*not_found.status_mut() = StatusCode::NOT_FOUND; | ||
Ok(not_found) | ||
} | ||
} | ||
}; | ||
response | ||
} | ||
|
||
fn init_tracer() { | ||
global::set_text_map_propagator(TraceContextPropagator::new()); | ||
|
||
// Install stdout exporter pipeline to be able to retrieve the collected spans. | ||
// For the demonstration, use `Sampler::AlwaysOn` sampler to sample all traces. In a production | ||
// application, use `Sampler::ParentBased` or `Sampler::TraceIdRatioBased` with a desired ratio. | ||
let provider = TracerProvider::builder() | ||
.with_simple_exporter(SpanExporter::default()) | ||
.build(); | ||
|
||
global::set_tracer_provider(provider); | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
init_tracer(); | ||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||
|
||
let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(router)) }); | ||
|
||
let server = Server::bind(&addr).serve(make_svc); | ||
|
||
println!("Listening on {addr}"); | ||
if let Err(e) = server.await { | ||
eprintln!("server error: {e}"); | ||
} | ||
} |