Skip to content

Commit c823385

Browse files
authored
Add example with tracing_opentelemetry and tonic (#122)
1 parent 5211835 commit c823385

File tree

8 files changed

+264
-0
lines changed

8 files changed

+264
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ members = [
5050
"examples/grpc",
5151
"examples/http",
5252
"examples/zipkin",
53+
"examples/tracing-grpc"
5354
]
5455

5556
[[bench]]

examples/tracing-grpc/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "tracing-grpc"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
[[bin]] # Bin to run the gRPC server
7+
name = "grpc-server"
8+
path = "src/server.rs"
9+
10+
[[bin]] # Bin to run the gRPC client
11+
name = "grpc-client"
12+
path = "src/client.rs"
13+
14+
[dependencies]
15+
http = "0.2"
16+
tonic = "0.2"
17+
prost = "0.6"
18+
tokio = { version = "0.2", features = ["full"] }
19+
opentelemetry = "0.5"
20+
opentelemetry-jaeger ="0.4"
21+
tracing = "0.1.14"
22+
tracing-subscriber = "0.2.5"
23+
tracing-opentelemetry = "0.4.0"
24+
tracing-futures = "0.2.4"
25+
26+
[build-dependencies]
27+
tonic-build = "0.2"

examples/tracing-grpc/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# GRPC example
2+
3+
Example showing [Tonic] client and server interaction with OpenTelemetry context propagation. [tracing_opentelemetry](https://docs.rs/tracing-opentelemetry/0.4.0/tracing_opentelemetry/) is used to hook into the [tracing](https://github.com/tokio-rs/tracing) ecosystem, which enables drop-in replacements for [log](https://github.com/rust-lang/log) macros and an `#[instrument]` macro that will automatically add spans to your functions.
4+
5+
[Tonic]: https://github.com/hyperium/tonic
6+
7+
Examples
8+
--------
9+
10+
```shell
11+
# Run jaeger in background
12+
$ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest
13+
14+
# Run the server
15+
$ cargo run --bin grpc-server
16+
17+
# Now run the client to make a request to the server
18+
$ cargo run --bin grpc-client
19+
20+
# View spans (see the image below)
21+
$ firefox http://localhost:16686/
22+
```
23+
24+
![Jaeger UI](trace.png)

examples/tracing-grpc/build.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn main() -> Result<(), Box<dyn std::error::Error>> {
2+
tonic_build::compile_protos("proto/helloworld.proto")?;
3+
Ok(())
4+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
syntax = "proto3";
2+
package helloworld;
3+
4+
service Greeter {
5+
// Our SayHello rpc accepts HelloRequests and returns HelloReplies
6+
rpc SayHello (HelloRequest) returns (HelloReply);
7+
}
8+
9+
message HelloRequest {
10+
// Request message contains the name to be greeted
11+
string name = 1;
12+
}
13+
14+
message HelloReply {
15+
// Reply contains the greeting message
16+
string message = 1;
17+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use hello_world::greeter_client::GreeterClient;
2+
use hello_world::HelloRequest;
3+
use opentelemetry::api::{HttpTextFormat, KeyValue, Provider, TraceContextPropagator};
4+
use opentelemetry::sdk::Sampler;
5+
use opentelemetry::{api, sdk};
6+
use tracing::*;
7+
use tracing_futures::Instrument;
8+
use tracing_opentelemetry::OpenTelemetrySpanExt;
9+
use tracing_subscriber::prelude::*;
10+
11+
pub mod hello_world {
12+
tonic::include_proto!("helloworld");
13+
}
14+
15+
fn tracing_init() -> Result<(), Box<dyn std::error::Error>> {
16+
let builder = opentelemetry_jaeger::Exporter::builder()
17+
.with_agent_endpoint("127.0.0.1:6831".parse().unwrap());
18+
19+
let exporter = builder
20+
.with_process(opentelemetry_jaeger::Process {
21+
service_name: "grpc-client".to_string(),
22+
tags: vec![KeyValue::new("version", "0.1.0")],
23+
})
24+
.init()?;
25+
26+
// For the demonstration, use `Sampler::Always` sampler to sample all traces. In a production
27+
// application, use `Sampler::Parent` or `Sampler::Probability` with a desired probability.
28+
let provider = sdk::Provider::builder()
29+
.with_simple_exporter(exporter)
30+
.with_config(sdk::Config {
31+
default_sampler: Box::new(Sampler::Always),
32+
..Default::default()
33+
})
34+
.build();
35+
let tracer = provider.get_tracer("grpc-client");
36+
37+
let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);
38+
tracing_subscriber::registry()
39+
.with(opentelemetry)
40+
.try_init()?;
41+
42+
Ok(())
43+
}
44+
45+
struct TonicMetadataMapCarrier<'a>(&'a mut tonic::metadata::MetadataMap);
46+
impl<'a> api::Carrier for TonicMetadataMapCarrier<'a> {
47+
fn get(&self, key: &'static str) -> Option<&str> {
48+
self.0.get(key).and_then(|metadata| metadata.to_str().ok())
49+
}
50+
51+
fn set(&mut self, key: &'static str, value: String) {
52+
if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.to_lowercase().as_bytes()) {
53+
self.0.insert(
54+
key,
55+
tonic::metadata::MetadataValue::from_str(&value).unwrap(),
56+
);
57+
}
58+
}
59+
}
60+
61+
#[instrument]
62+
async fn greet() -> Result<(), Box<dyn std::error::Error>> {
63+
let mut client = GreeterClient::connect("http://[::1]:50051")
64+
.instrument(info_span!("client connect"))
65+
.await?;
66+
let propagator = TraceContextPropagator::new();
67+
let cx = tracing::Span::current().context();
68+
69+
let mut request = tonic::Request::new(HelloRequest {
70+
name: "Tonic".into(),
71+
});
72+
propagator.inject_context(&cx, &mut TonicMetadataMapCarrier(request.metadata_mut()));
73+
74+
let response = client
75+
.say_hello(request)
76+
.instrument(info_span!("say_hello"))
77+
.await?;
78+
info!("Response received: {:?}", response);
79+
Ok(())
80+
}
81+
82+
#[tokio::main]
83+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
84+
tracing_init()?;
85+
greet().await?;
86+
87+
Ok(())
88+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use hello_world::greeter_server::{Greeter, GreeterServer};
2+
use hello_world::{HelloReply, HelloRequest};
3+
use opentelemetry::api::{self, HttpTextFormat, KeyValue, Provider};
4+
use opentelemetry::sdk::{self, Sampler};
5+
use tonic::{transport::Server, Request, Response, Status};
6+
use tracing::*;
7+
use tracing_opentelemetry::OpenTelemetrySpanExt;
8+
use tracing_subscriber::prelude::*;
9+
10+
pub mod hello_world {
11+
tonic::include_proto!("helloworld"); // The string specified here must match the proto package name
12+
}
13+
14+
#[instrument]
15+
fn expensive_fn(to_print: String) {
16+
for _ in 0..5 {
17+
std::thread::sleep(std::time::Duration::from_secs(1));
18+
info!("{}", to_print);
19+
}
20+
}
21+
22+
#[derive(Debug, Default)]
23+
pub struct MyGreeter {}
24+
25+
#[tonic::async_trait]
26+
impl Greeter for MyGreeter {
27+
#[instrument]
28+
async fn say_hello(
29+
&self,
30+
request: Request<HelloRequest>, // Accept request of type HelloRequest
31+
) -> Result<Response<HelloReply>, Status> {
32+
let propagator = api::TraceContextPropagator::new();
33+
let parent_cx = propagator.extract(&HttpHeaderMapCarrier(request.metadata()));
34+
let span = tracing::Span::current();
35+
span.set_parent(&parent_cx);
36+
let name = request.into_inner().name;
37+
expensive_fn(format!("Got name: {:?}", name));
38+
39+
// Return an instance of type HelloReply
40+
let reply = hello_world::HelloReply {
41+
message: format!("Hello {}!", name), // We must use .into_inner() as the fields of gRPC requests and responses are private
42+
};
43+
44+
Ok(Response::new(reply)) // Send back our formatted greeting
45+
}
46+
}
47+
48+
fn tracing_init() -> Result<(), Box<dyn std::error::Error>> {
49+
let builder = opentelemetry_jaeger::Exporter::builder()
50+
.with_agent_endpoint("127.0.0.1:6831".parse().unwrap());
51+
52+
let exporter = builder
53+
.with_process(opentelemetry_jaeger::Process {
54+
service_name: "grpc-server".to_string(),
55+
tags: vec![KeyValue::new("version", "0.1.0")],
56+
})
57+
.init()?;
58+
59+
// For the demonstration, use `Sampler::Always` sampler to sample all traces. In a production
60+
// application, use `Sampler::Parent` or `Sampler::Probability` with a desired probability.
61+
let provider = sdk::Provider::builder()
62+
.with_simple_exporter(exporter)
63+
.with_config(sdk::Config {
64+
default_sampler: Box::new(Sampler::Always),
65+
..Default::default()
66+
})
67+
.build();
68+
let tracer = provider.get_tracer("grpc-server");
69+
70+
let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);
71+
tracing_subscriber::registry()
72+
.with(opentelemetry)
73+
.try_init()?;
74+
75+
Ok(())
76+
}
77+
78+
struct HttpHeaderMapCarrier<'a>(&'a tonic::metadata::MetadataMap);
79+
impl<'a> api::Carrier for HttpHeaderMapCarrier<'a> {
80+
fn get(&self, key: &'static str) -> Option<&str> {
81+
self.0
82+
.get(key.to_lowercase().as_str())
83+
.and_then(|value| value.to_str().ok())
84+
}
85+
86+
fn set(&mut self, _key: &'static str, _value: String) {
87+
unimplemented!()
88+
}
89+
}
90+
91+
#[tokio::main]
92+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
93+
tracing_init()?;
94+
let addr = "[::1]:50051".parse()?;
95+
let greeter = MyGreeter::default();
96+
97+
Server::builder()
98+
.add_service(GreeterServer::new(greeter))
99+
.serve(addr)
100+
.await?;
101+
102+
Ok(())
103+
}

examples/tracing-grpc/trace.png

68.9 KB
Loading

0 commit comments

Comments
 (0)