Skip to content

Commit dbfb7fa

Browse files
authored
implement server.response.body.size metric (#21)
implement http.server.response.body.size
1 parent 45837c8 commit dbfb7fa

5 files changed

Lines changed: 36 additions & 15 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "tower-otel-http-metrics"
33
edition = "2021"
4-
version = "0.11.0"
4+
version = "0.12.0"
55
license = "MIT"
66
description = "OpenTelemetry Metrics Middleware for Tower-compatible Rust HTTP servers"
77
homepage = "https://github.com/francoposa/tower-otel-http-metrics"
@@ -18,6 +18,7 @@ axum = ["dep:axum"]
1818
axum = { features = ["matched-path", "macros"], version = "0.8", default-features = false, optional = true }
1919
futures-util = { version = "0.3", default-features = false }
2020
http = { version = "1", features = ["std"], default-features = false }
21+
http-body = { version = "1", default-features = false }
2122
opentelemetry = { version = "0.28", features = ["metrics"], default-features = false }
2223
pin-project-lite = { version = "0.2", default-features = false }
2324
tower = { version = "0.5", default-features = false }

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ fn init_otel_resource() -> Resource {
4646
// slow requests to show up on the histograms without completely blocking up the server.
4747
const PCT_SLOW_REQUESTS: u64 = 5;
4848
const MAX_SLOW_REQUEST_SEC: u64 = 16;
49-
const MAX_BODY_SIZE_MULTIPLE: u64 = 128;
49+
// MAX_BODY_SIZE_MULTIPLE is used to demonstrate the `http.server.response.body.size` histogram
50+
const MAX_BODY_SIZE_MULTIPLE: u64 = 16;
5051

5152
#[axum::debug_handler]
5253
async fn handle() -> Bytes {
@@ -55,7 +56,7 @@ async fn handle() -> Bytes {
5556
tokio::time::sleep(Duration::from_secs(slow_request_secs)).await;
5657
};
5758
let body_size_multiple = rand_09::random_range(0..=MAX_BODY_SIZE_MULTIPLE);
58-
Bytes::from("{'msg': 'hello world'}".repeat(body_size_multiple as usize))
59+
Bytes::from("hello world\n".repeat(body_size_multiple as usize))
5960
}
6061

6162
#[tokio::main]
@@ -139,15 +140,16 @@ fn init_otel_resource() -> Resource {
139140
// slow requests to show up on the histograms without completely blocking up the server.
140141
const PCT_SLOW_REQUESTS: u64 = 5;
141142
const MAX_SLOW_REQUEST_SEC: u64 = 16;
142-
const MAX_BODY_SIZE_MULTIPLE: u64 = 128;
143+
// MAX_BODY_SIZE_MULTIPLE is used to demonstrate the `http.server.response.body.size` histogram
144+
const MAX_BODY_SIZE_MULTIPLE: u64 = 16;
143145

144146
async fn handle(_req: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
145147
if rand_09::random_range(0..100) < PCT_SLOW_REQUESTS {
146148
let slow_request_secs = rand_09::random_range(0..=MAX_SLOW_REQUEST_SEC);
147149
tokio::time::sleep(Duration::from_secs(slow_request_secs)).await;
148150
};
149151
let body_size_multiple = rand_09::random_range(0..=MAX_BODY_SIZE_MULTIPLE);
150-
let body = Bytes::from("{'msg': 'hello world'}".repeat(body_size_multiple as usize));
152+
let body = Bytes::from("hello world\n".repeat(body_size_multiple as usize));
151153
Ok(Response::new(Full::new(body)))
152154
}
153155

examples/axum-http-service/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ fn init_otel_resource() -> Resource {
2828
// slow requests to show up on the histograms without completely blocking up the server.
2929
const PCT_SLOW_REQUESTS: u64 = 5;
3030
const MAX_SLOW_REQUEST_SEC: u64 = 16;
31-
const MAX_BODY_SIZE_MULTIPLE: u64 = 128;
31+
// MAX_BODY_SIZE_MULTIPLE is used to demonstrate the `http.server.response.body.size` histogram
32+
const MAX_BODY_SIZE_MULTIPLE: u64 = 16;
3233

3334
#[axum::debug_handler]
3435
async fn handle() -> Bytes {
@@ -37,7 +38,7 @@ async fn handle() -> Bytes {
3738
tokio::time::sleep(Duration::from_secs(slow_request_secs)).await;
3839
};
3940
let body_size_multiple = rand_09::random_range(0..=MAX_BODY_SIZE_MULTIPLE);
40-
Bytes::from("{'msg': 'hello world'}".repeat(body_size_multiple as usize))
41+
Bytes::from("hello world\n".repeat(body_size_multiple as usize))
4142
}
4243

4344
#[tokio::main]

examples/tower-http-service/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,16 @@ fn init_otel_resource() -> Resource {
3333
// slow requests to show up on the histograms without completely blocking up the server.
3434
const PCT_SLOW_REQUESTS: u64 = 5;
3535
const MAX_SLOW_REQUEST_SEC: u64 = 16;
36-
const MAX_BODY_SIZE_MULTIPLE: u64 = 128;
36+
// MAX_BODY_SIZE_MULTIPLE is used to demonstrate the `http.server.response.body.size` histogram
37+
const MAX_BODY_SIZE_MULTIPLE: u64 = 16;
3738

3839
async fn handle(_req: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
3940
if rand_09::random_range(0..100) < PCT_SLOW_REQUESTS {
4041
let slow_request_secs = rand_09::random_range(0..=MAX_SLOW_REQUEST_SEC);
4142
tokio::time::sleep(Duration::from_secs(slow_request_secs)).await;
4243
};
4344
let body_size_multiple = rand_09::random_range(0..=MAX_BODY_SIZE_MULTIPLE);
44-
let body = Bytes::from("{'msg': 'hello world'}".repeat(body_size_multiple as usize));
45+
let body = Bytes::from("hello world\n".repeat(body_size_multiple as usize));
4546
Ok(Response::new(Full::new(body)))
4647
}
4748

src/lib.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const HTTP_SERVER_ACTIVE_REQUESTS_UNIT: &str = "{request}";
4343
const HTTP_SERVER_REQUEST_BODY_SIZE_METRIC: &str = "http.server.request.body.size";
4444
const HTTP_SERVER_REQUEST_BODY_SIZE_UNIT: &str = "By";
4545

46+
const HTTP_SERVER_RESPONSE_BODY_SIZE_METRIC: &str = "http.server.response.body.size";
47+
const HTTP_SERVER_RESPONSE_BODY_SIZE_UNIT: &str = "By";
48+
4649
const NETWORK_PROTOCOL_NAME_LABEL: &str = "network.protocol.name";
4750
const NETWORK_PROTOCOL_VERSION_LABEL: &str = "network.protocol.version";
4851
const URL_SCHEME_LABEL: &str = "url.scheme";
@@ -61,6 +64,7 @@ struct HTTPMetricsLayerState {
6164
pub server_request_duration: Histogram<f64>,
6265
pub server_active_requests: UpDownCounter<i64>,
6366
pub server_request_body_size: Histogram<u64>,
67+
pub server_response_body_size: Histogram<u64>,
6468
}
6569

6670
#[derive(Clone)]
@@ -150,6 +154,11 @@ impl HTTPMetricsLayerBuilder {
150154
.with_description("Size of HTTP server request bodies.")
151155
.with_unit(HTTP_SERVER_REQUEST_BODY_SIZE_UNIT)
152156
.build(),
157+
server_response_body_size: meter
158+
.u64_histogram(HTTP_SERVER_RESPONSE_BODY_SIZE_METRIC)
159+
.with_description("Size of HTTP server response bodies.")
160+
.with_unit(HTTP_SERVER_RESPONSE_BODY_SIZE_UNIT)
161+
.build(),
153162
}
154163
}
155164
}
@@ -177,7 +186,7 @@ struct ResponseFutureMetricsState {
177186
// https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#metric-httpserverrequestduration
178187
duration_start: Instant,
179188
// https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#metric-httpserverrequestbodysize
180-
body_size: Option<u64>,
189+
req_body_size: Option<u64>,
181190

182191
// fields for metric labels
183192
protocol_name_kv: KeyValue,
@@ -197,7 +206,7 @@ pin_project! {
197206
}
198207
}
199208

200-
impl<S, ReqBody, ResBody> Service<http::Request<ReqBody>> for HTTPMetricsService<S>
209+
impl<S, ReqBody, ResBody: http_body::Body> Service<http::Request<ReqBody>> for HTTPMetricsService<S>
201210
where
202211
S: Service<http::Request<ReqBody>, Response = http::Response<ResBody>>,
203212
{
@@ -246,7 +255,7 @@ where
246255
layer_state: self.state.clone(),
247256
metrics_state: ResponseFutureMetricsState {
248257
duration_start,
249-
body_size: content_length,
258+
req_body_size: content_length,
250259

251260
protocol_name_kv,
252261
protocol_version_kv,
@@ -258,7 +267,7 @@ where
258267
}
259268
}
260269

261-
impl<F, ResBody, E> Future for HTTPMetricsResponseFuture<F>
270+
impl<F, ResBody: http_body::Body, E> Future for HTTPMetricsResponseFuture<F>
262271
where
263272
F: Future<Output = result::Result<http::Response<ResBody>, E>>,
264273
{
@@ -287,10 +296,17 @@ where
287296
&label_superset,
288297
);
289298

290-
if let Some(content_length) = this.metrics_state.body_size {
299+
if let Some(req_content_length) = this.metrics_state.req_body_size {
291300
this.layer_state
292301
.server_request_body_size
293-
.record(content_length, &label_superset);
302+
.record(req_content_length, &label_superset);
303+
}
304+
305+
// use same approach for `http.server.response.body.size` as hyper does to set content-length
306+
if let Some(resp_content_length) = response.body().size_hint().exact() {
307+
this.layer_state
308+
.server_response_body_size
309+
.record(resp_content_length, &label_superset);
294310
}
295311

296312
this.layer_state.server_active_requests.add(

0 commit comments

Comments
 (0)