首先确保安装好了OpenResty:
介绍:OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。
我的nginx配置大致如下
server
{
listen 80;
server_name xxxxx.com;
index index.php index.html index.htm default.php default.htm default.html;
#请求日志
lua_need_request_body on;
log_by_lua_file '/www/wwwlogs/logs/config/log.lua';
set $response_body "";
body_filter_by_lua_block {
local response_body = string.sub(ngx.arg[1],1,10000)
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. response_body
if ngx.arg[2] then
ngx.var.response_body = ngx.ctx.buffered
end
}
# 路径不存在api 则走dist目录
if ( $request_uri !~* /api ) {
set $root_path /www/wwwroot/larvael/web/dist;
}
# 否则走后端入口
if ( $request_uri ~* /(api|storage|\.well-known) ) {
set $root_path /www/wwwroot/laravel/public;
}
root $root_path;
location / {
try_files $uri $uri @router;
index index.html;
add_header Cache-Control "no-cache, no-store";
}
location @router {
rewrite ^.*$ /index.html break;
}
location /api/
{
index index.php;
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php?s=$1 last;
break;
}
}
# =============================================
#SSL-START SSL相关配置,请勿删除或修改下一行带注释的404规则
#error_page 404/404.html;
#SSL-END
#ERROR-PAGE-START 错误页配置,可以注释、删除或修改
#error_page 404 /404.html;
#error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START PHP引用配置,可以注释或修改
include enable-php-80.conf;
#PHP-INFO-END
#禁止访问的文件或目录
location ~ ^/(\.user.ini|\.htaccess|\.git|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
#一键申请SSL证书验证目录相关设置
location ~ \.well-known{
allow all;
}
#禁止在证书验证目录放入敏感文件
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
location ~ .*\.(js|css)?$
{
expires 12h;
error_log /dev/null;
access_log /dev/null;
}
access_log /www/wwwlogs/xxxxx.com.log;
error_log /www/wwwlogs/xxxxx.com.error.log;
}
nginx配置中关于lua的配置如下
#请求日志
lua_need_request_body on;
log_by_lua_file '/www/wwwlogs/logs/config/log.lua';
set $response_body "";
body_filter_by_lua_block {
local response_body = string.sub(ngx.arg[1],1,10000)#截断,防止过长
ngx.ctx.buffered = (ngx.ctx.buffered or "") .. response_body
if ngx.arg[2] then
ngx.var.response_body = ngx.ctx.buffered
end
}
我在/www/wwwlogs/logs/config下放了两个lua文件用于写记录日志的逻辑,当然你也可以用一个,我只是之前设计的时候考虑到服务器可能有多个网站所以抽了一个出来打算复用,后来好像没啥用
--这个文件是/www/wwwlogs/logs/config/log.lua
--用户IP
local headers = ngx.req.get_headers()
local request_id = headers["request_id"] or headers["REQUEST_ID"] or "xxxxxxxxxx"
if request_id == "xxxxxxxxxx" and ngx.var.request_method == "GET" then
return;
end
if ngx.var.request_method == "OPTIONS" then
return;
end
local host = ngx.var.host;
local file_path_sub = host;
if host == "demo.com" then
file_path_sub = "demo";
end
if host == "xxxxx.com" then
file_path_sub = "xxxxx";
end
if host == "abcdefg.com" then
file_path_sub = "随便写,就是生产的文件夹的名称";
end
package.path = package.path .. ";/www/wwwlogs/logs/config/?.lua"
require("common")
local file_args = {};
local args = {};
local url = '';
args, file_args, url = ModuleT.init_form_args()
-- 记录请求日志
-- 引⼊lua所有解析json的库
local cjson = require "cjson"
--加入判断文件夹是否存在
local file_path = "/www/wwwlogs/logs/".. file_path_sub;
local fdr = io.open(file_path,"rb")
if fdr then fdr:close() end
if fdr == nil then
os.execute("mkdir "..file_path .. " -p")
end
-- 使⽤lua的io打开⼀个⽂件,如果⽂件不存在,就创建,a为append模式
local file = io.open(file_path.. "/" ..file_path_sub .. os.date("%Y-%m-%d",unixtime) .. ".log", "a")
--消息内容
--时间
--serverTimeTable是把服务器时间转换成当地时间格式的、形如{year = 2022, month = 9, day = 1, hour = 9, min = 17, sec = 50}的表
local now = os.time()
local timeZone = os.difftime(now, os.time(os.date("!*t", now))) / 3600 -- 获取系统时区
local deltaTimeZone = 8 - timeZone
local chinaTime = os.time(serverTimeTable) + deltaTimeZone * 3600 - (isDst and 1 or 0) * 3600
local dateTime = '[' .. os.date("%Y-%m-%d %H:%M:%S", chinaTime) .. ']'
-- --IP
local ip = headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
-- -- 请求方式
local request_method = ngx.var.request_method
local request_params = {};
for key, value in pairs(args) do
request_params[tostring(key)] = tostring(value);
end
for key, value in pairs(file_args) do
request_params[tostring(key)] = tostring(value);
end
local response_body = ModuleT.unicode_to_utf8(ngx.var.response_body)
---------------------------
local logString = file_path.. dateTime .. ' ' .. ip .. ' ' .. request_method .. ' ' .. url .. ' \n';
local logString = logString .. 'request-id: ' .. request_id .. ' \n';
local logString = logString .. 'Request: ' .. cjson.encode(request_params) .. ' \n';
local logString = logString .. 'Response: ' .. response_body .. ' \n';
-- ngx.say(logString);
-- 将json写⼊到指定的log⽂件,末尾追加换⾏
file:write(logString, "\n")
-- 将数据写⼊
file:flush()
--这个文件是/www/wwwlogs/logs/config/common.lua
ModuleT = {}
-- 字符串处理
local function explode(_str, seperator)
local pos, arr = 0, {}
for st, sp in
function()
return string.find(_str, seperator, pos, true)
end
do
table.insert(arr, string.sub(_str, pos, st - 1));
pos = sp + 1;
end
table.insert(arr, string.sub(_str, pos));
return arr;
end
-- 格式化参数
function ModuleT.init_form_args()
local args = {};
local file_args = {};
local uri = '';
local is_have_file_param = false;
local cjson = require "cjson";
local headers = ngx.req.get_headers();
local method = ngx.var.request_method;
local request_uri_args = ngx.req.get_uri_args();
-- 获取url
for k, v in pairs(request_uri_args) do
if k == 's' then
uri = v
end
end
if "GET" == method or "DELETE" == method then
for k, v in pairs(request_uri_args) do
if k ~= 's' then
args[k] = v
end
end
elseif "POST" == method or "PUT" == method then
-- ngx.req.read_body();
--获取参数传递类型
local req_type = '';
local i, j = string.find(headers["content-type"], "application/x-www-form-urlencoded")
if i ~= nil and j ~= nil then
--判断是否是application/x-www-form-urlencoded类型
req_type = 'x-www-form-urlencoded';
end
local i, j = string.find(headers["content-type"], "application/json")
if i ~= nil and j ~= nil then
--判断是否是application/json类型
req_type = 'json';
end
local i, j = string.find(headers["content-type"], "multipart/form-data")
if i ~= nil and j ~= nil then
--判断是否是form-data类型
req_type = 'form-data';
end
--根据不同的参数传递类型进行逻辑处理
if req_type == 'x-www-form-urlencoded' then
args = ngx.req.get_post_args();
elseif req_type == 'json' then
local json = ngx.req.get_body_data();
if json ~= nil then
args = cjson.decode(json);
end
elseif req_type == 'form-data' then
--判断是否是multipart/form-data类型的表单
is_have_file_param = true;
content_type = headers["content-type"];
body_data = ngx.req.get_body_data();
--body_data可是符合http协议的请求体,不是普通的字符串
--请求体的size大于nginx配置里的client_body_buffer_size,则会导致请求体被缓冲到磁盘临时文件里,client_body_buffer_size默认是8k或者16k
if not body_data then
local datafile = ngx.req.get_body_file()
if not datafile then
error_code = 1
error_msg = "no request body found"
else
local fh, err = io.open(datafile, "r")
if not fh then
error_code = 2
error_msg = "failed to open " .. tostring(datafile) .. "for reading: " .. tostring(err)
else
fh:seek("set")
body_data = fh:read("*a")
fh:close()
if body_data == "" then
error_code = 3
error_msg = "request body is empty"
end
end
end
end
local new_body_data = {}
--确保取到请求体的数据
if not error_code then
local boundary = "--" .. string.sub(headers["content-type"], 31); -- 分割符
-- ngx.say(boundary);
local body_data_table = explode(tostring(body_data), boundary); -- 按分隔符分割
-- ngx.say(body_data_table);
local first_string = table.remove(body_data_table, 1);
local last_string = table.remove(body_data_table);
for _, v in ipairs(body_data_table) do
local start_pos, end_pos, capture, capture2 = string.find(v, 'Content%-Disposition: form%-data; name="(.+)"; filename="(.*)"')
if not start_pos then
--普通参数
local t = explode(v, "\r\n\r\n");
local temp_param_name = string.sub(t[1], 41, -2);
local temp_param_value = string.sub(t[2], 1, -3);
args[temp_param_name] = temp_param_value;
else
--文件类型的参数,capture是参数名称,capture2是文件名
file_args[capture] = capture2;
table.insert(new_body_data, v);
end
end
table.insert(new_body_data, 1, first_string);
table.insert(new_body_data, last_string);
--去掉app_key,app_secret等几个参数,把业务级别的参数传给内部的API
body_data = table.concat(new_body_data, boundary);
--body_data可是符合http协议的请求体,不是普通的字符串
end
else
args = ngx.req.get_post_args();
end
end
return args, file_args, uri;
end
--unicode转汉字
function ModuleT.unicode_to_utf8(convertStr)
if type(convertStr)~="string" then
return convertStr
end
local bit = require("bit")
local resultStr=""
local i=1
while true do
local num1=string.byte(convertStr,i)
local unicode
if num1~=nil and string.sub(convertStr,i,i+1)=="\\u" then
unicode=tonumber("0x"..string.sub(convertStr,i+2,i+5))
i=i+6
elseif num1~=nil then
unicode=num1
i=i+1
else
break
end
if unicode <= 0x007f then
resultStr=resultStr..string.char(bit.band(unicode,0x7f))
elseif unicode >= 0x0080 and unicode <= 0x07ff then
resultStr=resultStr..string.char(bit.bor(0xc0,bit.band(bit.rshift(unicode,6),0x1f)))
resultStr=resultStr..string.char(bit.bor(0x80,bit.band(unicode,0x3f)))
elseif unicode >= 0x0800 and unicode <= 0xffff then
resultStr=resultStr..string.char(bit.bor(0xe0,bit.band(bit.rshift(unicode,12),0x0f)))
resultStr=resultStr..string.char(bit.bor(0x80,bit.band(bit.rshift(unicode,6),0x3f)))
resultStr=resultStr..string.char(bit.bor(0x80,bit.band(unicode,0x3f)))
end
end
resultStr=resultStr..'\0'
return resultStr
end
return ModuleT
也可以自行添加记录的内容和封装,本文件仅支持laravel,其他框架得自己调试一下
可以选择给nginx配置中的
log_by_lua_file '/www/wwwlogs/logs/config/log.lua';
改成
content_by_lua_file '/www/wwwlogs/logs/config/log.lua';
来调试输出内容,或者查看nginx配置中的
/www/wwwlogs/xxxxx.com.error.log;
来分析报错行数
补充一下,laravel中好像也仅支持GET/DELETE和POST/PUT中的Raw类型的参数记录,其他的没试过,主要是前端的请求方式没有很多,可以写但是没必要