diff --git a/plugins/request-log2file.w b/plugins/request-log2file.w new file mode 100644 index 0000000..56106ff --- /dev/null +++ b/plugins/request-log2file.w @@ -0,0 +1,135 @@ +--- +--- Generated by MC(https://www.magentochina.org/) +--- Created by Shua1. +--- DateTime: 2024/04/29 14:32 +--- +local log = require("waf.log") +local _M = { + version = 0.1, + name = "request-log2file" +} +-- 内存队列,用于缓存日志数据 +local logQueue = {} +-- 获取当前日期 +local function getCurrentDate() + return os.date("%Y-%m-%d") +end +-- 日志文件路径 +local logFilePath = "/uuwaf/logs/access_log_" .. getCurrentDate() .. ".json" +-- 记录的请求数量阈值,降低IOPS +local LOG_THRESHOLD = 10 +-- 计数器,用于跟踪已记录的请求数量 +local requestCounter = 0 + +-- 将日志信息写入文件 +local function logToFile(filename, logs) + local file = io.open(filename, "a") + if not file then + ngx.log(ngx.ERR, "Failed to open log file: ", filename) + return + end + + for _, info in ipairs(logs) do + local json_str = log.encodeJson(info) + file:write(json_str .. "\n") + end + + file:close() +end + +-- 判断是否应该记录该请求 +local function shouldLogRequest(waf) + -- 如果是POST请求,则无论URI是否匹配静态文件,都记录 + if ngx.var.request_method == "POST" then + return true + end + -- 排除以静态文件格式为结尾的请求,可根据自身需求修改 + local uri = ngx.var.uri + if uri:match("/[^/]*%.(js|css|jpg|jpeg|png|gif|svg|webp)$") then + return false + end + return true +end + +-- 将日志信息写入内存队列 +local function logToMemory(info) + table.insert(logQueue, info) + -- 写入文件 + logToFile(logFilePath, logQueue) +end + +-- 将内存队列中的日志写入文件 +local function flushLogsToFile(premature, filename) + if not premature then + if #logQueue > 0 then + logToFile(filename, logQueue) + logQueue = {} -- 清空内存队列 + end + end +end + +-- 截断字符串到指定长度并进行Base64处理 +local function truncateString(str, length) + if str and #str > length then + str = str:sub(1, length) + end + return ngx.encode_base64(str) +end + +-- 请求阶段后过滤 +function _M.log_pre_filter(waf) + -- 判断是否应该记录该请求 + if shouldLogRequest(waf) then + local request_body_short = "" + local block_action = "" + local waf_rule_id = "" + if ngx.var.request_method == "POST" and waf.reqContentLength > 2 then + local body_data = (waf.form and waf.form["RAW"]) or '' + if body_data then + request_body_short = truncateString(body_data, 1000) -- 截断 request_body_short 并进行 Base64 处理 + end + end + if waf.msg then + block_action = "uuWaf" + waf_rule_id = waf.rule_id + end + local info = { + ["__time__"] = math.floor(ngx.var.msec), + ["block_action"] = block_action, + ["waf_rule_id"] = waf_rule_id, + ["time"] = ngx.var.time_iso8601, + ["real_client_ip"] = waf.ip, + ["server_addr"] = ngx.var.server_addr, + ["remote_addr"] = ngx.var.http_x_forwarded_for, + ["scheme"] = ngx.var.scheme, + ["request_method"] = ngx.var.request_method, + ["request_uri"] = ngx.var.request_uri, + ["request_length"] = ngx.var.request_length, + ["uri"] = ngx.var.uri, + ["request_time"] = ngx.var.request_time, + ["body_bytes_sent"] = ngx.var.body_bytes_sent, + ["request_body"] = request_body_short, + ["bytes_sent"] = ngx.var.bytes_sent, + ["status"] = ngx.var.status, + ["upstream_time"] = ngx.var.upstream_response_time, + ["upstream_host"] = ngx.var.upstream_addr, + ["upstream_status"] = ngx.var.upstream_status, + ["host"] = ngx.var.host, + ["http_referer"] = ngx.var.http_referer, + ["http_user_agent"] = ngx.var.http_user_agent, + ["http_cookie"] = ngx.var.http_cookie + } + -- 将日志信息存入内存队列 + table.insert(logQueue, info) + -- 更新计数器 + requestCounter = requestCounter + 1 + -- 如果达到记录阈值,则执行写入操作,并重置计数器 + if requestCounter >= LOG_THRESHOLD then + logToFile(logFilePath, logQueue) + logQueue = {} -- 清空内存队列 + requestCounter = 0 -- 重置计数器 + end + end +end + +return _M