OpenResty实现简单的记录操作laravel请求/响应日志

首先确保安装好了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类型的参数记录,其他的没试过,主要是前端的请求方式没有很多,可以写但是没必要

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容