use hyper::{
header::CONTENT_TYPE,
service::{make_service_fn, service_fn},
Body, Method, Request, Response, Server,
};
use once_cell::sync::Lazy;
use opentelemetry::{
metrics::{Counter, Histogram, MeterProvider as _, Unit},
KeyValue,
};
use opentelemetry_sdk::metrics::SdkMeterProvider;
use prometheus::{Encoder, Registry, TextEncoder};
use std::convert::Infallible;
use std::sync::Arc;
use std::time::SystemTime;
static HANDLER_ALL: Lazy<[KeyValue; 1]> = Lazy::new(|| [KeyValue::new("handler", "all")]);
async fn serve_req(
req: Request
,
state: Arc,
) -> Result, hyper::Error> {
println!("Receiving request at path {}", req.uri());
let request_start = SystemTime::now();
state.http_counter.add(1, HANDLER_ALL.as_ref());
let response = match (req.method(), req.uri().path()) {
(&Method::GET, "/metrics") => {
let mut buffer = vec![];
let encoder = TextEncoder::new();
let metric_families = state.registry.gather();
encoder.encode(&metric_families, &mut buffer).unwrap();
state
.http_body_gauge
.record(buffer.len() as u64, HANDLER_ALL.as_ref());
Response::builder()
.status(200)
.header(CONTENT_TYPE, encoder.format_type())
.body(Body::from(buffer))
.unwrap()
}
(&Method::GET, "/") => Response::builder()
.status(200)
.body(Body::from("Hello World"))
.unwrap(),
_ => Response::builder()
.status(404)
.body(Body::from("Missing Page"))
.unwrap(),
};
state.http_req_histogram.record(
request_start.elapsed().map_or(0.0, |d| d.as_secs_f64()),
&[],
);
Ok(response)
}
struct AppState {
registry: Registry,
http_counter: Counter,
http_body_gauge: Histogram,
http_req_histogram: Histogram,
}
#[tokio::main]
pub async fn main() -> Result<(), Box> {
let registry = Registry::new();
let exporter = opentelemetry_prometheus::exporter()
.with_registry(registry.clone())
.build()?;
let provider = SdkMeterProvider::builder().with_reader(exporter).build();
let meter = provider.meter("hyper-example");
let state = Arc::new(AppState {
registry,
http_counter: meter
.u64_counter("http_requests_total")
.with_description("Total number of HTTP requests made.")
.init(),
http_body_gauge: meter
.u64_histogram("example.http_response_size")
.with_unit(Unit::new("By"))
.with_description("The metrics HTTP response sizes in bytes.")
.init(),
http_req_histogram: meter
.f64_histogram("example.http_request_duration")
.with_unit(Unit::new("ms"))
.with_description("The HTTP request latencies in milliseconds.")
.init(),
});
// For every connection, we must make a `Service` to handle all
// incoming HTTP requests on said connection.
let make_svc = make_service_fn(move |_conn| {
let state = state.clone();
// This is the `Service` that will handle the connection.
// `service_fn` is a helper to convert a function that
// returns a Response into a `Service`.
async move { Ok::<_, Infallible>(service_fn(move |req| serve_req(req, state.clone()))) }
});
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr).serve(make_svc);
println!("Listening on http://{addr}");
server.await?;
Ok(())
}