use crate::rt::{MaybeSend, MaybeSync};
pub mod pressure;
pub const METRIC_REQUESTS: &str = "mkit_server_requests_total";
pub const METRIC_LATENCY: &str = "mkit_server_request_duration_ms";
pub const METRIC_INSPECTION_CALLS: &str = "mkit_server_inspection_calls_total";
pub const METRIC_UPLOAD_BYTES: &str = "mkit_server_upload_bytes_total";
pub const METRIC_RELAY_LEASE_LAG: &str = "mkit_server_relay_lease_lag_total";
pub const METRIC_RELAY_LAG_EXCEEDED: &str = "mkit_server_relay_lag_exceeded_total";
pub const METRIC_RELAY_BACKLOG_ROWS: &str = "mkit_server_relay_backlog_rows";
pub const METRIC_INDEX_SLICE_SUBREQUESTS: &str = "mkit_server_index_slice_subrequests";
pub const METRIC_INDEX_LOOKUP_CAPPED: &str = "mkit_server_index_lookup_capped_total";
pub const METRIC_REF_POLICY_ANCESTRY_UNCHECKED: &str =
"mkit_server_ref_policy_ancestry_unchecked_total";
pub const METRIC_INDEX_REJECTED_WRITE_FAILED: &str =
"mkit_server_index_rejected_write_failed_total";
pub const METRIC_NAMESPACE_QUOTA_VIEW_FALLBACK: &str =
"mkit_server_namespace_quota_view_fallback_total";
pub const METRIC_NAMESPACE_QUOTA_REBASE: &str = "mkit_server_namespace_quota_rebase_total";
pub const METRIC_NAMESPACE_QUOTA_ROLLUP_ERROR: &str =
"mkit_server_namespace_quota_rollup_error_total";
pub const NEVER_ECHO: &[&str] = &[
"authorization",
"proxy-authorization",
"cookie",
"payment-authorization",
"payment-signature",
];
pub const NEVER_LOG: &[&str] = &[
"authorization",
"proxy-authorization",
"cookie",
"payment-authorization",
"payment-signature",
"payment-receipt",
"payment-response",
"x-signature",
"set-cookie",
];
#[must_use]
pub fn is_never_echo(name: &str) -> bool {
NEVER_ECHO.iter().any(|n| n.eq_ignore_ascii_case(name))
}
#[must_use]
pub fn is_never_log(name: &str) -> bool {
NEVER_LOG.iter().any(|n| n.eq_ignore_ascii_case(name))
}
pub const REDACTED_VALUE: &str = "[redacted]";
#[derive(Debug, Clone, Default)]
pub struct Redactor {
extra: Vec<String>,
}
impl Redactor {
pub fn add_names(&mut self, names: &[String]) {
self.extra.extend(names.iter().cloned());
}
#[must_use]
pub fn new<I, S>(extra: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
extra: extra.into_iter().map(Into::into).collect(),
}
}
#[must_use]
pub fn redacts(&self, name: &str) -> bool {
is_never_log(name) || self.extra.iter().any(|n| n.eq_ignore_ascii_case(name))
}
#[must_use]
pub fn loggable<'a>(&self, name: &str, value: &'a str) -> &'a str {
if self.redacts(name) {
REDACTED_VALUE
} else {
value
}
}
}
pub trait Metrics: MaybeSend + MaybeSync {
fn incr(&self, name: &'static str, labels: &[(&'static str, &str)], by: u64);
fn observe_ms(&self, name: &'static str, labels: &[(&'static str, &str)], ms: f64);
fn gauge(&self, _name: &'static str, _labels: &[(&'static str, &str)], _value: f64) {}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoopMetrics;
impl Metrics for NoopMetrics {
fn incr(&self, _name: &'static str, _labels: &[(&'static str, &str)], _by: u64) {}
fn observe_ms(&self, _name: &'static str, _labels: &[(&'static str, &str)], _ms: f64) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sensitive_headers_case_insensitive() {
for name in NEVER_ECHO {
assert!(is_never_echo(name), "{name}");
assert!(is_never_echo(&name.to_ascii_uppercase()), "{name}");
assert!(NEVER_LOG.contains(name), "{name} must also be NEVER_LOG");
}
for name in NEVER_LOG {
assert!(is_never_log(name), "{name}");
assert!(is_never_log(&name.to_ascii_uppercase()), "{name}");
}
assert!(is_never_echo("Authorization"));
assert!(is_never_echo("PAYMENT-SIGNATURE"));
for receipt in ["Payment-Receipt", "PAYMENT-RESPONSE"] {
assert!(!is_never_echo(receipt), "{receipt}");
assert!(is_never_log(receipt), "{receipt}");
}
assert!(is_never_log("X-Signature") && is_never_log("Set-Cookie"));
assert!(!is_never_log("WWW-Authenticate"));
assert!(!is_never_log("authorization-hint"));
assert!(!is_never_echo(""));
}
#[test]
fn redactor_extends_never_log() {
let base = Redactor::default();
assert!(base.redacts("payment-receipt"));
assert!(!base.redacts("X-Admission-Token"));
assert_eq!(base.loggable("WWW-Authenticate", "Payment x"), "Payment x");
let configured = Redactor::new(["x-admission-token"]);
assert!(configured.redacts("X-Admission-Token"));
assert!(configured.redacts("Cookie"));
assert_eq!(
configured.loggable("X-ADMISSION-TOKEN", "s3cr3t"),
REDACTED_VALUE
);
}
#[test]
fn noop_metrics_is_object_safe() {
let sink: &dyn Metrics = &NoopMetrics;
sink.incr(
METRIC_REQUESTS,
&[("procedure", "UpdateRef"), ("code", "ok")],
1,
);
sink.observe_ms(METRIC_LATENCY, &[("procedure", "UpdateRef")], 1.5);
}
}