Getting Started

This page will show you how to get started with OpenTelemetry in Rust.

You will learn how you can instrument a simple Rust application, in such a way that traces are emitted to the console.

Prerequisites

Ensure that you have the following installed locally:

Example Application

The following example uses a basic hyper application. If you are not using hyper, that’s OK — you can use OpenTelemetry Rust with other HTTP implementations as well, such as Actix Web and Tide. For a complete list of libraries for supported frameworks, see the registry.

For more elaborate examples, see examples.

Dependencies

To begin, create a file Cargo.toml in a new directory and add the following content:

[package] name = "dice_server" version = "0.1.0" edition = "2021" publish = false [[bin]] name = "dice_server" path = "dice_server.rs" doc = false [dependencies] hyper = { version = "0.14", features = ["full"] } tokio = { version = "1.29", features = ["full"] } rand = { version = "0.8" }

Create and launch an HTTP Server

In that same folder, create a file called dice_server.rs and add the following code to the file:

use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request, Response, Server, Method, StatusCode}; use rand::Rng; use std::{convert::Infallible, net::SocketAddr}; async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> { let mut response = Response::new(Body::empty()); match (req.method(), req.uri().path()) { (&Method::GET, "/rolldice") => { let random_number = rand::thread_rng().gen_range(1..7); *response.body_mut() = Body::from(random_number.to_string()); } _ => { *response.status_mut() = StatusCode::NOT_FOUND; } }; Ok(response) } #[tokio::main] async fn main() { let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) }); let server = Server::bind(&addr).serve(make_svc); println!("Listening on {addr}"); if let Err(e) = server.await { eprintln!("server error: {e}"); } }

Build and run the application with the following command, then open http://localhost:8080/rolldice in your web browser to ensure it is working.

$ cargo run --bin dice_server ... Listening on 127.0.0.1:8080

Instrumentation

To add OpenTelemetry to your application, update the Cargo.toml with the dependencies for the OpenTelemetry Rust SDK opentelemetry and the OpenTelemetry Stdout Exporter opentelemetry-stdout:

opentelemetry = "0.22.0" opentelemetry_sdk = "0.22.1" opentelemetry-stdout = { version = "0.27.0", features = ["trace"] }

Update the dice_server.rs file with code to initialize a tracer and to emit spans when the handle function is called:

use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server, StatusCode}; use rand::Rng; use std::{convert::Infallible, net::SocketAddr}; use opentelemetry::global::ObjectSafeSpan; use opentelemetry::trace::{SpanKind, Status}; use opentelemetry::{global, trace::Tracer}; use opentelemetry_sdk::propagation::TraceContextPropagator; use opentelemetry_sdk::trace::TracerProvider; use opentelemetry_stdout::SpanExporter; async fn handle(req: Request<Body>) -> Result<Response<Body>, Infallible> { let mut response = Response::new(Body::empty()); let tracer = global::tracer("dice_server"); let mut span = tracer .span_builder(format!("{} {}", req.method(), req.uri().path())) .with_kind(SpanKind::Server) .start(&tracer); match (req.method(), req.uri().path()) { (&Method::GET, "/rolldice") => { let random_number = rand::thread_rng().gen_range(1..7); *response.body_mut() = Body::from(random_number.to_string()); span.set_status(Status::Ok); } _ => { *response.status_mut() = StatusCode::NOT_FOUND; span.set_status(Status::error("Not Found")); } }; Ok(response) } fn init_tracer() { global::set_text_map_propagator(TraceContextPropagator::new()); 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], 8080)); let make_svc = make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn(handle)) }); let server = Server::bind(&addr).serve(make_svc); println!("Listening on {addr}"); if let Err(e) = server.await { eprintln!("server error: {e}"); } }

Start your server again:

$ cargo run --bin dice_server ... Listening on 127.0.0.1:8080

When you send a request to the server at http://localhost:8080/rolldice, you’ll see a span being emitted to the console (output is pretty printed for convenience):

{ "resourceSpans": [ { "resource": { "attributes": [ { "key": "service.name", "value": { "stringValue": "unknown_service" } } ] }, "scopeSpans": [ { "scope": { "name": "dice_server" }, "spans": [ { "attributes": [], "droppedAttributesCount": 0, "droppedEventsCount": 0, "droppedLinksCount": 0, "endTimeUnixNano": 1691076354768034000, "kind": 2, "name": "GET /rolldice", "parentSpanId": "", "spanId": "27e1d7d8e44a63c5", "startTimeUnixNano": 1691076354768025000, "status": { "code": 2 }, "traceId": "adfe9d364ee19610adde517d722167ca" } ] } ] } ] }

What next?

For more: