{"id":17951813,"url":"https://github.com/francoposa/tower-otel-http-metrics","last_synced_at":"2025-10-30T06:41:27.246Z","repository":{"id":209244516,"uuid":"723559158","full_name":"francoposa/tower-otel-http-metrics","owner":"francoposa","description":"OpenTelemetry HTTP Metrics Middleware Layer for Tower-Compatible Servers (Axum, Hyper)","archived":false,"fork":false,"pushed_at":"2025-03-08T18:20:11.000Z","size":115,"stargazers_count":13,"open_issues_count":1,"forks_count":4,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-03-19T05:33:39.173Z","etag":null,"topics":["axum","axum-middleware","hacktoberfest","hacktoberfest2024","http","hyper","metrics","middleware","opentelemetry","opentelemetry-rust","tower","tower-middleware"],"latest_commit_sha":null,"homepage":"https://docs.rs/tower-otel-http-metrics/latest/tower_otel_http_metrics","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/francoposa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-26T03:31:55.000Z","updated_at":"2025-03-08T18:20:15.000Z","dependencies_parsed_at":"2023-12-12T23:29:54.435Z","dependency_job_id":"55f7e322-1084-42c6-8814-49bc1e6ca336","html_url":"https://github.com/francoposa/tower-otel-http-metrics","commit_stats":null,"previous_names":["francoposa/tower-otel-http-metrics"],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francoposa%2Ftower-otel-http-metrics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francoposa%2Ftower-otel-http-metrics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francoposa%2Ftower-otel-http-metrics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/francoposa%2Ftower-otel-http-metrics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/francoposa","download_url":"https://codeload.github.com/francoposa/tower-otel-http-metrics/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245377920,"owners_count":20605374,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["axum","axum-middleware","hacktoberfest","hacktoberfest2024","http","hyper","metrics","middleware","opentelemetry","opentelemetry-rust","tower","tower-middleware"],"created_at":"2024-10-29T09:51:18.526Z","updated_at":"2025-10-30T06:41:27.240Z","avatar_url":"https://github.com/francoposa.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n\n\u003e [!IMPORTANT]  \n\u003e **Read-Only Mode**\n\u003e This code has been moved to [openetelemetry-rust-contrib](https://github.com/open-telemetry/opentelemetry-rust-contrib)\nin the `opentelemetry-instrumentation-tower` package.\n\u003e It has not yet been published to crates.io from the new location\n\n---\n# Tower OTEL Metrics Middleware\n\nOpenTelemetry Metrics Middleware for Tower-compatible Rust HTTP servers.\n\n## Examples\n\nSee `examples` directory in repo for runnable code and supporting config files.\nAttempts are made to keep the code here synced, but it will not be perfect.\n\nOTEL libraries in particular are sensitive to minor version changes at this point,\nso the examples may only work with the OTEL crate versions pinned in `examples`.\n\n### Axum Server\n\nAdding OpenTelementry HTTP Server metrics using the [`Axum`](https://docs.rs/axum/latest/axum) framework\nover a Tower-compatible [`Hyper`](https://docs.rs/hyper/latest/hyper) Service:\n\n```rust\nuse axum::routing::{get, post, put, Router};\nuse bytes::Bytes;\nuse opentelemetry::global;\nuse opentelemetry_otlp::{\n    WithExportConfig, {self},\n};\nuse opentelemetry_sdk::metrics::PeriodicReader;\nuse opentelemetry_sdk::Resource;\nuse std::time::Duration;\nuse tower_otel_http_metrics;\n\nconst SERVICE_NAME: \u0026str = \"example-axum-http-service\";\n// Metric export interval should be less than or equal to 15s\n// if the metrics may be converted to Prometheus metrics.\n// Prometheus' query engine and compatible implementations\n// require ~4 data points / interval for range queries,\n// so queries ranging over 1m requre \u003c= 15s scrape intervals.\n// OTEL SDKS also respect the env var `OTEL_METRIC_EXPORT_INTERVAL` (no underscore prefix).\nconst _OTEL_METRIC_EXPORT_INTERVAL: Duration = Duration::from_secs(10);\n\nfn init_otel_resource() -\u003e Resource {\n    Resource::builder().with_service_name(SERVICE_NAME).build()\n}\n\n// PCT_SLOW_REQUESTS and MAX_SLOW_REQUEST_SEC are used to inject latency into some responses\n// in order to utilize the higher request duration buckets in the request duration histogram.\n// These values are chosen so that with the load-gen script's max 100 VUs, we get just enough\n// slow requests to show up on the histograms without completely blocking up the server.\nconst PCT_SLOW_REQUESTS: u64 = 5;\nconst MAX_SLOW_REQUEST_SEC: u64 = 16;\n// MAX_BODY_SIZE_MULTIPLE is used to demonstrate the `http.server.response.body.size` histogram\nconst MAX_BODY_SIZE_MULTIPLE: u64 = 16;\n\n#[axum::debug_handler]\nasync fn handle() -\u003e Bytes {\n    if rand_09::random_range(0..100) \u003c PCT_SLOW_REQUESTS {\n        let slow_request_secs = rand_09::random_range(0..=MAX_SLOW_REQUEST_SEC);\n        tokio::time::sleep(Duration::from_secs(slow_request_secs)).await;\n    };\n    let body_size_multiple = rand_09::random_range(0..=MAX_BODY_SIZE_MULTIPLE);\n    Bytes::from(\"hello world\\n\".repeat(body_size_multiple as usize))\n}\n\n#[tokio::main]\nasync fn main() {\n    let exporter = opentelemetry_otlp::MetricExporter::builder()\n        .with_tonic()\n        .with_endpoint(\"http://localhost:4317\")\n        .build()\n        .unwrap();\n\n    let reader = PeriodicReader::builder(exporter)\n        .with_interval(_OTEL_METRIC_EXPORT_INTERVAL)\n        .build();\n\n    let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()\n        .with_reader(reader)\n        .with_resource(init_otel_resource())\n        .build();\n\n    global::set_meter_provider(meter_provider);\n    // init our otel metrics middleware\n    let global_meter = global::meter(SERVICE_NAME);\n    let otel_metrics_service_layer = tower_otel_http_metrics::HTTPMetricsLayerBuilder::builder()\n        .with_meter(global_meter)\n        .build()\n        .unwrap();\n\n    let app = Router::new()\n        .route(\"/\", get(handle))\n        .route(\"/\", post(handle))\n        .route(\"/\", put(handle))\n        .layer(otel_metrics_service_layer);\n\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:5000\").await.unwrap();\n    let server = axum::serve(listener, app);\n\n    if let Err(err) = server.await {\n        eprintln!(\"server error: {}\", err);\n    }\n}\n```\n\n### Hyper Server\n\nAdding OpenTelementry HTTP Server metrics to a bare-bones Tower-compatible Service\nusing [`Hyper`](https://docs.rs/crate/hyper/latest):\n\n```rust\nuse http_body_util::Full;\nuse hyper::body::Bytes;\nuse hyper::{Request, Response};\nuse opentelemetry::global;\nuse opentelemetry_otlp::{\n    WithExportConfig, {self},\n};\nuse opentelemetry_sdk::metrics::PeriodicReader;\nuse opentelemetry_sdk::Resource;\nuse std::convert::Infallible;\nuse std::net::SocketAddr;\nuse std::time::Duration;\nuse tokio::net::TcpListener;\nuse tower::ServiceBuilder;\nuse tower_otel_http_metrics;\n\nconst SERVICE_NAME: \u0026str = \"example-hyper-http-service\";\n// Metric export interval should be less than or equal to 15s\n// if the metrics may be converted to Prometheus metrics.\n// Prometheus' query engine and compatible implementations\n// require ~4 data points / interval for range queries,\n// so queries ranging over 1m requre \u003c= 15s scrape intervals.\n// OTEL SDKS also respect the env var `OTEL_METRIC_EXPORT_INTERVAL` (no underscore prefix).\nconst _OTEL_METRIC_EXPORT_INTERVAL: Duration = Duration::from_secs(10);\n\nfn init_otel_resource() -\u003e Resource {\n    Resource::builder().with_service_name(SERVICE_NAME).build()\n}\n\n// PCT_SLOW_REQUESTS and MAX_SLOW_REQUEST_SEC are used to inject latency into some responses\n// in order to utilize the higher request duration buckets in the request duration histogram.\n// These values are chosen so that with the load-gen script's max 100 VUs, we get just enough\n// slow requests to show up on the histograms without completely blocking up the server.\nconst PCT_SLOW_REQUESTS: u64 = 5;\nconst MAX_SLOW_REQUEST_SEC: u64 = 16;\n// MAX_BODY_SIZE_MULTIPLE is used to demonstrate the `http.server.response.body.size` histogram\nconst MAX_BODY_SIZE_MULTIPLE: u64 = 16;\n\nasync fn handle(_req: Request\u003chyper::body::Incoming\u003e) -\u003e Result\u003cResponse\u003cFull\u003cBytes\u003e\u003e, Infallible\u003e {\n    if rand_09::random_range(0..100) \u003c PCT_SLOW_REQUESTS {\n        let slow_request_secs = rand_09::random_range(0..=MAX_SLOW_REQUEST_SEC);\n        tokio::time::sleep(Duration::from_secs(slow_request_secs)).await;\n    };\n    let body_size_multiple = rand_09::random_range(0..=MAX_BODY_SIZE_MULTIPLE);\n    let body = Bytes::from(\"hello world\\n\".repeat(body_size_multiple as usize));\n    Ok(Response::new(Full::new(body)))\n}\n\n#[tokio::main]\nasync fn main() {\n    let exporter = opentelemetry_otlp::MetricExporter::builder()\n        .with_tonic()\n        .with_endpoint(\"http://localhost:4317\")\n        .build()\n        .unwrap();\n\n    let reader = PeriodicReader::builder(exporter)\n        .with_interval(_OTEL_METRIC_EXPORT_INTERVAL)\n        .build();\n\n    let meter_provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()\n        .with_reader(reader)\n        .with_resource(init_otel_resource())\n        .build();\n\n    global::set_meter_provider(meter_provider);\n    // init our otel metrics middleware\n    let global_meter = global::meter(SERVICE_NAME);\n    let otel_metrics_service_layer = tower_otel_http_metrics::HTTPMetricsLayerBuilder::builder()\n        .with_meter(global_meter)\n        .build()\n        .unwrap();\n\n    let tower_service = ServiceBuilder::new()\n        .layer(otel_metrics_service_layer)\n        .service_fn(handle);\n    let hyper_service = hyper_util::service::TowerToHyperService::new(tower_service);\n\n    let addr = SocketAddr::from(([0, 0, 0, 0], 5000));\n    let listener = TcpListener::bind(addr).await.unwrap();\n\n    loop {\n        let (stream, _) = listener.accept().await.unwrap();\n\n        let io = hyper_util::rt::TokioIo::new(stream);\n        let service_clone = hyper_service.clone();\n\n        tokio::task::spawn(async move {\n            if let Err(err) =\n                hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new())\n                    .serve_connection(io, service_clone)\n                    .await\n            {\n                eprintln!(\"server error: {}\", err);\n            }\n        });\n    }\n}\n```\n\nCreated by Franco Posa (franco @ [francoposa.io](https://francoposa.io))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrancoposa%2Ftower-otel-http-metrics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffrancoposa%2Ftower-otel-http-metrics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffrancoposa%2Ftower-otel-http-metrics/lists"}