Getting Started

You are viewing the English version of this page because it has not yet been fully translated. Interested in helping out? See Contributing.

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: