1. 为什么需要动态日志分级?

在微服务架构中,我经历过一次惨痛的生产事故排查:某支付接口突发异常,但日志系统每分钟产生10GB的access日志,导致关键的error日志被淹没。这让我深刻认识到,智能的日志分级机制对于生产环境至关重要。

OpenResty基于Nginx的日志系统虽然高效,但传统的静态日志级别配置存在明显短板:

  • 调试阶段需要详细日志但生产环境需要精简
  • 突发异常时需要临时提升日志级别
  • 特定高危操作需要单独记录追踪

本文将以OpenResty 1.21.4.1版本+LuaJIT 2.1.0-beta3技术栈为例,演示如何通过请求特征实现动态日志分级。

2. 核心原理与基础配置

2.1 日志分级体系

OpenResty沿用Nginx的日志级别体系,从低到高分为:

ngx.DEBUG    --> 调试信息
ngx.INFO     --> 常规信息
ngx.NOTICE   --> 需要注意的情况
ngx.WARN     --> 警告事件
ngx.ERR      --> 错误事件
ngx.CRIT     --> 严重错误
ngx.ALERT    --> 需要立即处理
ngx.EMERG    --> 系统不可用

2.2 基础日志方法

常规日志输出方法:

ngx.log(ngx.ERR, "Payment failed for order:", order_id)

3. 动态分级实战案例

3.1 根据请求路径动态分级

location /api {
    access_by_lua_block {
        -- 定义敏感接口清单
        local sensitive_apis = {
            ["/api/payment"] = true,
            ["/api/auth"] = true
        }
        
        -- 获取当前请求路径
        local request_uri = ngx.var.uri
        
        -- 动态设置日志级别
        if sensitive_apis[request_uri] then
            ngx.ctx.log_level = ngx.INFO  -- 敏感接口记录详细日志
        else
            ngx.ctx.log_level = ngx.WARN  -- 普通接口只记录警告及以上
        end
    }

    log_by_lua_block {
        -- 统一日志输出点
        local msg = string.format("Response status:%s, latency:%.3fs",
                                  ngx.status, ngx.var.request_time)
        ngx.log(ngx.ctx.log_level, msg)
    }
}

3.2 基于响应状态码的分级增强

location / {
    log_by_lua_block {
        local status = ngx.status
        local log_level = ngx.WARN  -- 默认级别
        
        -- 5xx错误升级为ERROR级别
        if status >= 500 then
            log_level = ngx.ERR
        -- 4xx错误但排除404
        elseif status >= 400 and status ~= 404 then
            log_level = ngx.WARN
        -- 成功请求降级记录
        elseif status == 200 then
            log_level = ngx.INFO
        end
        
        -- 构造日志消息模板
        local log_msg = string.format("[%s] %s %s => %d",
                        ngx.var.request_method,
                        ngx.var.host,
                        ngx.var.request_uri,
                        status)
        
        ngx.log(log_level, log_msg)
    }
}

3.3 多条件组合判断示例

location ~ ^/v1/(.*) {
    access_by_lua_block {
        -- 初始化日志级别
        ngx.ctx.custom_log_level = nil
        
        -- 获取请求特征
        local client_ip = ngx.var.remote_addr
        local user_agent = ngx.var.http_user_agent
        local content_type = ngx.var.content_type
        
        -- 特征判断逻辑
        if client_ip == "192.168.1.100" then
            ngx.ctx.custom_log_level = ngx.DEBUG  -- 特定测试IP
        elseif content_type == "application/json" then
            ngx.ctx.custom_log_level = ngx.INFO    -- JSON请求详细记录
        elseif string.find(user_agent, "Android") then
            ngx.ctx.custom_log_level = ngx.NOTICE  -- 移动端特殊标记
        end
    }

    log_by_lua_block {
        -- 默认使用全局日志级别
        local final_level = ngx.ctx.custom_log_level or ngx.var.log_level
        
        -- 异常请求自动升级
        if ngx.var.status >= 500 then
            final_level = math.min(final_level, ngx.ERR)  -- 确保不低于ERROR
        end
        
        -- 生成结构化日志
        local log_entry = {
            timestamp = ngx.now(),
            client = ngx.var.remote_addr,
            method = ngx.var.request_method,
            path = ngx.var.uri,
            status = ngx.status,
            latency = ngx.var.request_time
        }
        
        ngx.log(final_level, cjson.encode(log_entry))
    }
}

4. 关联技术深度解析

4.1 执行阶段的选择策略

OpenResty的11个请求处理阶段中,日志分级的最佳实践:

  • access_by_lua*:适合请求特征判断
  • log_by_lua*:必须在此阶段输出日志
  • balancer_by_lua*:可用于上游服务日志标记

错误示例:

location /wrong {
    rewrite_by_lua_block {
        -- 错误:这个阶段无法获取完整响应信息
        ngx.log(ngx.ERR, "响应状态还未确定!")
    }
}

4.2 日志性能优化技巧

-- 缓存频繁使用的变量
local var = ngx.var
local log = ngx.log
local INFO = ngx.INFO
local ERR = ngx.ERR

-- 预编译正则表达式
local mobile_pattern = ngx.re.compile("Mobile|iP(hone|od|ad)|Android")

-- 使用table缓存判断结果
local log_level_cache = {}
local function get_log_level(uri)
    if not log_level_cache[uri] then
        -- 复杂的计算逻辑...
        log_level_cache[uri] = calculated_level
    end
    return log_level_cache[uri]
end

5. 应用场景分析

5.1 典型应用场景

  • 金丝雀发布监控:对新版本请求开启DEBUG日志
  • 安全审计追踪:对敏感操作保留完整访问日志
  • 突发故障排查:动态提升日志级别无需重启服务
  • 客户端特征分析:区分移动端/PC端日志粒度

5.2 技术方案对比

方案类型 优点 缺点
静态配置 简单可靠,性能最佳 缺乏灵活性
动态分级 实时可控,精准过滤 增加少量性能开销
外部日志服务 功能强大,支持复杂分析 架构复杂,依赖网络

6. 实施注意事项

6.1 性能陷阱规避

-- 错误示例:在日志阶段执行复杂计算
log_by_lua_block {
    -- 会严重影响性能的操作
    local res = heavy_calculation()
    ngx.log(ngx.INFO, "计算结果:", res)
}

-- 正确做法:在早期阶段预处理
access_by_lua_block {
    ngx.ctx.pre_calculation = preprocess_data()
}

6.2 安全防护要点

location /api {
    log_by_lua_block {
        -- 过滤敏感信息
        local sanitized = ngx.arg[1]:gsub("password=[^&]*", "password=***")
        ngx.log(ngx.INFO, sanitized)
    }
}

7. 技术方案总结

7.1 方案优势

  • 动态调整无需重启
  • 精确控制日志量级
  • 支持多维判断条件
  • 与现有架构无缝集成

7.2 潜在缺陷

  • 增加条件判断逻辑耗时
  • 需要完善的测试用例
  • 调试复杂度略有提升

7.3 最佳实践建议

  • 为动态日志建立特征白名单
  • 设置动态级别的生效时长
  • 定期审查日志分级策略
  • 结合监控系统联动调整