mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-27 15:37:02 +00:00
fix(metrics): keep S3 op counts when chains are disabled (#2675)
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
@@ -20,10 +20,10 @@ use std::str::FromStr;
|
|||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
/// De-facto standard header keys.
|
/// De-facto standard header keys.
|
||||||
const X_FORWARDED_FOR: &str = "x-forwarded-for";
|
pub const X_FORWARDED_FOR: &str = "x-forwarded-for";
|
||||||
const X_FORWARDED_PROTO: &str = "x-forwarded-proto";
|
pub const X_FORWARDED_PROTO: &str = "x-forwarded-proto";
|
||||||
const X_FORWARDED_SCHEME: &str = "x-forwarded-scheme";
|
pub const X_FORWARDED_SCHEME: &str = "x-forwarded-scheme";
|
||||||
const X_REAL_IP: &str = "x-real-ip";
|
pub const X_REAL_IP: &str = "x-real-ip";
|
||||||
|
|
||||||
/// RFC7239 defines a new "Forwarded: " header designed to replace the
|
/// RFC7239 defines a new "Forwarded: " header designed to replace the
|
||||||
/// existing use of X-Forwarded-* headers.
|
/// existing use of X-Forwarded-* headers.
|
||||||
|
|||||||
+106
-15
@@ -92,15 +92,6 @@ impl OperationHelper {
|
|||||||
let audit_enabled = is_audit_module_enabled();
|
let audit_enabled = is_audit_module_enabled();
|
||||||
let notify_enabled = is_notify_module_enabled();
|
let notify_enabled = is_notify_module_enabled();
|
||||||
|
|
||||||
// Fast path: when both chains are disabled, avoid all request parsing/builder work.
|
|
||||||
if !audit_enabled && !notify_enabled {
|
|
||||||
return Self::Disabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
if audit_enabled {
|
|
||||||
counter!("rustfs.log.chain.audit.total").increment(1);
|
|
||||||
}
|
|
||||||
// Parse path -> bucket/object
|
|
||||||
let path = req.uri.path().trim_start_matches('/');
|
let path = req.uri.path().trim_start_matches('/');
|
||||||
let mut segs = path.splitn(2, '/');
|
let mut segs = path.splitn(2, '/');
|
||||||
let path_bucket = segs.next().unwrap_or("").to_string();
|
let path_bucket = segs.next().unwrap_or("").to_string();
|
||||||
@@ -110,6 +101,19 @@ impl OperationHelper {
|
|||||||
.and_then(|info| info.bucket.clone())
|
.and_then(|info| info.bucket.clone())
|
||||||
.filter(|value| !value.is_empty())
|
.filter(|value| !value.is_empty())
|
||||||
.unwrap_or(path_bucket);
|
.unwrap_or(path_bucket);
|
||||||
|
|
||||||
|
let bucket_label = if bucket.is_empty() { "*" } else { &bucket };
|
||||||
|
record_s3_op(op, bucket_label);
|
||||||
|
|
||||||
|
// Fast path: when both chains are disabled, avoid all request parsing/builder work.
|
||||||
|
if !audit_enabled && !notify_enabled {
|
||||||
|
return Self::Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if audit_enabled {
|
||||||
|
counter!("rustfs_log_chain_audit_total").increment(1);
|
||||||
|
}
|
||||||
|
// Parse path -> bucket/object
|
||||||
let object_key = req_info
|
let object_key = req_info
|
||||||
.and_then(|info| info.object.clone())
|
.and_then(|info| info.object.clone())
|
||||||
.filter(|value| !value.is_empty())
|
.filter(|value| !value.is_empty())
|
||||||
@@ -118,17 +122,14 @@ impl OperationHelper {
|
|||||||
// Infer remote address
|
// Infer remote address
|
||||||
let remote_host = req
|
let remote_host = req
|
||||||
.headers
|
.headers
|
||||||
.get("x-forwarded-for")
|
.get(rustfs_utils::http::X_FORWARDED_FOR)
|
||||||
.and_then(|v| v.to_str().ok())
|
.and_then(|v| v.to_str().ok())
|
||||||
.or_else(|| req.headers.get("x-real-ip").and_then(|v| v.to_str().ok()))
|
.or_else(|| req.headers.get(rustfs_utils::http::X_REAL_IP).and_then(|v| v.to_str().ok()))
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let trigger = op.as_str();
|
let trigger = op.as_str();
|
||||||
|
|
||||||
let bucket_label = if bucket.is_empty() { "*" } else { &bucket };
|
|
||||||
record_s3_op(op, bucket_label);
|
|
||||||
|
|
||||||
// Initialize audit builder
|
// Initialize audit builder
|
||||||
let mut api_builder = ApiDetailsBuilder::new().name(trigger);
|
let mut api_builder = ApiDetailsBuilder::new().name(trigger);
|
||||||
if !bucket.is_empty() {
|
if !bucket.is_empty() {
|
||||||
@@ -142,7 +143,7 @@ impl OperationHelper {
|
|||||||
// RequestContext.request_id > extract_request_id_from_headers() > generated fallback id
|
// RequestContext.request_id > extract_request_id_from_headers() > generated fallback id
|
||||||
let request_context = request_context_from_req(req);
|
let request_context = request_context_from_req(req);
|
||||||
if request_context.is_none() {
|
if request_context.is_none() {
|
||||||
counter!("rustfs.log.chain.orphan.total", "component" => "operation_helper").increment(1);
|
counter!("rustfs_log_chain_orphan_total", "component" => "operation_helper").increment(1);
|
||||||
}
|
}
|
||||||
let request_id = request_context
|
let request_id = request_context
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -371,8 +372,10 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::server::{refresh_audit_module_enabled, refresh_notify_module_enabled};
|
use crate::server::{refresh_audit_module_enabled, refresh_notify_module_enabled};
|
||||||
use http::{Extensions, HeaderMap, HeaderValue, Method, Uri};
|
use http::{Extensions, HeaderMap, HeaderValue, Method, Uri};
|
||||||
|
use metrics::{Counter, CounterFn, Gauge, GaugeFn, Histogram, HistogramFn, Key, KeyName, Metadata, SharedString, Unit};
|
||||||
use rustfs_credentials::Credentials;
|
use rustfs_credentials::Credentials;
|
||||||
use s3s::dto::DeleteObjectTaggingInput;
|
use s3s::dto::DeleteObjectTaggingInput;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use temp_env::with_vars;
|
use temp_env::with_vars;
|
||||||
|
|
||||||
fn build_request<T>(input: T, method: Method, uri: Uri) -> S3Request<T> {
|
fn build_request<T>(input: T, method: Method, uri: Uri) -> S3Request<T> {
|
||||||
@@ -389,6 +392,62 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
struct SeenMetricsRecorder {
|
||||||
|
counters: Arc<Mutex<Vec<Key>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SeenMetricsRecorder {
|
||||||
|
fn saw_counter_named(&self, name: &str) -> bool {
|
||||||
|
self.counters.lock().unwrap().iter().any(|key| key.name() == name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl metrics::Recorder for SeenMetricsRecorder {
|
||||||
|
fn describe_counter(&self, _key: KeyName, _unit: Option<Unit>, _description: SharedString) {}
|
||||||
|
|
||||||
|
fn describe_gauge(&self, _key: KeyName, _unit: Option<Unit>, _description: SharedString) {}
|
||||||
|
|
||||||
|
fn describe_histogram(&self, _key: KeyName, _unit: Option<Unit>, _description: SharedString) {}
|
||||||
|
|
||||||
|
fn register_counter(&self, key: &Key, _metadata: &Metadata<'_>) -> Counter {
|
||||||
|
self.counters.lock().unwrap().push(key.clone());
|
||||||
|
Counter::from_arc(Arc::new(NoopCounter))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_gauge(&self, _key: &Key, _metadata: &Metadata<'_>) -> Gauge {
|
||||||
|
Gauge::from_arc(Arc::new(NoopGauge))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_histogram(&self, _key: &Key, _metadata: &Metadata<'_>) -> Histogram {
|
||||||
|
Histogram::from_arc(Arc::new(NoopHistogram))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoopCounter;
|
||||||
|
|
||||||
|
impl CounterFn for NoopCounter {
|
||||||
|
fn increment(&self, _value: u64) {}
|
||||||
|
|
||||||
|
fn absolute(&self, _value: u64) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoopGauge;
|
||||||
|
|
||||||
|
impl GaugeFn for NoopGauge {
|
||||||
|
fn increment(&self, _value: f64) {}
|
||||||
|
|
||||||
|
fn decrement(&self, _value: f64) {}
|
||||||
|
|
||||||
|
fn set(&self, _value: f64) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoopHistogram;
|
||||||
|
|
||||||
|
impl HistogramFn for NoopHistogram {
|
||||||
|
fn record(&self, _value: f64) {}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn operation_helper_uses_req_info_for_notification_context() {
|
fn operation_helper_uses_req_info_for_notification_context() {
|
||||||
with_vars(
|
with_vars(
|
||||||
@@ -545,4 +604,36 @@ mod tests {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn operation_helper_still_records_s3_ops_when_audit_and_notify_are_disabled() {
|
||||||
|
with_vars(
|
||||||
|
[
|
||||||
|
(rustfs_config::ENV_NOTIFY_ENABLE, Some("false")),
|
||||||
|
(rustfs_config::ENV_AUDIT_ENABLE, Some("false")),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
refresh_notify_module_enabled();
|
||||||
|
refresh_audit_module_enabled();
|
||||||
|
|
||||||
|
let recorder = SeenMetricsRecorder::default();
|
||||||
|
let input = DeleteObjectTaggingInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let req = build_request(input, Method::DELETE, Uri::from_static("/test-bucket/test-key"));
|
||||||
|
|
||||||
|
metrics::with_local_recorder(&recorder, || {
|
||||||
|
let helper = OperationHelper::new(&req, EventName::ObjectAccessedGet, S3Operation::GetObject);
|
||||||
|
assert!(matches!(helper, OperationHelper::Disabled));
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
recorder.saw_counter_named("rustfs_s3_operations_total"),
|
||||||
|
"S3 operation metrics should still be recorded when audit/notify are disabled"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user