一、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 日志文件过大导致磁盘空间不足
这个问题太常见了,解决方案有几种:
- 前面提到的logrotate自动切割
- 使用syslog协议将日志发送到远程服务器
- 在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等。解决方案:
- 在Nginx层过滤:
map $request_body $filtered_body {
default "";
"~(.*password=)[^&]+" "$1[FILTERED]";
"~(.*token=)[^&]+" "$1[FILTERED]";
}
- 用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
五、性能优化建议
- 对于高流量场景,建议使用内存缓冲区:
access_log /var/log/openresty/access.log main buffer=32k flush=5s;
- 如果不需要详细日志,可以关闭access_log:
location /static/ {
access_log off;
}
- 使用syslog协议可以减少磁盘IO:
access_log syslog:server=127.0.0.1:514,facility=local7,tag=nginx,severity=info main;
六、总结
OpenResty的日志分析是个系统工程,从日志收集、格式化到存储分析都需要精心设计。关键点在于:
- 统一日志格式规范
- 敏感信息过滤
- 合理的日志轮转策略
- 根据业务需求选择合适的分析工具
- 高并发场景下的性能考量
对于中小型项目,简单的Shell脚本+logrotate可能就够用了。但对于大型分布式系统,建议考虑ELK、Fluentd等专业方案。无论哪种方案,都要记得日志分析的最终目标是为业务服务,不要为了分析而分析。
评论