Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ members = [
"examples/grpc",
"examples/http",
"examples/zipkin",
"examples/tracing-grpc"
]

[[bench]]
Expand Down
27 changes: 27 additions & 0 deletions examples/tracing-grpc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "tracing-grpc"
version = "0.1.0"
edition = "2018"

[[bin]] # Bin to run the gRPC server
name = "grpc-server"
path = "src/server.rs"

[[bin]] # Bin to run the gRPC client
name = "grpc-client"
path = "src/client.rs"

[dependencies]
http = "0.2"
tonic = "0.2"
prost = "0.6"
tokio = { version = "0.2", features = ["full"] }
opentelemetry = "0.5"
opentelemetry-jaeger ="0.4"
tracing = "0.1.14"
tracing-subscriber = "0.2.5"
tracing-opentelemetry = "0.4.0"
tracing-futures = "0.2.4"

[build-dependencies]
tonic-build = "0.2"
24 changes: 24 additions & 0 deletions examples/tracing-grpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# GRPC example

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.

[Tonic]: https://github.com/hyperium/tonic

Examples
--------

```shell
# Run jaeger in background
$ docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in-one:latest

# Run the server
$ cargo run --bin grpc-server

# Now run the client to make a request to the server
$ cargo run --bin grpc-client

# View spans (see the image below)
$ firefox http://localhost:16686/
```

![Jaeger UI](trace.png)
4 changes: 4 additions & 0 deletions examples/tracing-grpc/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/helloworld.proto")?;
Ok(())
}
17 changes: 17 additions & 0 deletions examples/tracing-grpc/proto/helloworld.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
syntax = "proto3";
package helloworld;

service Greeter {
// Our SayHello rpc accepts HelloRequests and returns HelloReplies
rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
// Request message contains the name to be greeted
string name = 1;
}

message HelloReply {
// Reply contains the greeting message
string message = 1;
}
88 changes: 88 additions & 0 deletions examples/tracing-grpc/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;
use opentelemetry::api::{HttpTextFormat, KeyValue, Provider, TraceContextPropagator};
use opentelemetry::sdk::Sampler;
use opentelemetry::{api, sdk};
use tracing::*;
use tracing_futures::Instrument;
use tracing_opentelemetry::OpenTelemetrySpanExt;
use tracing_subscriber::prelude::*;

pub mod hello_world {
tonic::include_proto!("helloworld");
}

fn tracing_init() -> Result<(), Box<dyn std::error::Error>> {
let builder = opentelemetry_jaeger::Exporter::builder()
.with_agent_endpoint("127.0.0.1:6831".parse().unwrap());

let exporter = builder
.with_process(opentelemetry_jaeger::Process {
service_name: "grpc-client".to_string(),
tags: vec![KeyValue::new("version", "0.1.0")],
})
.init()?;

// For the demonstration, use `Sampler::Always` sampler to sample all traces. In a production
// application, use `Sampler::Parent` or `Sampler::Probability` with a desired probability.
let provider = sdk::Provider::builder()
.with_simple_exporter(exporter)
.with_config(sdk::Config {
default_sampler: Box::new(Sampler::Always),
..Default::default()
})
.build();
let tracer = provider.get_tracer("grpc-client");

let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);
tracing_subscriber::registry()
.with(opentelemetry)
.try_init()?;

Ok(())
}

struct TonicMetadataMapCarrier<'a>(&'a mut tonic::metadata::MetadataMap);
impl<'a> api::Carrier for TonicMetadataMapCarrier<'a> {
fn get(&self, key: &'static str) -> Option<&str> {
self.0.get(key).and_then(|metadata| metadata.to_str().ok())
}

fn set(&mut self, key: &'static str, value: String) {
if let Ok(key) = tonic::metadata::MetadataKey::from_bytes(key.to_lowercase().as_bytes()) {
self.0.insert(
key,
tonic::metadata::MetadataValue::from_str(&value).unwrap(),
);
}
}
}

#[instrument]
async fn greet() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://[::1]:50051")
.instrument(info_span!("client connect"))
.await?;
let propagator = TraceContextPropagator::new();
let cx = tracing::Span::current().context();

let mut request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});
propagator.inject_context(&cx, &mut TonicMetadataMapCarrier(request.metadata_mut()));

let response = client
.say_hello(request)
.instrument(info_span!("say_hello"))
.await?;
info!("Response received: {:?}", response);
Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_init()?;
greet().await?;

Ok(())
}
103 changes: 103 additions & 0 deletions examples/tracing-grpc/src/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
use opentelemetry::api::{self, HttpTextFormat, KeyValue, Provider};
use opentelemetry::sdk::{self, Sampler};
use tonic::{transport::Server, Request, Response, Status};
use tracing::*;
use tracing_opentelemetry::OpenTelemetrySpanExt;
use tracing_subscriber::prelude::*;

pub mod hello_world {
tonic::include_proto!("helloworld"); // The string specified here must match the proto package name
}

#[instrument]
fn expensive_fn(to_print: String) {
for _ in 0..5 {
std::thread::sleep(std::time::Duration::from_secs(1));
info!("{}", to_print);
}
}

#[derive(Debug, Default)]
pub struct MyGreeter {}

#[tonic::async_trait]
impl Greeter for MyGreeter {
#[instrument]
async fn say_hello(
&self,
request: Request<HelloRequest>, // Accept request of type HelloRequest
) -> Result<Response<HelloReply>, Status> {
let propagator = api::TraceContextPropagator::new();
let parent_cx = propagator.extract(&HttpHeaderMapCarrier(request.metadata()));
let span = tracing::Span::current();
span.set_parent(&parent_cx);
let name = request.into_inner().name;
expensive_fn(format!("Got name: {:?}", name));

// Return an instance of type HelloReply
let reply = hello_world::HelloReply {
message: format!("Hello {}!", name), // We must use .into_inner() as the fields of gRPC requests and responses are private
};

Ok(Response::new(reply)) // Send back our formatted greeting
}
}

fn tracing_init() -> Result<(), Box<dyn std::error::Error>> {
let builder = opentelemetry_jaeger::Exporter::builder()
.with_agent_endpoint("127.0.0.1:6831".parse().unwrap());

let exporter = builder
.with_process(opentelemetry_jaeger::Process {
service_name: "grpc-server".to_string(),
tags: vec![KeyValue::new("version", "0.1.0")],
})
.init()?;

// For the demonstration, use `Sampler::Always` sampler to sample all traces. In a production
// application, use `Sampler::Parent` or `Sampler::Probability` with a desired probability.
let provider = sdk::Provider::builder()
.with_simple_exporter(exporter)
.with_config(sdk::Config {
default_sampler: Box::new(Sampler::Always),
..Default::default()
})
.build();
let tracer = provider.get_tracer("grpc-server");

let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);
tracing_subscriber::registry()
.with(opentelemetry)
.try_init()?;

Ok(())
}

struct HttpHeaderMapCarrier<'a>(&'a tonic::metadata::MetadataMap);
impl<'a> api::Carrier for HttpHeaderMapCarrier<'a> {
fn get(&self, key: &'static str) -> Option<&str> {
self.0
.get(key.to_lowercase().as_str())
.and_then(|value| value.to_str().ok())
}

fn set(&mut self, _key: &'static str, _value: String) {
unimplemented!()
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_init()?;
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();

Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;

Ok(())
}
Binary file added examples/tracing-grpc/trace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.