Skip to content

Commit 897e70a

Browse files
authored
Add tracing-http example (#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.
1 parent fcd12eb commit 897e70a

File tree

6 files changed

+225
-2
lines changed

6 files changed

+225
-2
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ members = [
2121
"examples/metrics-advanced",
2222
"examples/logs-basic",
2323
"examples/tracing-grpc",
24+
"examples/tracing-http-propagator",
2425
"examples/tracing-jaeger",
2526
"stress",
2627
]

examples/README.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,12 @@ This example uses following crates from this repo:
2020

2121
Check this example if you want to understand *how to instrument metrics using opentelemetry*.
2222

23-
## traceresponse
23+
## tracing-http-propagator
2424
**Tracing**
2525

2626
This example uses following crates from this repo:
2727
- opentelemetry(tracing)
2828
- opentelemetry-http
29-
- opentelemetry-contrib(TraceContextResponsePropagator)
3029
- opentelemetry-stdout
3130

3231
## tracing-grpc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "tracing-http-propagator"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
publish = false
7+
8+
[[bin]] # Bin to run the http server
9+
name = "http-server"
10+
path = "src/server.rs"
11+
doc = false
12+
13+
[[bin]] # Bin to run the client
14+
name = "http-client"
15+
path = "src/client.rs"
16+
doc = false
17+
18+
[dependencies]
19+
hyper = { version = "0.14", features = ["full"] }
20+
tokio = { version = "1.0", features = ["full"] }
21+
opentelemetry = { path = "../../opentelemetry" }
22+
opentelemetry_sdk = { path = "../../opentelemetry-sdk" }
23+
opentelemetry-http = { path = "../../opentelemetry-http" }
24+
opentelemetry-stdout = { path = "../../opentelemetry-stdout", features = ["trace"] }
25+
opentelemetry-semantic-conventions = { path = "../../opentelemetry-semantic-conventions" }
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# HTTP Example
2+
3+
This is a simple example using [hyper] that demonstrates tracing http request
4+
from client to server. The example shows key aspects of tracing
5+
such as:
6+
7+
- Root Span (on Client)
8+
- Child Span from a Remote Parent (on Server)
9+
- Child Span created on the async function parented by the first level child (on Server)
10+
- SpanContext Propagation (from Client to Server)
11+
- Span Events
12+
- Span Attributes
13+
- Context propagation across async task boundaries.
14+
15+
[hyper]: https://hyper.rs/
16+
17+
## Usage
18+
19+
```shell
20+
# Run server
21+
$ cargo run --bin http-server
22+
23+
# In another tab, run client
24+
$ cargo run --bin http-client
25+
26+
# The spans should be visible in stdout in the order that they were exported.
27+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use hyper::{body::Body, Client};
2+
use opentelemetry::{
3+
global,
4+
trace::{SpanKind, TraceContextExt, Tracer},
5+
Context, KeyValue,
6+
};
7+
use opentelemetry_http::HeaderInjector;
8+
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::TracerProvider};
9+
use opentelemetry_stdout::SpanExporter;
10+
11+
fn init_tracer() {
12+
global::set_text_map_propagator(TraceContextPropagator::new());
13+
// Install stdout exporter pipeline to be able to retrieve the collected spans.
14+
// For the demonstration, use `Sampler::AlwaysOn` sampler to sample all traces.
15+
let provider = TracerProvider::builder()
16+
.with_simple_exporter(SpanExporter::default())
17+
.build();
18+
19+
global::set_tracer_provider(provider);
20+
}
21+
22+
async fn send_request(
23+
url: &str,
24+
body_content: &str,
25+
span_name: &str,
26+
) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
27+
let client = Client::new();
28+
let tracer = global::tracer("example/client");
29+
let span = tracer
30+
.span_builder(String::from(span_name))
31+
.with_kind(SpanKind::Client)
32+
.start(&tracer);
33+
let cx = Context::current_with_span(span);
34+
35+
let mut req = hyper::Request::builder().uri(url);
36+
global::get_text_map_propagator(|propagator| {
37+
propagator.inject_context(&cx, &mut HeaderInjector(req.headers_mut().unwrap()))
38+
});
39+
let res = client
40+
.request(req.body(Body::from(String::from(body_content)))?)
41+
.await?;
42+
43+
cx.span().add_event(
44+
"Got response!".to_string(),
45+
vec![KeyValue::new("status", res.status().to_string())],
46+
);
47+
48+
Ok(())
49+
}
50+
51+
#[tokio::main]
52+
async fn main() -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
53+
init_tracer();
54+
55+
send_request(
56+
"http://127.0.0.1:3000/health",
57+
"Health Request!",
58+
"server_health_check",
59+
)
60+
.await?;
61+
send_request(
62+
"http://127.0.0.1:3000/echo",
63+
"Echo Request!",
64+
"server_echo_check",
65+
)
66+
.await?;
67+
68+
Ok(())
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use hyper::{
2+
service::{make_service_fn, service_fn},
3+
Body, Request, Response, Server, StatusCode,
4+
};
5+
use opentelemetry::{
6+
global,
7+
trace::{FutureExt, Span, SpanKind, TraceContextExt, Tracer},
8+
Context, KeyValue,
9+
};
10+
use opentelemetry_http::HeaderExtractor;
11+
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::TracerProvider};
12+
use opentelemetry_semantic_conventions::trace;
13+
use opentelemetry_stdout::SpanExporter;
14+
use std::{convert::Infallible, net::SocketAddr};
15+
16+
// Utility function to extract the context from the incoming request headers
17+
fn extract_context_from_request(req: &Request<Body>) -> Context {
18+
global::get_text_map_propagator(|propagator| {
19+
propagator.extract(&HeaderExtractor(req.headers()))
20+
})
21+
}
22+
23+
// Separate async function for the handle endpoint
24+
async fn handle_health_check(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
25+
let tracer = global::tracer("example/server");
26+
let mut span = tracer
27+
.span_builder("health_check")
28+
.with_kind(SpanKind::Internal)
29+
.start(&tracer);
30+
span.add_event("Health check accessed", vec![]);
31+
let res = Response::new(Body::from("Server is up and running!"));
32+
Ok(res)
33+
}
34+
35+
// Separate async function for the echo endpoint
36+
async fn handle_echo(req: Request<Body>) -> Result<Response<Body>, Infallible> {
37+
let tracer = global::tracer("example/server");
38+
let mut span = tracer
39+
.span_builder("echo")
40+
.with_kind(SpanKind::Internal)
41+
.start(&tracer);
42+
span.add_event("Echoing back the request", vec![]);
43+
let res = Response::new(req.into_body());
44+
Ok(res)
45+
}
46+
47+
async fn router(req: Request<Body>) -> Result<Response<Body>, Infallible> {
48+
// Extract the context from the incoming request headers
49+
let parent_cx = extract_context_from_request(&req);
50+
let response = {
51+
// Create a span parenting the remote client span.
52+
let tracer = global::tracer("example/server");
53+
let mut span = tracer
54+
.span_builder("router")
55+
.with_kind(SpanKind::Server)
56+
.start_with_context(&tracer, &parent_cx);
57+
58+
span.add_event("dispatching request", vec![]);
59+
60+
let cx = Context::default().with_span(span);
61+
match (req.method(), req.uri().path()) {
62+
(&hyper::Method::GET, "/health") => handle_health_check(req).with_context(cx).await,
63+
(&hyper::Method::GET, "/echo") => handle_echo(req).with_context(cx).await,
64+
_ => {
65+
cx.span()
66+
.set_attribute(KeyValue::new(trace::HTTP_RESPONSE_STATUS_CODE, 404));
67+
let mut not_found = Response::default();
68+
*not_found.status_mut() = StatusCode::NOT_FOUND;
69+
Ok(not_found)
70+
}
71+
}
72+
};
73+
response
74+
}
75+
76+
fn init_tracer() {
77+
global::set_text_map_propagator(TraceContextPropagator::new());
78+
79+
// Install stdout exporter pipeline to be able to retrieve the collected spans.
80+
// For the demonstration, use `Sampler::AlwaysOn` sampler to sample all traces. In a production
81+
// application, use `Sampler::ParentBased` or `Sampler::TraceIdRatioBased` with a desired ratio.
82+
let provider = TracerProvider::builder()
83+
.with_simple_exporter(SpanExporter::default())
84+
.build();
85+
86+
global::set_tracer_provider(provider);
87+
}
88+
89+
#[tokio::main]
90+
async fn main() {
91+
init_tracer();
92+
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
93+
94+
let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(router)) });
95+
96+
let server = Server::bind(&addr).serve(make_svc);
97+
98+
println!("Listening on {addr}");
99+
if let Err(e) = server.await {
100+
eprintln!("server error: {e}");
101+
}
102+
}

0 commit comments

Comments
 (0)