nginx日志采集系列(一)-实时日常采集的几种方式

608 阅读6分钟

其他文章

待补充

前言

首先声明,本文的日志不是特指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本身自带了很多模块:

image.png

方式一: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各个脚本模块执行的顺序

image.png

init_worker_by_lua_block

主要是起定时任务,定时任务中将需要的信息发送给业务方

log_by_lua_block

nginx最后记录日志的阶段,在这个阶段处理的话,不会影响反向代理请求的处理时间

至于为什么不直接在log_by_lua_block中请求接口,是因为log_by_lua_block中,resty.http:new()方法被禁用,如果你使用了,则会报错

image.png github上也有说明:

image.png

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代码贴错了,有问题的话,可以随时在评论区指出