Skip to content

Commit

Permalink
Add tracing-http example (open-telemetry#1404)
Browse files Browse the repository at this point in the history
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
lalitb authored Nov 28, 2023
1 parent fcd12eb commit 897e70a
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [
"examples/metrics-advanced",
"examples/logs-basic",
"examples/tracing-grpc",
"examples/tracing-http-propagator",
"examples/tracing-jaeger",
"stress",
]
Expand Down
3 changes: 1 addition & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@ This example uses following crates from this repo:

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

## traceresponse
## tracing-http-propagator
**Tracing**

This example uses following crates from this repo:
- opentelemetry(tracing)
- opentelemetry-http
- opentelemetry-contrib(TraceContextResponsePropagator)
- opentelemetry-stdout

## tracing-grpc
Expand Down
25 changes: 25 additions & 0 deletions examples/tracing-http-propagator/Cargo.toml
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" }
27 changes: 27 additions & 0 deletions examples/tracing-http-propagator/README.md
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.
```
69 changes: 69 additions & 0 deletions examples/tracing-http-propagator/src/client.rs
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(())
}
102 changes: 102 additions & 0 deletions examples/tracing-http-propagator/src/server.rs
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}");
}
}

0 comments on commit 897e70a

Please sign in to comment.