diff --git a/content/en/docs/languages/rust/getting-started.md b/content/en/docs/languages/rust/getting-started.md index b461c2866184..23101aac2694 100644 --- a/content/en/docs/languages/rust/getting-started.md +++ b/content/en/docs/languages/rust/getting-started.md @@ -28,65 +28,78 @@ For more elaborate examples, see [examples](/docs/languages/rust/examples/). ### Dependencies -To begin, create a file `Cargo.toml` in a new directory and add the following -content: +To begin, create an executable using `cargo new dice_server` in a new directory +and add the following content to the `Cargo.toml` file: ```toml [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" } +hyper = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["full"] } +http-body-util = "0.1" +hyper-util = { version = "0.1", features = ["full"] } +rand = "0.9.0" ``` ### 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: +Modify `main.rs` to the following: ```rust -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Request, Response, Server, Method, StatusCode}; +use std::convert::Infallible; +use std::net::SocketAddr; + +use http_body_util::Full; +use hyper::body::Bytes; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::Method; +use hyper::{Request, Response}; +use hyper_util::rt::TokioIo; use rand::Rng; -use std::{convert::Infallible, net::SocketAddr}; +use tokio::net::TcpListener; -async fn handle(req: Request) -> Result, Infallible> { - let mut response = Response::new(Body::empty()); +async fn roll_dice(_: Request) -> Result>, Infallible> { + let random_number = rand::rng().random_range(1..=6); + Ok(Response::new(Full::new(Bytes::from( + random_number.to_string(), + )))) +} +async fn handle(req: Request) -> Result>, Infallible> { 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) + (&Method::GET, "/rolldice") => roll_dice(req).await, + _ => Ok(Response::builder() + .status(404) + .body(Full::new(Bytes::from("Not Found"))) + .unwrap()), + } } #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { 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); + let listener = TcpListener::bind(addr).await?; - println!("Listening on {addr}"); - if let Err(e) = server.await { - eprintln!("server error: {e}"); + loop { + let (stream, _) = listener.accept().await?; + + let io = TokioIo::new(stream); + + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection(io, service_fn(handle)) + .await + { + eprintln!("Error serving connection: {:?}", err); + } + }); } } ``` @@ -95,7 +108,7 @@ Build and run the application with the following command, then open in your web browser to ensure it is working. ```console -$ cargo run --bin dice_server +$ cargo run ... Listening on 127.0.0.1:8080 ``` @@ -114,67 +127,85 @@ opentelemetry_sdk = "{{% version-from-registry otel-rust-sdk %}}" opentelemetry-stdout = { version = "{{% version-from-registry exporter-rust-stdout %}}", features = ["trace"] } ``` -Update the `dice_server.rs` file with code to initialize a tracer and to emit -spans when the `handle` function is called: +Update the `main.rs` file with code to initialize a tracer and to emit spans +when the `handle` function is called: ```rust -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 std::convert::Infallible; +use std::net::SocketAddr; +use std::sync::OnceLock; + +use http_body_util::Full; +use hyper::body::Bytes; +use hyper::server::conn::http1; +use hyper::service::service_fn; +use hyper::Method; +use hyper::{Request, Response}; +use hyper_util::rt::TokioIo; +use opentelemetry::global::{self, BoxedTracer}; +use opentelemetry::trace::{Span, SpanKind, Status, Tracer}; +use opentelemetry_sdk::trace::SdkTracerProvider; use opentelemetry_stdout::SpanExporter; +use rand::Rng; +use tokio::net::TcpListener; -async fn handle(req: Request) -> Result, Infallible> { - let mut response = Response::new(Body::empty()); +async fn roll_dice(_: Request) -> Result>, Infallible> { + let random_number = rand::rng().random_range(1..=6); + Ok(Response::new(Full::new(Bytes::from( + random_number.to_string(), + )))) +} - let tracer = global::tracer("dice_server"); +async fn handle(req: Request) -> Result>, Infallible> { + let tracer = get_tracer(); let mut span = tracer .span_builder(format!("{} {}", req.method(), req.uri().path())) .with_kind(SpanKind::Server) - .start(&tracer); + .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); - } + (&Method::GET, "/rolldice") => roll_dice(req).await, _ => { - *response.status_mut() = StatusCode::NOT_FOUND; - span.set_status(Status::error("Not Found")); + span.set_status(Status::Ok); + Ok(Response::builder() + .status(404) + .body(Full::new(Bytes::from("Not Found"))) + .unwrap()) } - }; + } +} - Ok(response) +fn get_tracer() -> &'static BoxedTracer { + static TRACER: OnceLock = OnceLock::new(); + TRACER.get_or_init(|| global::tracer("dice_server")) } -fn init_tracer() { - global::set_text_map_propagator(TraceContextPropagator::new()); - let provider = TracerProvider::builder() +fn init_tracer_provider() { + let provider = SdkTracerProvider::builder() .with_simple_exporter(SpanExporter::default()) .build(); global::set_tracer_provider(provider); } #[tokio::main] -async fn main() { - init_tracer(); +async fn main() -> Result<(), Box> { 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); + let listener = TcpListener::bind(addr).await?; + init_tracer_provider(); - println!("Listening on {addr}"); - if let Err(e) = server.await { - eprintln!("server error: {e}"); + loop { + let (stream, _) = listener.accept().await?; + let io = TokioIo::new(stream); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection(io, service_fn(handle)) + .await + { + eprintln!("Error serving connection: {:?}", err); + } + }); } } ``` @@ -182,57 +213,34 @@ async fn main() { Start your server again: ```sh -$ cargo run --bin dice_server +$ cargo run ... Listening on 127.0.0.1:8080 ``` When you send a request to the server at , -you'll see a span being emitted to the console (output is pretty printed for -convenience): - -```json -{ - "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" - } - ] - } - ] - } - ] -} +you'll see a span being emitted to the console: + +```txt +Spans +Resource + -> telemetry.sdk.version=String(Static("0.28.0")) + -> service.name=String(Static("unknown_service")) + -> telemetry.sdk.language=String(Static("rust")) + -> telemetry.sdk.name=String(Static("opentelemetry")) +Span #0 + Instrumentation Scope + Name : "dice_server" + + Name : GET /rolldice + TraceId : 9f03de7cf14780bd54b95d7095332107 + SpanId : 9faed88b3f9ed699 + TraceFlags : TraceFlags(1) + ParentSpanId: 0000000000000000 + Kind : Server + Start time: 2025-03-11 00:47:26.687497 + End time: 2025-03-11 00:47:26.687653 + Status: Unset ``` ## What next? diff --git a/data/registry/exporter-rust-stdout.yml b/data/registry/exporter-rust-stdout.yml index c29484fd9b97..6c51590bb522 100644 --- a/data/registry/exporter-rust-stdout.yml +++ b/data/registry/exporter-rust-stdout.yml @@ -16,4 +16,4 @@ createdAt: 2024-01-19 package: registry: crates name: opentelemetry-stdout - version: 0.27.0 + version: 0.28.0 diff --git a/data/registry/otel-rust-sdk.yml b/data/registry/otel-rust-sdk.yml index 50f7beec71e6..a7ebd49f2b00 100644 --- a/data/registry/otel-rust-sdk.yml +++ b/data/registry/otel-rust-sdk.yml @@ -13,4 +13,4 @@ createdAt: 2020-02-04 package: registry: crates name: opentelemetry_sdk - version: 0.22.1 + version: 0.28.0 diff --git a/data/registry/otel-rust.yml b/data/registry/otel-rust.yml index ae5d83e78f18..ccdfbe248591 100644 --- a/data/registry/otel-rust.yml +++ b/data/registry/otel-rust.yml @@ -13,4 +13,4 @@ createdAt: 2020-02-04 package: registry: crates name: opentelemetry - version: 0.22.0 + version: 0.28.0