refactor(obs): optimize logging with custom RollingAppender and improved cleanup (#2151)

Signed-off-by: houseme <housemecn@gmail.com>
Signed-off-by: heihutu <30542132+heihutu@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
Co-authored-by: heihutu <30542132+heihutu@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
houseme
2026-03-13 13:20:27 +08:00
committed by GitHub
parent f83bf95b04
commit 593a58c161
15 changed files with 1227 additions and 610 deletions
+39 -40
View File
@@ -32,6 +32,7 @@ use crate::cleaner::types::FileMatchMode;
use crate::config::OtelConfig;
use crate::global::OBSERVABILITY_METRIC_ENABLED;
use crate::telemetry::filter::build_env_filter;
use crate::telemetry::rolling::{RollingAppender, Rotation};
use metrics::counter;
use rustfs_config::observability::{
DEFAULT_OBS_LOG_CLEANUP_INTERVAL_SECONDS, DEFAULT_OBS_LOG_COMPRESS_OLD_FILES, DEFAULT_OBS_LOG_COMPRESSED_FILE_RETENTION_DAYS,
@@ -175,7 +176,7 @@ fn init_file_logging_internal(
// ── 3. Choose rotation strategy ──────────────────────────────────────────
// `log_rotation_time` drives the rolling-appender rotation period.
let rotation = config
let rotation_str = config
.log_rotation_time
.as_deref()
.unwrap_or(DEFAULT_LOG_ROTATION_TIME)
@@ -187,28 +188,20 @@ fn init_file_logging_internal(
_ => FileMatchMode::Suffix,
};
use tracing_appender::rolling::{RollingFileAppender, Rotation};
let file_appender = {
let rotation = match rotation.as_str() {
"minutely" => Rotation::MINUTELY,
"hourly" => Rotation::HOURLY,
_ => Rotation::DAILY,
};
let mut builder = RollingFileAppender::builder()
.rotation(rotation)
.max_log_files(keep_files * 3); // Make sure there are some data files to archive to avoid premature deletion
match match_mode {
FileMatchMode::Prefix => builder = builder.filename_prefix(log_filename),
FileMatchMode::Suffix => builder = builder.filename_suffix(log_filename),
}
builder
.build(log_directory)
.map_err(|e| TelemetryError::Io(format!("failed to initialize rolling file appender: {e}")))?
let rotation = match rotation_str.as_str() {
"minutely" => Rotation::Minutely,
"hourly" => Rotation::Hourly,
"daily" => Rotation::Daily,
_ => Rotation::Daily,
};
let max_single_file_size = config
.log_max_single_file_size_bytes
.unwrap_or(DEFAULT_OBS_LOG_MAX_SINGLE_FILE_SIZE_BYTES);
let file_appender =
RollingAppender::new(log_directory, log_filename.to_string(), rotation, max_single_file_size, match_mode)?;
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
// ── 4. Build subscriber layers ────────────────────────────────────────────
@@ -270,7 +263,7 @@ fn init_file_logging_internal(
info!(
"Init file logging at '{}', rotation: {}, keep {} files",
log_directory, rotation, keep_files
log_directory, rotation_str, keep_files
);
Ok(OtelGuard {
@@ -352,6 +345,7 @@ fn spawn_cleanup_task(
// Use suffix matching for log files like "2026-03-01-06-21.rustfs.log"
// where "rustfs.log" is the suffix.
let file_pattern = config.log_filename.as_deref().unwrap_or(log_filename).to_string();
let active_filename = file_pattern.clone();
// Determine match mode from config, defaulting to Suffix
let match_mode = match config.log_match_mode.as_deref().map(|s| s.to_lowercase()).as_deref() {
@@ -386,21 +380,21 @@ fn spawn_cleanup_task(
.log_cleanup_interval_seconds
.unwrap_or(DEFAULT_OBS_LOG_CLEANUP_INTERVAL_SECONDS);
let cleaner = Arc::new(LogCleaner::new(
log_dir,
file_pattern,
match_mode,
keep_files,
max_total_size,
max_single_file_size,
compress,
gzip_level,
retention_days,
exclude_patterns,
delete_empty,
min_age,
dry_run,
));
let cleaner = Arc::new(
LogCleaner::builder(log_dir, file_pattern, active_filename)
.match_mode(match_mode)
.keep_files(keep_files)
.max_total_size_bytes(max_total_size)
.max_single_file_size_bytes(max_single_file_size)
.compress_old_files(compress)
.gzip_compression_level(gzip_level)
.compressed_file_retention_days(retention_days)
.exclude_patterns(exclude_patterns)
.delete_empty_files(delete_empty)
.min_file_age_seconds(min_age)
.dry_run(dry_run)
.build(),
);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(cleanup_interval));
@@ -433,8 +427,13 @@ mod tests {
..OtelConfig::default()
};
let result = init_file_logging_internal(&config, temp_path, "info", true);
assert!(result.is_err());
// We must run within a Tokio runtime because init_file_logging_internal spawns a background task.
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let result = init_file_logging_internal(&config, temp_path, "info", true);
// With eager file opening, an invalid filename (null byte) causes the OS to reject
// the open() call, so the function returns Err instead of panicking.
assert!(result.is_err(), "invalid filename must return Err, not panic");
});
}
}