Getting Started
このページでは、Rust で OpenTelemetry を使い始める方法を紹介します。
シンプルな Rust アプリケーションを計装し、トレース、メトリクス、ログ がコンソールに出力されるようにする方法を学びます。
前提条件
ローカル環境に以下がインストールされていることを確認してください。
アプリケーション例
以下の例では、基本的な hyper アプリケーションを使用します。 hyper を使用していなくても問題ありません。OpenTelemetry Rust は、Actix Web や Tide などの他の HTTP 実装でも使用できます。 サポートされているフレームワーク向けのライブラリの完全な一覧については、レジストリを参照してください。
より高度な例については、例を参照してください。
依存関係
まず、新しいディレクトリで cargo new dice_server を使って実行ファイルを作成し、Cargo.toml ファイルに以下の内容を追加します。
[package]
name = "dice_server"
version = "0.1.0"
edition = "2021"
[dependencies]
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"
HTTP サーバーを作成して起動する
main.rs を以下のように変更します。
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 tokio::net::TcpListener;
async fn roll_dice(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, 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<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
match (req.method(), req.uri().path()) {
(&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() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
let listener = TcpListener::bind(addr).await?;
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);
}
});
}
}
次のコマンドでアプリケーションをビルドして実行し、Web ブラウザで http://localhost:8080/rolldice を開いて動作していることを確認します。
$ cargo run
...
Listening on 127.0.0.1:8080
計装
ここでは、サンプルアプリに OpenTelemetry の計装を追加する方法を示します。 自分のアプリケーションを使用している場合も、それに沿って進められます。コードは多少異なるかもしれません。
依存関係を追加する
OpenTelemetry Rust SDK の opentelemetry、OpenTelemetry の Stdout エクスポーターである opentelemetry-stdout、そしてログ用のブリッジである opentelemetry-appender-tracing の依存関係を Cargo.toml に追加します。
opentelemetry = { version = "0.28.0", features = ["metrics"] }
opentelemetry_sdk = { version = "0.28.0", features = ["trace", "metrics", "logs"] }
opentelemetry-stdout = { version = "0.28.0", features = ["trace", "metrics", "logs"] }
opentelemetry-appender-tracing = "0.28.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
計装済みの完全なアプリケーション
main.rs を以下の完全に計装されたバージョンに置き換えます。
以下のセクションでは、各シグナルが何を追加するのかを説明します。
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::KeyValue;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_stdout::{LogExporter, MetricExporter, SpanExporter};
use rand::Rng;
use tokio::net::TcpListener;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
// --- メトリクス: カウンターはリクエスト間で再利用するために一度だけ保存する ---
static ROLL_COUNTER: OnceLock<opentelemetry::metrics::Counter<u64>> = OnceLock::new();
fn get_roll_counter() -> &'static opentelemetry::metrics::Counter<u64> {
ROLL_COUNTER.get_or_init(|| {
global::meter("dice_server")
.u64_counter("dice.rolls")
.with_description("The number of rolls by roll value")
.build()
})
}
// --- アプリケーションハンドラー ---
async fn roll_dice(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
let random_number = rand::rng().random_range(1..=6);
// メトリクス: 各ロールを記録する
get_roll_counter().add(1, &[KeyValue::new("roll.value", random_number as i64)]);
// ログ: tracingブリッジ経由で構造化ログイベントを送出する
tracing::info!(name: "roll_dice", roll.value = random_number, message = "Player rolled the dice");
Ok(Response::new(Full::new(Bytes::from(
random_number.to_string(),
))))
}
async fn handle(req: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
// トレース: 受信リクエストごとにサーバースパンを作成する
let tracer = get_tracer();
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") => roll_dice(req).await,
_ => {
span.set_status(Status::Ok);
Ok(Response::builder()
.status(404)
.body(Full::new(Bytes::from("Not Found")))
.unwrap())
}
}
}
// --- トレース: グローバルトレーサーアクセサー ---
fn get_tracer() -> &'static BoxedTracer {
static TRACER: OnceLock<BoxedTracer> = OnceLock::new();
TRACER.get_or_init(|| global::tracer("dice_server"))
}
// --- プロバイダーの初期化 ---
fn init_tracer_provider() -> SdkTracerProvider {
let provider = SdkTracerProvider::builder()
.with_simple_exporter(SpanExporter::default())
.build();
global::set_tracer_provider(provider.clone());
provider
}
fn init_meter_provider() -> SdkMeterProvider {
let provider = SdkMeterProvider::builder()
.with_periodic_exporter(MetricExporter::default())
.build();
global::set_meter_provider(provider.clone());
provider
}
fn init_logger_provider() -> SdkLoggerProvider {
SdkLoggerProvider::builder()
.with_simple_exporter(LogExporter::default())
.build()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
// プロバイダーを初期化してシャットダウン用に保持する
let tracer_provider = init_tracer_provider();
let meter_provider = init_meter_provider();
let logger_provider = init_logger_provider();
// ログ: tracing::info! 等が OTel に送られるように tracingブリッジを接続する
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
tracing_subscriber::registry()
.with(otel_layer)
.init();
let listener = TcpListener::bind(addr).await?;
tracing::info!("Listening on {addr}");
loop {
tokio::select! {
Ok((stream, _)) = listener.accept() => {
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);
}
});
}
_ = tokio::signal::ctrl_c() => {
break;
}
}
}
// 終了前にすべてのプロバイダーをフラッシュしてシャットダウンする
if let Err(err) = tracer_provider.shutdown() {
eprintln!("Error shutting down tracer provider: {err:?}");
}
if let Err(err) = meter_provider.shutdown() {
eprintln!("Error shutting down meter provider: {err:?}");
}
if let Err(err) = logger_provider.shutdown() {
eprintln!("Error shutting down logger provider: {err:?}");
}
Ok(())
}
サーバーを起動します。
$ cargo run
...
Listening on 127.0.0.1:8080
トレース
トレースは handle() で追加されます。
受信した HTTP リクエストごとに、グローバルプロバイダーから取得した Tracer を使って サーバースパン が作成されます。
let mut span = tracer
.span_builder(format!("{} {}", req.method(), req.uri().path()))
.with_kind(SpanKind::Server)
.start(tracer);
init_tracer_provider() は stdout エクスポーターを備えた SdkTracerProvider を構築し、グローバルに設定したうえで、終了時に main() が .shutdown() を呼び出せるようにそれを返します。
http://localhost:8080/rolldice にリクエストを送信すると、コンソールにスパンが出力されます。
出力例
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
メトリクス
dice.rolls という名前の u64_counter が(OnceLock を介して)一度だけ作成され、roll_dice() の中でインクリメントされます。
get_roll_counter().add(1, &[KeyValue::new("roll.value", random_number as i64)]);
init_meter_provider() は定期的な stdout エクスポーターを備えた SdkMeterProvider を構築します。
しばらくすると、カウンターが出力されるのが確認できます。
出力例
Metrics
Resource
-> service.name=String(Static("unknown_service"))
-> telemetry.sdk.language=String(Static("rust"))
-> telemetry.sdk.name=String(Static("opentelemetry"))
-> telemetry.sdk.version=String(Static("0.28.0"))
Metric #0
Instrumentation Scope
Name : "dice_server"
Name : dice.rolls
Description : The number of rolls by roll value
Unit :
Type : Sum
Value: 1
Attributes:
-> roll.value: Int(3)
Value: 2
Attributes:
-> roll.value: Int(5)
ログ
OpenTelemetry Rust は、独自のエンドユーザー向けロギング API を提供していません。
そのかわりに、既存の Rust のロギングフレームワークを OpenTelemetry のデータモデルへとブリッジします。
推奨されるアプローチでは、tracing クレートを opentelemetry-appender-tracing ブリッジと組み合わせて使用します。
init_logger_provider() は stdout エクスポーターを備えた SdkLoggerProvider を構築します。
main() の中で OpenTelemetryTracingBridge が tracing_subscriber のスタックに組み込まれ、tracing::info!(やその他のレベル)の呼び出しがすべて OTel のログパイプラインへ転送されます。
let otel_layer = OpenTelemetryTracingBridge::new(&logger_provider);
tracing_subscriber::registry()
.with(otel_layer)
.init();
roll_dice() の中では、構造化されたログイベントが出力されます。
tracing::info!(name: "roll_dice", roll.value = random_number, message = "Player rolled the dice");
スパンやメトリクスとともに、ログレコードがコンソールに表示されるようになります。
出力例
Logs
Resource
-> service.name=String(Static("unknown_service"))
-> telemetry.sdk.language=String(Static("rust"))
-> telemetry.sdk.name=String(Static("opentelemetry"))
-> telemetry.sdk.version=String(Static("0.28.0"))
Log #0
Instrumentation Scope
Name : "dice_server"
Timestamp : 2025-03-11 00:47:26.687497
Severity : Info
Body : Player rolled the dice
Attributes:
-> roll.value: Int(3)
次のステップ
さらに学ぶには、以下を参照してください。
フィードバック
このページは役に立ちましたか?
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!