其他文章
待补充
前言
首先声明,本文的日志不是特指nginx的access.log,而是nginx作为方向代理器将所代理的请求信息作为日志采集,包含access.log和access.log中不可记录的信息(用户敏感信息);本文涉及rabbitmq,数据库,flume等中间件,时间有限,本篇只涉及数据上传(即将日志通过请求接口,或者数据库,或者消息队列),至于数据上传后,业务怎么消费不涉及;
本文用的是封装了lua的OpenResty, OpenResty 是一个基于 NGINX 的可伸缩的 Web 平台,可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,提高 Web 应用的性能;
涉及到的lua-nginx模块的作用,可以去github上看GitHub - openresty/lua-nginx-module: Embed the Power of Lua into NGINX HTTP servers;
准备材料
详情安装步骤可以看看文章最后面贴的链接,里面介绍了各个中间件的安装和使用
openresty-1.25.3.1-win64
openresty内置了很多lua脚本,可以减少我们实践的时间;
下载openresty,然后直接进入安装目录,点击nginx.exe启动;
在安装目录下,通过 nginx-t 校验配置文件,nginx -s reload重载配置文件
RabbitMQ3.12.8
管理页面地址 localhost:15672 erlang是26.1.6 连接端口是5672, 用户名/密码:guest/guest 进入安装目录下sbin目录,打开cmd, 输入rabbitmq-plugins enable rabbitmq_management 安装管理页面
然后双击rabbitmq-server.bat启动脚本,然后打开服务管理可以看到RabbitMQ正在运行
注意:rabbitmq 的 stomp 协议支持默认是不开启的,需要手动开启: rabbitmq-plugins enable rabbitmq_stomp
apache-flume-1.11.0-bin
tail.exe
这个是用来flume采集日志的,就是linux的tail命令
下载文件tail.exe,下载地址files.cnblogs.com/hantianwei/…
下载后解压,把tail.exe 复制到 目录:C:\Windows\System32下
mysql 5.7
lua-nginx的脚本
lua脚本中require导入的模块,需要去对应github上下载源文件,放到openresty安装目录下lualib->resty文件夹下,openresy本身自带了很多模块:
方式一:nginx + lua + 接口
场景:需要对请求的日志信息进行处理,这时候将raw数据上传到业务系统中
使用到的nginx-lua模块
最后在nginx.conf的http下新增的lua脚本:
lua_shared_dict logs 10M;
init_worker_by_lua_block {
local new_timer= ngx.timer.at;
local json = require "cjson";
function upload()
local httpc = require "resty.http"
local http = httpc:new();
local key = "logs";
local vals = {};
local temp_val = ngx.shared.logs:lpop(key);
while (temp_val ~= nil)
do
vals[#reqbody + 1] = temp_val;
end
if #vals ~= 0 then
local reqbody = {};
reqbody["data"] = vals;
local res, err = http:request_uri("your interface", {
method = POST,
headers = {["Content-Type"] = "application/json"},
body = json.encode(reqbody)});
-- do something
local ok, err = ngx.timer.at(10, upload);
-- do something
end
end
new_timer(10, upload);
}
log_by_lua_block {
-- 需要采集的信息
local val = "XXX";
local key = "logs";
local len, err = ngx.shared.logs:rpush(key, val);
-- do somthing
}
贴一张github一lua-nginx各个脚本模块执行的顺序
init_worker_by_lua_block
主要是起定时任务,定时任务中将需要的信息发送给业务方
log_by_lua_block
nginx最后记录日志的阶段,在这个阶段处理的话,不会影响反向代理请求的处理时间
至于为什么不直接在log_by_lua_block中请求接口,是因为log_by_lua_block中,resty.http:new()方法被禁用,如果你使用了,则会报错
github上也有说明:
lua_shared_dic
共享内存,用来存储log_by_lua_block记录需要的请求(日志)信息
方式二:nginx+lua+mysql
场景:如果不需要业务系统再做处理,直接入库
同方式一,也是三个模块
这里连接的是本地的mysql数据库
- 数据库名:nginx_log
- 表名:tbl_log_info
- 用户名:root
- 密码:root
最后在nginx.conf的http下新增的lua脚本:
lua_shared_dict logs 10M;
init_worker_by_lua_block {
local new_timer= ngx.timer.at;
local json = require "cjson";
local delay = 10
function put_log_into_mysql()
local mysql = require "resty.mysql";
local db, err = mysql:new();
if not db then
ngx.log(ngx.ERR, "failed to instantiate mysql: ", err);
return;
end
db:set_timeout(1000);
local ok, err, errcode, sqlstate = db:connect{
host = "127.0.0.1",
port = 3306,
database = "nginx_log",
user = "root",
password = "root",
charset = "utf8"
}
if not ok then
-- ngx.log(ngx.ERR, "failed to connect: ", err);
return;
end
-- 从共享内存中取出日志, 写入mysql
local key = "logs";
local vals = "";
local temp_val = ngx.shared.logs:lpop(key);
while (temp_val ~= nil)
do
vals = vals .. "," .. temp_val;
temp_val = ngx.shared.logs:lpop(key);
end
if vals ~= "" then
local command = ("insert into nginx_log.tbl_log_info(demo) VALUES ('"..vals.."')");
ngx.log(ngx.ERR, "command is ", command);
local res, err, errcode, sqlstate = db:query(command);
if not res then
ngx.log(ngx.ERR, "insert error: ", err, ": ", errcode, ": ", sqlstate, ".");
return;
end
end
local ok, err = db:close();
if not ok then
ngx.log(ngx.ERR, "failed to close: ", err);
return;
end
local ok, err = ngx.timer.at(delay, put_log_into_mysql);
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err);
return
end
end
local ok, err = ngx.timer.at(delay, put_log_into_mysql);
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err);
return
end
}
log_by_lua_block {
-- 需要采集的信息
local val = "XXX";
local key = "logs";
local len, err = ngx.shared.logs:rpush(key, val);
ngx.log(ngx.ERR, "log: "..val);
-- do somthing
}
方式三:nginx+lua+rabbitmq
碰到一个问题,没有跑通:
2024/03/02 22:27:05 [error] 20812#41720: *801 [lua] init_worker_by_lua(nginx.conf:35):27: failed connect to rabitmqfailed to connect: no resolver defined to resolve "localhost", context: ngx.timer
在connect阶段报错了,没有找到原因
实现上,参考对应模块github的样例; 这里就只贴对应的函数,只要替换掉上面两种方式在init_worker_by_lua_block中的函数就行;
function put_log_into_rabbitmq()
local cjson = require "cjson";
local rabbitmq = require "resty.rabbitmqstomp";
local opts = {
username = "guest",
password = "guest",
vhost = "/"}
local mq, err = rabbitmq:new(opts);
if not mq then
return;
end
mq:set_timeout(10000);
local ok, err = mq:connect("127.0.0.1", 5672);
if not ok then
ngx.log(ngx.ERR, "failed connect to rabitmq", err);
return;
end
local key = "logs";
local vals = "";
local temp_val = ngx.shared.logs:lpop(key);
while (temp_val ~= nil) do
vals = vals .. "," .. temp_val;
temp_val = ngx.shared.logs:lpop(key);
end
if vals ~= "" then
local msg = {key = vals};
local headers = {};
-- 消息发送到哪里 /exchange/交换机名称/routing_key名称
headers["destination"] = "/exchange/rabbitmqDemoDirectExchange/rabbitmqDemoDirectExchange";
headers["receipt"] = "msg#1"
headers["app-id"] = "luaresty"
-- 是否持久化
headers["persistent"] = "true";
-- 消息格式
headers["content-type"] = "application/json";
local ok, err = mq:send(cjson.encode(msg), headers);
if not ok then
ngx.say('cannot send mq');
return;
end
end
-- 消息保持长连接,第一个参数表示连接超时时间,第二个参数是表示连接池大小
-- 由于 rabbitmq 连接建立比较耗时,所以保持连接池是非常必要的
local ok, err = mq:set_keepalive(10000, 500);
if not ok then
ngx.say(err);
return;
end
local ok, err = ngx.timer.at(delay, put_log_into_rabbitmq);
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err);
return
end
end
方式四:nginx + flume
场景:同步nginx的日志文件:access.log或者error.log
在flume安装目录下的conf文件夹下新增nginx_log.conf配置,将nginx的access.log文件同步到D:/log/sink下:
nginx_log.conf内容如下:
# 指定Agent的组件名称
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# 指定Flume source(要监听的路径)
a1.sources.r1.type = exec
#在windows中要执行的命令, D:/life/blog/function/nginx/openresty-1.25.3.1-win64/logs/access.log 是要监控文件的绝对路径
a1.sources.r1.command=tail -f D:/life/blog/function/nginx/openresty-1.25.3.1-win64/logs/access.log
# 指定Flume sink 将本机监控的日志源access.log同步到 D:/log/sink目录下
a1.sinks.k1.type = file_roll
a1.sinks.k1.channel = c1
# 日志保存的位置
a1.sinks.k1.sink.directory = D://log//sink
a1.sinks.k1.sink.rollInterval = 0
# 指定Flume channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# 绑定source和sink到channel上
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
实现效果如下:下面这个文件就是同步的,通过测试,发现能实时同步access.log中的内容,达到了效果
链接
-
lua-resty-mysql: GitHub - openresty/lua-resty-mysql: Nonblocking Lua MySQL driver library for ngx_lua or OpenResty
-
lua-resty-rabbitmqstomp GitHub - wingify/lua-resty-rabbitmqstomp: Opinionated Lua RabbitMQ client library for the ngx_lua apps based on the cosocket API
-
lua 通过 stomp协议发送消息到 rabbitmq - shenyixin - 博客园 (cnblogs.com)
-
flume安装与使用 在windows中实现Flume日志收集_windows flume-CSDN博客 flume安装与使用
-
rabbit安装与使用 juejin.cn/post/685457…
内容比较多,可能有的lua代码贴错了,有问题的话,可以随时在评论区指出