feat(proxy): add request IDs, tracing spans, and observability hooks
Generate UUID v4 request IDs per request, create manual tracing spans (Pingora types don't impl Debug), record Prometheus metrics for detection decisions and request totals, and forward X-Request-Id to both upstream requests and downstream responses. Signed-off-by: Sienna Meridian Satterwhite <sienna@sunbeam.pt>
This commit is contained in:
205
Cargo.lock
generated
205
Cargo.lock
generated
@@ -1038,6 +1038,12 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1176,10 +1182,23 @@ checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi 5.3.0",
|
||||||
"wasip2",
|
"wasip2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi 6.0.0",
|
||||||
|
"wasip2",
|
||||||
|
"wasip3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getset"
|
name = "getset"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@@ -1252,6 +1271,15 @@ version = "0.12.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"foldhash 0.1.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
@@ -1260,7 +1288,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"foldhash",
|
"foldhash 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1501,6 +1529,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -1546,6 +1580,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1794,6 +1830,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128fmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.183"
|
version = "0.2.183"
|
||||||
@@ -2697,6 +2739,16 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -2824,6 +2876,12 @@ version = "5.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -3459,6 +3517,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-opentelemetry",
|
"tracing-opentelemetry",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3976,6 +4035,12 @@ version = "1.0.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-libyaml"
|
name = "unsafe-libyaml"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@@ -4012,6 +4077,17 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.4.2",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -4058,6 +4134,15 @@ dependencies = [
|
|||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip3"
|
||||||
|
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.114"
|
version = "0.2.114"
|
||||||
@@ -4117,6 +4202,40 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||||
|
dependencies = [
|
||||||
|
"leb128fmt",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-metadata"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.91"
|
version = "0.3.91"
|
||||||
@@ -4331,6 +4450,88 @@ name = "wit-bindgen"
|
|||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rust-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-core"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck 0.5.0",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"heck 0.5.0",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
"prettyplease",
|
||||||
|
"syn 2.0.117",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-component",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rust-macro"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
"wit-bindgen-core",
|
||||||
|
"wit-bindgen-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-component"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-encoder",
|
||||||
|
"wasm-metadata",
|
||||||
|
"wasmparser",
|
||||||
|
"wit-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-parser"
|
||||||
|
version = "0.244.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
"log",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"unicode-xid",
|
||||||
|
"wasmparser",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ dns-lookup = "2"
|
|||||||
# Prometheus metrics
|
# Prometheus metrics
|
||||||
prometheus = "0.13"
|
prometheus = "0.13"
|
||||||
|
|
||||||
|
# Request IDs
|
||||||
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
|
||||||
# Rustls crypto provider — must be installed before any TLS init
|
# Rustls crypto provider — must be installed before any TLS init
|
||||||
rustls = { version = "0.23", features = ["aws-lc-rs"] }
|
rustls = { version = "0.23", features = ["aws-lc-rs"] }
|
||||||
|
|
||||||
|
|||||||
81
src/proxy.rs
81
src/proxy.rs
@@ -2,6 +2,7 @@ use crate::acme::AcmeRoutes;
|
|||||||
use crate::config::RouteConfig;
|
use crate::config::RouteConfig;
|
||||||
use crate::ddos::detector::DDoSDetector;
|
use crate::ddos::detector::DDoSDetector;
|
||||||
use crate::ddos::model::DDoSAction;
|
use crate::ddos::model::DDoSAction;
|
||||||
|
use crate::metrics;
|
||||||
use crate::rate_limit::key;
|
use crate::rate_limit::key;
|
||||||
use crate::rate_limit::limiter::{RateLimitResult, RateLimiter};
|
use crate::rate_limit::limiter::{RateLimitResult, RateLimiter};
|
||||||
use crate::scanner::allowlist::BotAllowlist;
|
use crate::scanner::allowlist::BotAllowlist;
|
||||||
@@ -20,7 +21,6 @@ use std::time::Instant;
|
|||||||
pub struct SunbeamProxy {
|
pub struct SunbeamProxy {
|
||||||
pub routes: Vec<RouteConfig>,
|
pub routes: Vec<RouteConfig>,
|
||||||
/// Per-challenge route table populated by the Ingress watcher.
|
/// Per-challenge route table populated by the Ingress watcher.
|
||||||
/// Maps `/.well-known/acme-challenge/<token>` → solver service address.
|
|
||||||
pub acme_routes: AcmeRoutes,
|
pub acme_routes: AcmeRoutes,
|
||||||
/// Optional KNN-based DDoS detector.
|
/// Optional KNN-based DDoS detector.
|
||||||
pub ddos_detector: Option<Arc<DDoSDetector>>,
|
pub ddos_detector: Option<Arc<DDoSDetector>>,
|
||||||
@@ -35,6 +35,10 @@ pub struct SunbeamProxy {
|
|||||||
pub struct RequestCtx {
|
pub struct RequestCtx {
|
||||||
pub route: Option<RouteConfig>,
|
pub route: Option<RouteConfig>,
|
||||||
pub start_time: Instant,
|
pub start_time: Instant,
|
||||||
|
/// Unique request identifier (UUID v4).
|
||||||
|
pub request_id: String,
|
||||||
|
/// Tracing span for this request.
|
||||||
|
pub span: tracing::Span,
|
||||||
/// Resolved solver backend address for this ACME challenge, if applicable.
|
/// Resolved solver backend address for this ACME challenge, if applicable.
|
||||||
pub acme_backend: Option<String>,
|
pub acme_backend: Option<String>,
|
||||||
/// Path prefix to strip before forwarding to the upstream (e.g. "/kratos").
|
/// Path prefix to strip before forwarding to the upstream (e.g. "/kratos").
|
||||||
@@ -91,7 +95,7 @@ fn extract_client_ip(session: &Session) -> Option<IpAddr> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Strip the scheme prefix from a backend URL like `http://host:port`.
|
/// Strip the scheme prefix from a backend URL like `http://host:port`.
|
||||||
fn backend_addr(backend: &str) -> &str {
|
pub fn backend_addr(backend: &str) -> &str {
|
||||||
backend
|
backend
|
||||||
.trim_start_matches("https://")
|
.trim_start_matches("https://")
|
||||||
.trim_start_matches("http://")
|
.trim_start_matches("http://")
|
||||||
@@ -110,9 +114,12 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
type CTX = RequestCtx;
|
type CTX = RequestCtx;
|
||||||
|
|
||||||
fn new_ctx(&self) -> RequestCtx {
|
fn new_ctx(&self) -> RequestCtx {
|
||||||
|
let request_id = uuid::Uuid::new_v4().to_string();
|
||||||
RequestCtx {
|
RequestCtx {
|
||||||
route: None,
|
route: None,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
|
request_id,
|
||||||
|
span: tracing::Span::none(),
|
||||||
acme_backend: None,
|
acme_backend: None,
|
||||||
downstream_scheme: "https",
|
downstream_scheme: "https",
|
||||||
strip_prefix: None,
|
strip_prefix: None,
|
||||||
@@ -130,6 +137,19 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
{
|
{
|
||||||
ctx.downstream_scheme = if is_plain_http(session) { "http" } else { "https" };
|
ctx.downstream_scheme = if is_plain_http(session) { "http" } else { "https" };
|
||||||
|
|
||||||
|
// Create the request-scoped tracing span.
|
||||||
|
let method = session.req_header().method.to_string();
|
||||||
|
let host = extract_host(session);
|
||||||
|
let path = session.req_header().uri.path().to_string();
|
||||||
|
ctx.span = tracing::info_span!("request",
|
||||||
|
request_id = %ctx.request_id,
|
||||||
|
method = %method,
|
||||||
|
host = %host,
|
||||||
|
path = %path,
|
||||||
|
);
|
||||||
|
|
||||||
|
metrics::ACTIVE_CONNECTIONS.inc();
|
||||||
|
|
||||||
if is_plain_http(session) {
|
if is_plain_http(session) {
|
||||||
let path = session.req_header().uri.path().to_string();
|
let path = session.req_header().uri.path().to_string();
|
||||||
|
|
||||||
@@ -157,7 +177,6 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// All other plain-HTTP traffic.
|
// All other plain-HTTP traffic.
|
||||||
let host = extract_host(session);
|
|
||||||
let prefix = host.split('.').next().unwrap_or("");
|
let prefix = host.split('.').next().unwrap_or("");
|
||||||
|
|
||||||
// Routes that explicitly opt out of HTTPS enforcement pass through.
|
// Routes that explicitly opt out of HTTPS enforcement pass through.
|
||||||
@@ -242,6 +261,8 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
"pipeline"
|
"pipeline"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
metrics::DDOS_DECISIONS.with_label_values(&[decision]).inc();
|
||||||
|
|
||||||
if matches!(ddos_action, DDoSAction::Block) {
|
if matches!(ddos_action, DDoSAction::Block) {
|
||||||
let mut resp = ResponseHeader::build(429, None)?;
|
let mut resp = ResponseHeader::build(429, None)?;
|
||||||
resp.insert_header("Retry-After", "60")?;
|
resp.insert_header("Retry-After", "60")?;
|
||||||
@@ -325,6 +346,10 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
"pipeline"
|
"pipeline"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
metrics::SCANNER_DECISIONS
|
||||||
|
.with_label_values(&[decision, &reason])
|
||||||
|
.inc();
|
||||||
|
|
||||||
if decision == "block" {
|
if decision == "block" {
|
||||||
let mut resp = ResponseHeader::build(403, None)?;
|
let mut resp = ResponseHeader::build(403, None)?;
|
||||||
resp.insert_header("Content-Length", "0")?;
|
resp.insert_header("Content-Length", "0")?;
|
||||||
@@ -363,6 +388,10 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
"pipeline"
|
"pipeline"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
metrics::RATE_LIMIT_DECISIONS
|
||||||
|
.with_label_values(&[decision])
|
||||||
|
.inc();
|
||||||
|
|
||||||
if let RateLimitResult::Reject { retry_after } = rl_result {
|
if let RateLimitResult::Reject { retry_after } = rl_result {
|
||||||
let mut resp = ResponseHeader::build(429, None)?;
|
let mut resp = ResponseHeader::build(429, None)?;
|
||||||
resp.insert_header("Retry-After", retry_after.to_string())?;
|
resp.insert_header("Retry-After", retry_after.to_string())?;
|
||||||
@@ -491,6 +520,15 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Forward X-Request-Id to upstream.
|
||||||
|
upstream_req.insert_header("x-request-id", &ctx.request_id).map_err(|e| {
|
||||||
|
pingora_core::Error::because(
|
||||||
|
pingora_core::ErrorType::InternalError,
|
||||||
|
"failed to insert x-request-id",
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
if ctx.route.as_ref().map(|r| r.websocket).unwrap_or(false) {
|
if ctx.route.as_ref().map(|r| r.websocket).unwrap_or(false) {
|
||||||
for name in &[CONNECTION, UPGRADE] {
|
for name in &[CONNECTION, UPGRADE] {
|
||||||
if let Some(val) = session.req_header().headers.get(name.clone()) {
|
if let Some(val) = session.req_header().headers.get(name.clone()) {
|
||||||
@@ -535,6 +573,20 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add X-Request-Id response header so clients can correlate.
|
||||||
|
async fn upstream_response_filter(
|
||||||
|
&self,
|
||||||
|
_session: &mut Session,
|
||||||
|
upstream_response: &mut ResponseHeader,
|
||||||
|
ctx: &mut RequestCtx,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
Self::CTX: Send + Sync,
|
||||||
|
{
|
||||||
|
let _ = upstream_response.insert_header("x-request-id", &ctx.request_id);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Emit a structured JSON audit log line for every request.
|
/// Emit a structured JSON audit log line for every request.
|
||||||
async fn logging(
|
async fn logging(
|
||||||
&self,
|
&self,
|
||||||
@@ -544,10 +596,15 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
) where
|
) where
|
||||||
Self::CTX: Send + Sync,
|
Self::CTX: Send + Sync,
|
||||||
{
|
{
|
||||||
|
metrics::ACTIVE_CONNECTIONS.dec();
|
||||||
|
|
||||||
let status = session
|
let status = session
|
||||||
.response_written()
|
.response_written()
|
||||||
.map_or(0, |r| r.status.as_u16());
|
.map_or(0, |r| r.status.as_u16());
|
||||||
let duration_ms = ctx.start_time.elapsed().as_millis() as u64;
|
let duration_ms = ctx.start_time.elapsed().as_millis() as u64;
|
||||||
|
let duration_secs = ctx.start_time.elapsed().as_secs_f64();
|
||||||
|
let method_str = session.req_header().method.to_string();
|
||||||
|
let host = extract_host(session);
|
||||||
let backend = ctx
|
let backend = ctx
|
||||||
.route
|
.route
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -563,6 +620,12 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
});
|
});
|
||||||
let error_str = error.map(|e| e.to_string());
|
let error_str = error.map(|e| e.to_string());
|
||||||
|
|
||||||
|
// Record Prometheus metrics.
|
||||||
|
metrics::REQUESTS_TOTAL
|
||||||
|
.with_label_values(&[&method_str, &host, &status.to_string(), backend])
|
||||||
|
.inc();
|
||||||
|
metrics::REQUEST_DURATION.observe(duration_secs);
|
||||||
|
|
||||||
let content_length: u64 = session
|
let content_length: u64 = session
|
||||||
.req_header()
|
.req_header()
|
||||||
.headers
|
.headers
|
||||||
@@ -609,8 +672,9 @@ impl ProxyHttp for SunbeamProxy {
|
|||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
target = "audit",
|
target = "audit",
|
||||||
|
request_id = %ctx.request_id,
|
||||||
method = %session.req_header().method,
|
method = %session.req_header().method,
|
||||||
host = %extract_host(session),
|
host = %host,
|
||||||
path = %session.req_header().uri.path(),
|
path = %session.req_header().uri.path(),
|
||||||
query,
|
query,
|
||||||
client_ip,
|
client_ip,
|
||||||
@@ -678,6 +742,8 @@ mod tests {
|
|||||||
let ctx = RequestCtx {
|
let ctx = RequestCtx {
|
||||||
route: None,
|
route: None,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
|
request_id: "1".to_string(),
|
||||||
|
span: tracing::Span::none(),
|
||||||
acme_backend: None,
|
acme_backend: None,
|
||||||
strip_prefix: None,
|
strip_prefix: None,
|
||||||
downstream_scheme: "https",
|
downstream_scheme: "https",
|
||||||
@@ -705,4 +771,11 @@ mod tests {
|
|||||||
// Content-Length must survive the strip.
|
// Content-Length must survive the strip.
|
||||||
assert!(req.headers.get("content-length").is_some());
|
assert!(req.headers.get("content-length").is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_request_id_is_uuid_v4() {
|
||||||
|
let id = uuid::Uuid::new_v4().to_string();
|
||||||
|
assert_eq!(id.len(), 36);
|
||||||
|
assert!(uuid::Uuid::parse_str(&id).is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user