🦀 Rust LogCleaner 压缩策略深度解析:gzip 老文件自动压缩 + 分布式日志聚合生产实践指南

Photos provided by Unsplash OR Pexels

🦀 Rust LogCleaner 压缩策略深度解析:gzip 老文件自动压缩 + 分布式日志聚合生产实践指南

在 RustFS 这套高性能 S3 对象存储系统中,日志文件轮转只是第一步。真正让生产环境“永不爆盘、永不丢日志”的,是后台的 LogCleaner 子系统。它与我们之前剖析的 RollingAppender 紧密配合,实现了时间 + 大小双轮转 + 老文件 gzip 压缩 + 总大小/保留数/空文件清理的全生命周期管理。

本文基于 RustFS 官方仓库(crates/obs/src/cleaner/)的最新源码与 README,结合 local.rs 中的配置注入逻辑,由浅入深完整拆解 LogCleaner 压缩策略,并给出分布式集群下日志聚合的实战方案。看完即可直接拷贝到你的 Rust 项目中落地。

浅层:整体架构与三阶段流水线(来自官方 README)

LogCleaner 采用 Scanner → Selection → Action 清晰流水线设计,零侵入、异步后台运行:

// local.rs 中的启动(已在上篇详细解析)
let cleaner = Arc::new(
    LogCleaner::builder(log_dir, file_pattern, active_filename)
        .match_mode(match_mode)          // Prefix / Suffix
        .keep_files(keep_files)
        .max_total_size_bytes(...)
        .compress_old_files(compress)
        .gzip_compression_level(gzip_level)
        .compressed_file_retention_days(retention_days)
        .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));
    loop {
        interval.tick().await;
        let _ = tokio::task::spawn_blocking(move || cleaner_clone.cleanup()).await;
    }
});
  • Discovery (scanner.rs)read_dir 非递归扫描,仅匹配 file_pattern(支持前缀/后缀模式),跳过活跃文件(active_filename)、排除模式、年龄 < min_file_age_seconds(默认 3600s = 1 小时)。
  • Selection (core.rs):按策略排序文件:
    • 保留最近 keep_files
    • 若总大小超 max_total_size_bytes → 删除最老
    • 单文件超 max_single_file_size_bytes → 立即清理
    • 删除空文件(可选)
  • Action (core.rs + compress.rs):先压缩(可选)→ 再删除原文件;.gz 文件单独保留 compressed_file_retention_days 天后删除。

核心返回值cleanup() -> (deleted: usize, freed: u64),便于打指标监控。

中层:压缩策略核心细节(gzip 自动触发机制)

compress_old_files = true(默认开启)时,压缩发生在 Action 阶段

  1. 选中的“待清理”文件(非活跃、年龄 ≥ min_file_age_seconds)先被 gzip 压缩。
  2. 压缩级别log_gzip_compression_level(默认 6,范围 1-9),使用 flate2 或同类库实现。
  3. 压缩后命名:原归档文件(如 20260315153012.123456-0.rustfs.log)→ ... .gz
  4. 保留策略:压缩后的 .gz 文件单独计入保留天数,超过 compressed_file_retention_days(默认值见 config)后彻底删除。
  5. 防误操作
  • min_file_age_seconds 保护刚轮转的文件不被立即压缩。
  • dry_run = true 时只打印日志,不实际操作(生产验证神器)。
  • delete_empty_files = true 额外清理 0 字节残留文件。

与 RollingAppender 完美联动

  • RollingAppender 产生带微秒 + 原子计数器的归档文件(Suffix/Prefix 两种模式)。
  • LogCleaner 用同一 match_mode + file_pattern 精确识别,避免误删活跃文件。
  • 即使活跃文件因 min_file_age_seconds 被 scanner 跳过,max_single_file_size_bytes 仍在 RollingAppender::should_roll 中实时强制轮转(双保险)。

性能亮点

  • 所有文件操作在 spawn_blocking 中执行,不阻塞 Tokio 主线程。
  • 扫描仅 read_dir + 元数据,O(N) 极轻量(N 为日志文件数,通常 < 1000)。
  • 压缩发生在“待删除”阶段,节省磁盘空间同时保留可读历史。

深层:配置全景 + 常见坑规避(直接拷贝生产模板)

[observability]
log_directory = "/var/log/rustfs"
log_filename = "rustfs.log"
log_match_mode = "suffix"                    # 或 prefix
log_rotation_time = "daily"
log_max_single_file_size_bytes = 10485760    # 10MB 强制轮转
log_max_total_size_bytes = 1073741824        # 1GB 总上限
log_keep_files = 30
log_compress_old_files = true
log_gzip_compression_level = 6
log_compressed_file_retention_days = 90      # .gz 保留 3 个月
log_min_file_age_seconds = 3600              # 防新文件误压
log_delete_empty_files = true
log_dry_run = false
log_cleanup_interval_seconds = 300           # 每 5 分钟清理一次
log_exclude_patterns = "*.tmp,*.lock"
log_stdout_enabled = false

生产避坑

  • 若发现日志不清理 → 检查 min_file_age_seconds 是否过大(Issue #2130 已修复,但仍建议设为 0~3600)。
  • Windows 环境:RollingAppender 已内置重试,LogCleaner 也兼容。
  • 监控指标:暴露 log_cleaner.deleted_files_totallog_cleaner.freed_bytes_total 到 Prometheus。

实战延伸:分布式日志聚合生产落地方案

RustFS 支持分布式部署(Erasure Coding + 多节点),每个节点都会产生本地日志。LogCleaner 只解决单节点磁盘问题,分布式聚合需额外层:

推荐架构(零侵入、秒级查询)

  1. 每节点本地处理(已实现):
  • LogCleaner + RollingAppender → 压缩后 .gz 文件。
  • 活跃文件始终保持 JSON 格式,便于实时采集。
  1. 日志采集侧车(推荐 Vector / Fluent Bit)

    # Kubernetes sidecar 示例
    containers:
    - name: vector
      image: timberio/vector:latest
      volumeMounts:
      - mountPath: /var/log/rustfs
        name: logs

    Vector 配置(Rust 原生支持):

    [sources.rustfs_logs]
    type = "file"
    include = ["/var/log/rustfs/*.log", "/var/log/rustfs/*.log.gz"]
    read_from = "beginning"
    
    [transforms.parse_json]
    type = "remap"
    inputs = ["rustfs_logs"]
    source = '''
      . = parse_json!(.message)
    '''
    
    [sinks.loki]
    type = "loki"
    inputs = ["parse_json"]
    endpoint = "http://loki:3100"
    labels = { app = "rustfs", node = "${HOSTNAME}" }
  2. 中央存储 + 查询

  • 首选 Loki + Grafana(轻量、日志索引快)。
  • 备选 Elasticsearch(如果已有 ELK)。
  • 高级:OpenTelemetry Logs 导出(当配置 OTLP endpoint 时,RustFS 会走日志导出,绕过本地文件)。
  1. 跨节点统一归档策略
  • 所有节点使用相同 log_filename + match_mode
  • 通过 log_exclude_patterns 排除临时文件。
  • 压缩后 .gz 直接被采集器识别(Vector 支持 gzip 自动解压)。
  • 总大小控制:每个节点 1GB,本地不爆 → 集群也不会爆。
  1. 监控闭环
  • Prometheus 采集 rustfs.start.total + LogCleaner 指标。
  • Alert:磁盘使用率 > 80% 或 cleaner.deleted_files_total 突增。

生产验证路径

  • 单机:RUSTFS_OBS_LOG_DRY_RUN=true 先跑 24h。
  • 集群:部署 3 节点,模拟高负载(每秒 10k 日志),观察 Loki 查询延迟 < 2s。
  • 回滚:compressed_file_retention_days=7 快速腾空间。

总结与可扩展建议

RustFS 的 LogCleaner 把日志管理从“功能”升级为生产级能力

  • gzip 压缩 + 多维度保留策略 = 磁盘使用率稳定在 20% 以内。
  • RollingAppender 双保险 = 永不丢日志、永不爆单文件。
  • 分布式侧车采集 = 海量日志秒级聚合查询。

想更进一步?

  • compress.rs 自定义 Brotli / zstd 压缩(扩展压缩率)。
  • 把清理结果推送 OTLP metric,实现 Grafana 面板可视化。
  • 集成 S3 生命周期:压缩后的 .gz 自动上传对象存储,compressed_file_retention_days 改成 S3 规则。

参考资料

掌握 LogCleaner 压缩策略 + 分布式聚合,你就拥有了 Rust 生态中最硬核的日志基础设施。无论单机还是万节点集群,都能稳稳扛住日志洪峰,让 observability 真正“开箱即生产”。

写在最后:日志压缩看似小事,却是分布式系统“最后一公里”的稳定性保障。用好这套 LogCleaner + 侧车方案,你的 RustFS(或任意 Rust 服务)将真正做到“日志永存、磁盘永控、查询永快”。🦀 欢迎 PR 你的压缩优化!

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)