一、OpenResty日志分析的基本套路

说到OpenResty的日志分析,咱们得先搞清楚它日志的存放位置。默认情况下,OpenResty的访问日志和错误日志都放在安装目录下的logs文件夹里。不过这个路径可以通过nginx.conf配置文件自定义,比如:

# 在http块中定义日志格式
http {
    log_format main '$remote_addr - $remote_user [$time_local] '
                   '"$request" $status $body_bytes_sent '
                   '"$http_referer" "$http_user_agent"';
    
    # 定义访问日志路径和格式
    access_log /var/log/openresty/access.log main;
    
    # 错误日志配置
    error_log /var/log/openresty/error.log warn;
}

这里有个小技巧,如果日志量很大,建议按天分割日志。可以用logrotate工具来实现自动切割:

# /etc/logrotate.d/openresty 配置示例
/var/log/openresty/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 0640 www-data www-data
    sharedscripts
    postrotate
        [ ! -f /var/run/openresty.pid ] || kill -USR1 `cat /var/run/openresty.pid`
    endscript
}

二、常见日志分析场景及解决方案

2.1 高频访问IP分析

有时候我们需要找出访问频率异常的IP,可能是恶意爬虫或者DDoS攻击。用awk命令可以快速统计:

# 统计访问频率最高的前10个IP
awk '{print $1}' /var/log/openresty/access.log | sort | uniq -c | sort -nr | head -10

# 输出示例:
# 892 192.168.1.100
# 765 10.0.0.15
# 632 172.16.0.22

2.2 响应时间分析

慢请求是性能优化的重点对象。如果日志中记录了$request_time,可以这样分析:

# 找出响应时间超过3秒的请求
awk '{if($NF>3) print $0}' /var/log/openresty/access.log | sort -k10 -nr | head -20

# 按接口统计平均响应时间
awk '{sum[$7]+=$NF; count[$7]++} END{for(k in sum) print k, sum[k]/count[k]}' /var/log/openresty/access.log

2.3 HTTP状态码统计

异常状态码往往暗示着问题:

# 统计各种状态码出现的次数
awk '{print $9}' /var/log/openresty/access.log | sort | uniq -c | sort -rn

# 专门统计5xx错误
awk '{if($9>=500) print $0}' /var/log/openresty/access.log | less

三、Lua脚本增强日志分析

OpenResty的强大之处在于可以用Lua扩展日志功能。比如在access_by_lua阶段记录更多信息:

-- 在nginx.conf的server块中添加
location / {
    access_by_lua '
        local start_time = ngx.now()
        -- 这里放业务逻辑
        ngx.ctx.request_time = ngx.now() - start_time
    ';
    
    log_by_lua '
        local log_dict = {
            remote_addr = ngx.var.remote_addr,
            request_time = ngx.ctx.request_time,
            upstream_time = ngx.var.upstream_response_time,
            request_body = ngx.var.request_body,
            custom_header = ngx.req.get_headers()["X-Custom-Header"]
        }
        local cjson = require "cjson"
        ngx.log(ngx.INFO, cjson.encode(log_dict))
    ';
}

这样生成的日志就是结构化的JSON格式,方便后续用ELK等工具分析。

四、常见问题处理方案

4.1 日志文件过大导致磁盘空间不足

这个问题太常见了,解决方案有几种:

  1. 前面提到的logrotate自动切割
  2. 使用syslog协议将日志发送到远程服务器
  3. 在OpenResty中直接对接Kafka等消息队列:
server {
    location /log {
        content_by_lua '
            local kafka = require "resty.kafka"
            local producer = kafka:new(broker_list, { producer_type = "async" })
            
            local message = ngx.req.get_body_data()
            local offset, err = producer:send("nginx_logs", nil, message)
            
            if err then
                ngx.log(ngx.ERR, "send kafka error:", err)
                return
            end
        ';
    }
}

4.2 日志格式不一致问题

建议在项目初期就统一日志格式规范。如果已经存在多种格式,可以用Lua脚本进行标准化:

local function normalize_log(log_line)
    local patterns = {
        -- 匹配第一种格式
        { pattern = '^(%S+) (%S+) (%S+) %[(%S+)%] "(.-)" (%d+) (%d+) "(.-)" "(.-)"',
          fields = {"ip", "user", "auth", "time", "request", "status", "size", "referer", "ua"} },
        
        -- 匹配第二种格式
        { pattern = '^(%S+) %[(%S+)%] "(.-)" (%d+) (%d+) (%d+)',
          fields = {"ip", "time", "request", "status", "size", "rt"} }
    }
    
    for _, p in ipairs(patterns) do
        local matches = { ngx.re.match(log_line, p.pattern) }
        if matches then
            local log = {}
            for i, field in ipairs(p.fields) do
                log[field] = matches[i]
            end
            return log
        end
    end
    return nil
end

4.3 敏感信息泄露问题

日志中经常不小心记录敏感数据,比如密码、token等。解决方案:

  1. 在Nginx层过滤:
map $request_body $filtered_body {
    default "";
    "~(.*password=)[^&]+" "$1[FILTERED]";
    "~(.*token=)[^&]+" "$1[FILTERED]";
}
  1. 用Lua脚本处理更灵活:
local function filter_sensitive(data)
    local patterns = {
        { "password=[^&]+", "password=[FILTERED]" },
        { "token=[^&]+", "token=[FILTERED]" },
        { "\"credit_card\":\"%d+", "\"credit_card\":\"[FILTERED]\"" }
    }
    
    for _, p in ipairs(patterns) do
        data = ngx.re.gsub(data, p[1], p[2])
    end
    return data
end

五、性能优化建议

  1. 对于高流量场景,建议使用内存缓冲区:
access_log /var/log/openresty/access.log main buffer=32k flush=5s;
  1. 如果不需要详细日志,可以关闭access_log:
location /static/ {
    access_log off;
}
  1. 使用syslog协议可以减少磁盘IO:
access_log syslog:server=127.0.0.1:514,facility=local7,tag=nginx,severity=info main;

六、总结

OpenResty的日志分析是个系统工程,从日志收集、格式化到存储分析都需要精心设计。关键点在于:

  1. 统一日志格式规范
  2. 敏感信息过滤
  3. 合理的日志轮转策略
  4. 根据业务需求选择合适的分析工具
  5. 高并发场景下的性能考量

对于中小型项目,简单的Shell脚本+logrotate可能就够用了。但对于大型分布式系统,建议考虑ELK、Fluentd等专业方案。无论哪种方案,都要记得日志分析的最终目标是为业务服务,不要为了分析而分析。