mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-23 12:49:04 +00:00
fix(obs): validate numeric env settings (#4474)
Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -33,9 +33,9 @@ use rustfs_config::{
|
||||
use rustfs_utils::get_env_bool;
|
||||
use rustfs_utils::get_env_f64;
|
||||
use rustfs_utils::get_env_opt_str;
|
||||
use rustfs_utils::get_env_opt_u64;
|
||||
use rustfs_utils::get_env_opt_usize;
|
||||
use rustfs_utils::get_env_str;
|
||||
use rustfs_utils::get_env_u64;
|
||||
use rustfs_utils::get_env_usize;
|
||||
use std::path::PathBuf;
|
||||
use tracing::{info, warn};
|
||||
|
||||
@@ -43,6 +43,18 @@ const LOG_COMPONENT_OBS: &str = "obs";
|
||||
const LOG_SUBSYSTEM_DIAL9: &str = "dial9";
|
||||
const EVENT_DIAL9_STATE: &str = "dial9_state";
|
||||
|
||||
fn sanitize_dial9_max_file_size(bytes: u64) -> u64 {
|
||||
bytes.max(1)
|
||||
}
|
||||
|
||||
fn sanitize_dial9_rotation_count(count: usize) -> usize {
|
||||
count.max(1)
|
||||
}
|
||||
|
||||
fn dial9_total_rotation_size(max_file_size: u64, rotation_count: usize) -> u64 {
|
||||
max_file_size.saturating_mul(u64::try_from(rotation_count).unwrap_or(u64::MAX))
|
||||
}
|
||||
|
||||
/// Configuration for dial9 Tokio telemetry.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dial9Config {
|
||||
@@ -95,12 +107,37 @@ impl Dial9Config {
|
||||
return Self::default();
|
||||
}
|
||||
|
||||
let raw_max_file_size = get_env_opt_u64(ENV_RUNTIME_DIAL9_MAX_FILE_SIZE);
|
||||
let raw_rotation_count = get_env_opt_usize(ENV_RUNTIME_DIAL9_ROTATION_COUNT);
|
||||
let max_file_size = sanitize_dial9_max_file_size(raw_max_file_size.unwrap_or(DEFAULT_RUNTIME_DIAL9_MAX_FILE_SIZE));
|
||||
let rotation_count = sanitize_dial9_rotation_count(raw_rotation_count.unwrap_or(DEFAULT_RUNTIME_DIAL9_ROTATION_COUNT));
|
||||
if raw_max_file_size == Some(0) {
|
||||
warn!(
|
||||
event = EVENT_DIAL9_STATE,
|
||||
component = LOG_COMPONENT_OBS,
|
||||
subsystem = LOG_SUBSYSTEM_DIAL9,
|
||||
result = "invalid_max_file_size",
|
||||
fallback_bytes = 1_u64,
|
||||
"dial9 state changed"
|
||||
);
|
||||
}
|
||||
if raw_rotation_count == Some(0) {
|
||||
warn!(
|
||||
event = EVENT_DIAL9_STATE,
|
||||
component = LOG_COMPONENT_OBS,
|
||||
subsystem = LOG_SUBSYSTEM_DIAL9,
|
||||
result = "invalid_rotation_count",
|
||||
fallback_count = 1_usize,
|
||||
"dial9 state changed"
|
||||
);
|
||||
}
|
||||
|
||||
Self {
|
||||
enabled,
|
||||
output_dir: get_env_str(ENV_RUNTIME_DIAL9_OUTPUT_DIR, DEFAULT_RUNTIME_DIAL9_OUTPUT_DIR),
|
||||
file_prefix: get_env_str(ENV_RUNTIME_DIAL9_FILE_PREFIX, DEFAULT_RUNTIME_DIAL9_FILE_PREFIX),
|
||||
max_file_size: get_env_u64(ENV_RUNTIME_DIAL9_MAX_FILE_SIZE, DEFAULT_RUNTIME_DIAL9_MAX_FILE_SIZE),
|
||||
rotation_count: get_env_usize(ENV_RUNTIME_DIAL9_ROTATION_COUNT, DEFAULT_RUNTIME_DIAL9_ROTATION_COUNT),
|
||||
max_file_size,
|
||||
rotation_count,
|
||||
s3_bucket: get_env_opt_str(ENV_RUNTIME_DIAL9_S3_BUCKET).filter(|s| !s.is_empty()),
|
||||
s3_prefix: get_env_opt_str(ENV_RUNTIME_DIAL9_S3_PREFIX).filter(|s| !s.is_empty()),
|
||||
sampling_rate: get_env_f64(ENV_RUNTIME_DIAL9_SAMPLING_RATE, DEFAULT_RUNTIME_DIAL9_SAMPLING_RATE).clamp(0.0, 1.0),
|
||||
@@ -282,8 +319,12 @@ pub fn build_traced_runtime(
|
||||
|
||||
// Create rotating writer (synchronous for runtime building)
|
||||
let base_path = config.base_path();
|
||||
let writer = RotatingWriter::new(base_path, config.max_file_size, config.max_file_size * config.rotation_count as u64)
|
||||
.map_err(|e| TelemetryError::Io(format!("Failed to create RotatingWriter: {}", e)))?;
|
||||
let writer = RotatingWriter::new(
|
||||
base_path,
|
||||
config.max_file_size,
|
||||
dial9_total_rotation_size(config.max_file_size, config.rotation_count),
|
||||
)
|
||||
.map_err(|e| TelemetryError::Io(format!("Failed to create RotatingWriter: {}", e)))?;
|
||||
|
||||
// Build traced runtime
|
||||
// Note: sampling_rate and S3 upload settings are reserved for future use
|
||||
@@ -319,6 +360,17 @@ mod tests {
|
||||
assert_eq!(config.base_path(), PathBuf::from("/tmp/telemetry/rustfs"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dial9_sanitizers_reject_zero_values() {
|
||||
assert_eq!(sanitize_dial9_max_file_size(0), 1);
|
||||
assert_eq!(sanitize_dial9_rotation_count(0), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dial9_total_rotation_size_saturates() {
|
||||
assert_eq!(dial9_total_rotation_size(u64::MAX, 2), u64::MAX);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_enabled_default() {
|
||||
// Skip if environment variable is explicitly set
|
||||
|
||||
@@ -74,6 +74,25 @@ const STDERR_WARNING_PREFIX: &str = "[WARN]";
|
||||
const REQUEST_ID_CANONICAL: &str = "request_id";
|
||||
const REQUEST_ID_COMPAT: &str = "request-id";
|
||||
|
||||
fn resolve_log_cleanup_interval_seconds(config: &OtelConfig) -> u64 {
|
||||
match config.log_cleanup_interval_seconds {
|
||||
Some(0) => {
|
||||
warn!(
|
||||
event = EVENT_LOG_CLEANER_STATE,
|
||||
component = LOG_COMPONENT_OBS,
|
||||
subsystem = LOG_SUBSYSTEM_LOCAL_LOGGING,
|
||||
result = "invalid_cleanup_interval",
|
||||
configured_seconds = 0_u64,
|
||||
fallback_seconds = DEFAULT_OBS_LOG_CLEANUP_INTERVAL_SECONDS,
|
||||
"log cleaner state changed"
|
||||
);
|
||||
DEFAULT_OBS_LOG_CLEANUP_INTERVAL_SECONDS
|
||||
}
|
||||
Some(seconds) => seconds,
|
||||
None => DEFAULT_OBS_LOG_CLEANUP_INTERVAL_SECONDS,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct RequestIdJsonFormat<T> {
|
||||
inner: Format<Json, T>,
|
||||
@@ -549,9 +568,7 @@ pub fn spawn_cleanup_task(
|
||||
.log_min_file_age_seconds
|
||||
.unwrap_or(DEFAULT_OBS_LOG_MIN_FILE_AGE_SECONDS);
|
||||
let dry_run = config.log_dry_run.unwrap_or(DEFAULT_OBS_LOG_DRY_RUN);
|
||||
let cleanup_interval = config
|
||||
.log_cleanup_interval_seconds
|
||||
.unwrap_or(DEFAULT_OBS_LOG_CLEANUP_INTERVAL_SECONDS);
|
||||
let cleanup_interval = resolve_log_cleanup_interval_seconds(config);
|
||||
|
||||
let cleaner = Arc::new(
|
||||
LogCleaner::builder(log_dir, file_pattern, active_filename)
|
||||
@@ -873,4 +890,14 @@ mod tests {
|
||||
&& span.get("request_id").and_then(Value::as_str) == Some("req-parent")
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_log_cleanup_interval_seconds_rejects_zero() {
|
||||
let config = OtelConfig {
|
||||
log_cleanup_interval_seconds: Some(0),
|
||||
..OtelConfig::default()
|
||||
};
|
||||
|
||||
assert_eq!(resolve_log_cleanup_interval_seconds(&config), DEFAULT_OBS_LOG_CLEANUP_INTERVAL_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ fn build_meter_provider(
|
||||
.build()
|
||||
.map_err(|e| TelemetryError::BuildMetricExporter(e.to_string()))?;
|
||||
|
||||
let meter_interval = config.meter_interval.unwrap_or(METER_INTERVAL);
|
||||
let meter_interval = resolve_meter_interval(config);
|
||||
|
||||
let (provider, recorder) = Recorder::builder(service_name.to_string())
|
||||
.with_meter_provider(|b: opentelemetry_sdk::metrics::MeterProviderBuilder| {
|
||||
@@ -608,6 +608,22 @@ fn create_periodic_reader(interval: u64) -> PeriodicReader<opentelemetry_stdout:
|
||||
.build()
|
||||
}
|
||||
|
||||
fn resolve_meter_interval(config: &OtelConfig) -> u64 {
|
||||
match config.meter_interval {
|
||||
Some(0) => {
|
||||
warn!(
|
||||
result = "invalid_meter_interval",
|
||||
configured_seconds = 0_u64,
|
||||
fallback_seconds = METER_INTERVAL,
|
||||
"Metrics export interval is invalid; using default interval"
|
||||
);
|
||||
METER_INTERVAL
|
||||
}
|
||||
Some(interval) => interval,
|
||||
None => METER_INTERVAL,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_signal_headers(common_headers: Option<&str>, signal_headers: Option<&str>) -> HashMap<String, String> {
|
||||
let mut headers = HashMap::new();
|
||||
if let Some(raw_headers) = common_headers {
|
||||
@@ -698,6 +714,16 @@ mod tests {
|
||||
assert_eq!(resolve_signal_timeout(None, Some(0)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_meter_interval_rejects_zero() {
|
||||
let config = OtelConfig {
|
||||
meter_interval: Some(0),
|
||||
..OtelConfig::default()
|
||||
};
|
||||
|
||||
assert_eq!(resolve_meter_interval(&config), METER_INTERVAL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_object_duration_histogram_metric_match_is_scoped() {
|
||||
assert!(is_get_object_duration_histogram_metric("rustfs_io_get_object_stage_duration_seconds"));
|
||||
|
||||
Reference in New Issue
Block a user