1. 日志泛滥的典型场景

某天凌晨3点,你的手机突然收到Disk Space Alert短信——OpenResty所在服务器的/var/log分区使用率超过95%。此时的你,可能正面临以下典型场景:

  • 突发流量导致access.log每小时增长10GB
  • 调试模式忘记关闭产生海量error日志
  • 日志切割配置失效导致单个文件撑爆磁盘
  • 第三方模块异常输出冗余调试信息
  • 业务日志与访问日志混合存储

(某电商曾因促销活动未及时清理日志,导致支付接口日志写满磁盘,交易中断37分钟)

2. 紧急救援三板斧

2.1 快速释放空间(外科手术)
$ cd /usr/local/openresty/nginx/logs
$ du -sh * | sort -rh | head -n 5  # 按大小倒序显示前5

# 应急清理方案
$ truncate -s 0 access.log   # 清空但不删除文件(保持inode)
$ find . -name "*.log" -size +1G -exec tee {} \;  # 安全清空大文件
$ rm -f debug_*.log.(2020-2022)  # 删除历史调试日志
2.2 动态日志级别调整(不停机修复)
-- OpenResty Lua动态配置示例
local function toggle_log_level()
    local logger = ngx.log
    local current_level = ngx.config.ngx_lua_version > 9000 and 4 or 3  -- 兼容版本差异
    
    -- 当磁盘剩余空间<10%时切换为ERROR级别
    if get_disk_free_percent() < 10 then
        ngx.log = function(level, ...)
            if level < ngx.ERR then return end
            logger(level, ...)
        end
    else
        ngx.log = logger  -- 恢复原始函数
    end
end

-- 挂载到定时器
local timer = require "resty.timer"
timer.every(60, toggle_log_level)
2.3 临时日志重定向(空间转移术)
# nginx.conf临时配置
http {
    lua_shared_dict log_redirect 10m;
    
    init_by_lua_block {
        local free = get_disk_free("/var")
        if free < 1024*1024*1024 then  -- 小于1GB时
            ngx.shared.log_redirect:set("emergency_mode", true)
        end
    }

    log_by_lua_block {
        local is_emergency = ngx.shared.log_redirect:get("emergency_mode")
        if is_emergency then
            -- 将日志分流到临时存储
            local tmp_logger = require "ngx.pipe".spawn("tee -a /mnt/tmpfs/emergency.log")
            tmp_logger:send(ngx.var.request_uri .. "\n")
        end
    }
}

3. 长效预防机制

3.1 智能日志切割方案
#!/bin/bash
# 自适应日志切割脚本(Shell示例)
LOG_DIR="/usr/local/openresty/nginx/logs"
MAX_SIZE="2G"      # 单个文件上限
RETENTION_DAYS=7   # 保留天数
DISK_WATERMARK=80  # 磁盘使用率阈值

current_usage=$(df -h $LOG_DIR | awk 'NR==2 {print $5}' | tr -d '%')

# 动态调整保留策略
if [ $current_usage -ge $DISK_WATERMARK ]; then
    EXTRA_ARGS="--remove-oldest --compress"
else
    EXTRA_ARGS="--compress"
fi

/usr/sbin/logrotate -f /etc/logrotate.d/openresty $EXTRA_ARGS
3.2 日志生命周期管理
-- 基于LJIT的日志分析模块
local function log_gc()
    local access = io.popen("ls -t /logs/access*.log | tail -n +4")
    for old_log in access:lines() do
        os.remove(old_log)
        ngx.log(ngx.NOTICE, "Purged old log: "..old_log)
    end
    
    -- 自动清理错误率<0.1%的日志
    local err_count = count_errors_last_hour()
    local total_req = count_requests_last_hour()
    if err_count/total_req < 0.001 then
        os.execute("rm -f /logs/error.log.$(date -d '1 day ago' +%Y%m%d)")
    end
end

4. 高阶技巧:日志分级存储

# 分级存储配置示例
map $time_iso8601 $log_year {
    ~^(?<year>\d{4})-  $year;
}

map $status $log_level {
    ~^[23]  1;  # 正常日志
    ~^4     2;  # 客户端错误
    ~^5     3;  # 服务端错误
    default 0;  # 特殊日志
}

server {
    access_log /var/log/nginx/access_$log_level.$log_year.log combined;
    
    error_log /var/log/nginx/error.log;
    error_log /var/log/nginx/error_debug.log debug;  # 调试日志单独存储
}

5. 技术方案对比

方案 优点 缺点 适用场景
定时清理 实现简单 可能误删重要日志 稳定环境
日志分级 精准控制 增加配置复杂度 关键业务系统
动态采样 节省90%以上空间 丢失部分细节 高并发场景
云原生存储 弹性扩展 依赖基础设施 云环境部署
日志聚合 方便集中管理 增加网络开销 分布式系统

6. 避坑指南

  1. inode耗尽危机:删除大文件前检查df -i
  2. 日志切割时间差:使用delaycompress避免压缩期间丢失日志
  3. 异步写入风险:确保flush_time设置合理
  4. 符号链接陷阱:软连接目标分区需同步监控
  5. 审计合规要求:医疗/金融行业注意保留期限

7. 实战总结

经过多次深夜救火,总结出日志管理的"三要三不要"原则:

要:

  • 实现磁盘空间动态感知
  • 建立日志分级熔断机制
  • 定期演练日志清理流程

不要:

  • 在业务高峰期运行find -delete
  • 直接rm正在写入的日志文件
  • 过度依赖日志聚合忽略本地存储

8. 终极解决方案展望

未来的智能日志系统应该具备:

  • 基于机器学习的异常日志识别
  • 自动生成日志保留策略建议
  • 实时预测磁盘使用趋势
  • 跨集群的日志生命周期协同