Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
abcec6a
include server trace information in tower instrumentation
jan-xyz Aug 22, 2025
20f580f
remove useless comment
jan-xyz Aug 25, 2025
84c2860
update README
jan-xyz Aug 25, 2025
dc535a5
remove http.route
jan-xyz Aug 25, 2025
2497a7f
clean up code and documentation
jan-xyz Aug 25, 2025
46d4510
add CHANGELOG
jan-xyz Aug 25, 2025
c29aa6f
Merge branch 'main' into add_tower_tracing
jan-xyz Aug 25, 2025
1d61504
remove unused import
jan-xyz Aug 25, 2025
0e9cc0b
use global providers
jan-xyz Aug 27, 2025
8a5e533
break backwards compatibility
jan-xyz Aug 27, 2025
7b97677
undo semconv for metrics, it's done in a separate PR
jan-xyz Aug 27, 2025
27248f7
add helpers to inject meter and tracer provider to have tests not int…
jan-xyz Aug 28, 2025
47300ff
fix trait
jan-xyz Aug 28, 2025
8c4081b
fmt
jan-xyz Aug 28, 2025
b4d597a
update CHANGELOG
jan-xyz Aug 28, 2025
0fd11c5
update examples
jan-xyz Aug 28, 2025
62425e9
update remaining labels to semconv
jan-xyz Aug 28, 2025
ed127a5
Merge branch 'main' into add_tower_tracing
jan-xyz Aug 29, 2025
4d10b92
upgrade tower-test
jan-xyz Aug 29, 2025
edea91b
Merge branch 'main' into add_tower_tracing
jan-xyz Sep 5, 2025
a129c22
Merge branch 'main' into add_tower_tracing
jan-xyz Sep 9, 2025
fe6f068
Merge branch 'main' into add_tower_tracing
jan-xyz Oct 29, 2025
42e5223
move migration guide to vNext
jan-xyz Oct 29, 2025
d0ec3cc
don't set Ok
jan-xyz Nov 25, 2025
9f26844
Merge branch 'main' into add_tower_tracing
jan-xyz Nov 25, 2025
e03beb2
rename layer in example
jan-xyz Nov 25, 2025
eaf22d3
setup also tracing in examples
jan-xyz Nov 25, 2025
241bf2e
extract parent span
jan-xyz Nov 25, 2025
76702fb
add imports and Cargo dependency for HTTP extractor
jan-xyz Nov 25, 2025
5439709
attach and activate Context
jan-xyz Nov 25, 2025
1a0a77e
add ContextGuard import
jan-xyz Nov 25, 2025
6a56a2e
re-add sdk for now to keep it compiling
jan-xyz Nov 25, 2025
7248139
re-add sdk for now to keep it compiling
jan-xyz Nov 25, 2025
bb9b92f
Revert "attach and activate Context"
jan-xyz Nov 25, 2025
36fe784
Revert "add ContextGuard import"
jan-xyz Nov 25, 2025
d9189c1
remove sdk dependency
jan-xyz Nov 25, 2025
0394821
remove tracer from builder
jan-xyz Nov 25, 2025
6da62ae
undo meter changes
jan-xyz Nov 25, 2025
ae516e6
smaller fixups
jan-xyz Nov 26, 2025
89c6d2d
use the opentelemetry with_context future extension for attaching
jan-xyz Nov 26, 2025
c1f6a04
clean up around the context handling and response parsing
jan-xyz Nov 26, 2025
9c02108
Add field comments and move RequestData before Service impl
jan-xyz Nov 26, 2025
7cc2289
Remove unused dependencies futures-util and pin-project-lite
jan-xyz Nov 26, 2025
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
43 changes: 43 additions & 0 deletions opentelemetry-instrumentation-tower/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,49 @@

## vNext

### Changed

* **BREAKING**: Removed `with_meter()` method. The middleware now uses global meter and tracer providers by default via `opentelemetry::global::meter()` and `opentelemetry::global::tracer()`, with optional overrides via `with_tracer_provider()` and `with_meter_provider()` methods.
* **BREAKING**: Renamed types. Use the new names:
- `HTTPMetricsLayer` → `HTTPLayer`
- `HTTPMetricsService` → `HTTPService`
- `HTTPMetricsResponseFuture` → `HTTPResponseFuture`
- `HTTPMetricsLayerBuilder` → `HTTPLayerBuilder`
* Added OpenTelemetry trace support

### Migration Guide

#### API Changes

Before:
```rust
use opentelemetry_instrumentation_tower::HTTPMetricsLayerBuilder;

let layer = HTTPMetricsLayerBuilder::builder()
.with_meter(meter)
.build()
.unwrap();
```

After:
```rust
use opentelemetry_instrumentation_tower::HTTPLayer;

// Set global providers
global::set_meter_provider(meter_provider);
global::set_tracer_provider(tracer_provider); // for tracing support

// Then create the layer - simple API using global providers
let layer = HTTPLayer::new();
```

#### Type Name Changes

- Replace `HTTPMetricsLayerBuilder` with `HTTPLayerBuilder`
- Replace `HTTPMetricsLayer` with `HTTPLayer`
- Replace `HTTPMetricsService` with `HTTPService`
- Replace `HTTPMetricsResponseFuture` with `HTTPResponseFuture`

## v0.17.0

### Changed
Expand Down
8 changes: 4 additions & 4 deletions opentelemetry-instrumentation-tower/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ rust-version = "1.75.0"

version = "0.17.0"
license = "Apache-2.0"
description = "OpenTelemetry Metrics Middleware for Tower-compatible Rust HTTP servers"
description = "OpenTelemetry Metrics and Tracing Middleware for Tower-compatible Rust HTTP servers"
homepage = "https://github.com/open-telemetry/opentelemetry-rust-contrib"
repository = "https://github.com/open-telemetry/opentelemetry-rust-contrib"
documentation = "https://docs.rs/tower-otel-http-metrics"
Expand All @@ -18,19 +18,19 @@ axum = ["dep:axum"]

[dependencies]
axum = { features = ["matched-path", "macros"], version = "0.8", default-features = false, optional = true }
futures-util = { version = "0.3", default-features = false }
http = { version = "1", features = ["std"], default-features = false }
http-body = { version = "1", default-features = false }
opentelemetry = { workspace = true, features = ["futures", "metrics"]}
opentelemetry = { workspace = true, features = ["futures", "metrics", "trace"] }
opentelemetry-http = "0.31"
opentelemetry-semantic-conventions = { workspace = true, features = ["semconv_experimental"] }
pin-project-lite = { version = "0.2", default-features = false }
tower-service = { version = "0.3", default-features = false }
tower-layer = { version = "0.3", default-features = false }

[dev-dependencies]
opentelemetry_sdk = { workspace = true, features = ["metrics", "testing"] }
tokio = { version = "1.0", features = ["macros", "rt"] }
tower = { version = "0.5", features = ["util"] }
tower-test = { version = "0.4" }

[lints]
workspace = true
36 changes: 34 additions & 2 deletions opentelemetry-instrumentation-tower/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
# Tower OTEL Metrics Middleware
# Tower OTEL HTTP Instrumentation Middleware

OpenTelemetry Metrics Middleware for Tower-compatible Rust HTTP servers.
OpenTelemetry HTTP Metrics and Tracing Middleware for Tower-compatible Rust HTTP servers.

This middleware provides both metrics and distributed tracing for HTTP requests, following OpenTelemetry semantic conventions.

## Features

- **HTTP Metrics**: Request duration, active requests, request/response body sizes
- **Distributed Tracing**: HTTP spans with semantic attributes
- **Semantic Conventions**: Uses OpenTelemetry semantic conventions for consistent attribute naming
- **Flexible Configuration**: Support for custom attribute extractors and tracer configuration
- **Framework Support**: Works with any Tower-compatible HTTP framework (Axum, Hyper, Tonic etc.)

## Usage

## Metrics

The middleware exports the following metrics:

- `http.server.request.duration` - Duration of HTTP requests
- `http.server.active_requests` - Number of active HTTP requests
- `http.server.request.body.size` - Size of HTTP request bodies
- `http.server.response.body.size` - Size of HTTP response bodies

## Tracing

HTTP spans are created with the following attributes (following OpenTelemetry semantic conventions):

- `http.request.method` - HTTP method
- `url.scheme` - URL scheme (http/https)
- `url.path` - Request path
- `url.full` - Full URL
- `user_agent.original` - User agent string
- `http.response.status_code` - HTTP response status code

## Examples

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ axum = { features = ["http1", "tokio"], version = "0.8", default-features = fals
bytes = { version = "1", default-features = false }
opentelemetry = { workspace = true}
opentelemetry_sdk = { workspace = true, default-features = false }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics"], default-features = false }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics", "trace"], default-features = false }
tokio = { version = "1", features = ["rt-multi-thread"], default-features = false }
rand_09 = { package = "rand", version = "0.9" }

[lints]
workspace = true
workspace = true
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use axum::routing::{get, post, put, Router};
use bytes::Bytes;
use opentelemetry::global;
use opentelemetry_instrumentation_tower as otel_tower_metrics;
use opentelemetry_instrumentation_tower::HTTPLayer;
use opentelemetry_otlp::{MetricExporter, SpanExporter};
use opentelemetry_sdk::{
metrics::{PeriodicReader, SdkMeterProvider},
trace::SdkTracerProvider,
};
use std::time::Duration;

const SERVICE_NAME: &str = "example-axum-http-service";
Expand Down Expand Up @@ -40,34 +45,47 @@ async fn handle() -> Bytes {

#[tokio::main]
async fn main() {
let exporter = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
// .with_endpoint("http://localhost:4317") // default; leave out in favor of env var OTEL_EXPORTER_OTLP_ENDPOINT
.build()
.unwrap();
{
let exporter = MetricExporter::builder()
.with_tonic()
// .with_endpoint("http://localhost:4317") // default; leave out in favor of env var OTEL_EXPORTER_OTLP_ENDPOINT
.build()
.unwrap();

let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(exporter)
.with_interval(_OTEL_METRIC_EXPORT_INTERVAL)
.build();
let reader = PeriodicReader::builder(exporter)
.with_interval(_OTEL_METRIC_EXPORT_INTERVAL)
.build();

let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
.with_reader(reader)
.with_resource(init_otel_resource())
.build();
let provider = SdkMeterProvider::builder()
.with_reader(reader)
.with_resource(init_otel_resource())
.build();

global::set_meter_provider(meter_provider);
// init our otel metrics middleware
let global_meter = global::meter(SERVICE_NAME);
let otel_metrics_service_layer = otel_tower_metrics::HTTPMetricsLayerBuilder::builder()
.with_meter(global_meter)
.build()
.unwrap();
global::set_meter_provider(provider);
}

{
let exporter = SpanExporter::builder()
.with_tonic()
// .with_endpoint("http://localhost:4317") // default; leave out in favor of env var OTEL_EXPORTER_OTLP_ENDPOINT
.build()
.unwrap();

let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(init_otel_resource())
.build();

global::set_tracer_provider(provider);
}

let otel_service_layer = HTTPLayer::new();

let app = Router::new()
.route("/", get(handle))
.route("/", post(handle))
.route("/", put(handle))
.layer(otel_metrics_service_layer);
.layer(otel_service_layer);

let listener = tokio::net::TcpListener::bind("0.0.0.0:5000").await.unwrap();
let server = axum::serve(listener, app);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ http-body-util = { version = "0.1", default-features = false }
hyper-util = { version = "0.1", features = ["http1", "service", "server", "tokio"], default-features = false }
opentelemetry = { workspace = true}
opentelemetry_sdk = { workspace = true, default-features = false }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics"], default-features = false }
opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics", "trace"], default-features = false }
tokio = { version = "1", features = ["rt-multi-thread", "macros"], default-features = false }
tower = { version = "0.5", default-features = false }
rand_09 = { package = "rand", version = "0.9" }

[lints]
workspace = true
workspace = true
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use http_body_util::Full;
use hyper::body::Bytes;
use hyper::{Request, Response};
use opentelemetry::global;
use opentelemetry_instrumentation_tower as otel_tower_metrics;
use opentelemetry_instrumentation_tower::HTTPLayer;
use opentelemetry_otlp::{MetricExporter, SpanExporter};
use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
use opentelemetry_sdk::trace::SdkTracerProvider;
use std::convert::Infallible;
use std::net::SocketAddr;
use std::time::Duration;
Expand Down Expand Up @@ -45,31 +48,44 @@ async fn handle(_req: Request<hyper::body::Incoming>) -> Result<Response<Full<By

#[tokio::main]
async fn main() {
let exporter = opentelemetry_otlp::MetricExporter::builder()
.with_tonic()
// .with_endpoint("http://localhost:4317") // default; leave out in favor of env var OTEL_EXPORTER_OTLP_ENDPOINT
.build()
.unwrap();

let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(exporter)
.with_interval(_OTEL_METRIC_EXPORT_INTERVAL)
.build();

let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
.with_reader(reader)
.with_resource(init_otel_resource())
.build();

global::set_meter_provider(meter_provider);
// init our otel metrics middleware
let global_meter = global::meter(SERVICE_NAME);
let otel_metrics_service_layer = otel_tower_metrics::HTTPMetricsLayerBuilder::builder()
.with_meter(global_meter)
.build()
.unwrap();
{
let exporter = MetricExporter::builder()
.with_tonic()
// .with_endpoint("http://localhost:4317") // default; leave out in favor of env var OTEL_EXPORTER_OTLP_ENDPOINT
.build()
.unwrap();

let reader = PeriodicReader::builder(exporter)
.with_interval(_OTEL_METRIC_EXPORT_INTERVAL)
.build();

let provider = SdkMeterProvider::builder()
.with_reader(reader)
.with_resource(init_otel_resource())
.build();

global::set_meter_provider(provider);
}

{
let exporter = SpanExporter::builder()
.with_tonic()
// .with_endpoint("http://localhost:4317") // default; leave out in favor of env var OTEL_EXPORTER_OTLP_ENDPOINT
.build()
.unwrap();

let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(init_otel_resource())
.build();

global::set_tracer_provider(provider);
}

let otel_service_layer = HTTPLayer::new();

let tower_service = ServiceBuilder::new()
.layer(otel_metrics_service_layer)
.layer(otel_service_layer)
.service_fn(handle);
let hyper_service = hyper_util::service::TowerToHyperService::new(tower_service);

Expand Down
Loading