231 lines
5.4 KiB
Lua
231 lines
5.4 KiB
Lua
-- 日志记录模块
|
||
-- 记录IP频率限制的详细日志信息
|
||
|
||
local _M = {}
|
||
|
||
-- 日志级别
|
||
_M.LOG_DEBUG = "DEBUG"
|
||
_M.LOG_INFO = "INFO"
|
||
_M.LOG_WARN = "WARN"
|
||
_M.LOG_ERROR = "ERROR"
|
||
|
||
-- 从公共配置加载日志配置
|
||
local common_config = require "config.common_config"
|
||
local config = common_config.log_config
|
||
|
||
-- 日志文件句柄缓存
|
||
config.log_file_handle = nil
|
||
|
||
-- 日志级别映射
|
||
local log_levels = {
|
||
DEBUG = ngx.DEBUG,
|
||
INFO = ngx.INFO,
|
||
WARN = ngx.WARN,
|
||
ERROR = ngx.ERR
|
||
}
|
||
|
||
-- 检查日志级别是否应该输出
|
||
local function should_log(level)
|
||
if not config.enabled then
|
||
return false
|
||
end
|
||
|
||
local current_level = log_levels[config.log_level] or ngx.INFO
|
||
local msg_level = log_levels[level] or ngx.INFO
|
||
|
||
return msg_level >= current_level
|
||
end
|
||
|
||
-- 清理字符串,防止日志注入
|
||
local function sanitize_string(str)
|
||
if not str then
|
||
return ""
|
||
end
|
||
|
||
-- 去除换行符、回车符等特殊字符
|
||
str = tostring(str)
|
||
str = str:gsub("[\r\n\t]", " ")
|
||
str = str:gsub("[%z\1-\31]", "")
|
||
|
||
-- 限制长度
|
||
if #str > 500 then
|
||
str = str:sub(1, 500) .. "..."
|
||
end
|
||
|
||
return str
|
||
end
|
||
|
||
-- 获取当前时间戳
|
||
local function get_timestamp()
|
||
return ngx.time()
|
||
end
|
||
|
||
-- 格式化日志消息
|
||
local function format_message(level, message, extra_info)
|
||
local timestamp = get_timestamp()
|
||
local date_str = ngx.http_time(timestamp)
|
||
|
||
local log_msg = string.format("%s %s [%s] %s",
|
||
"[IP_RATE_LIMIT]",
|
||
date_str,
|
||
level,
|
||
message
|
||
)
|
||
|
||
if extra_info and next(extra_info) then
|
||
local extras = {}
|
||
for k, v in pairs(extra_info) do
|
||
table.insert(extras, string.format("%s=%s", k, sanitize_string(v)))
|
||
end
|
||
log_msg = log_msg .. " | " .. table.concat(extras, " ")
|
||
end
|
||
|
||
-- 添加换行符
|
||
log_msg = log_msg .. "\n"
|
||
|
||
return log_msg
|
||
end
|
||
|
||
-- 打开日志文件
|
||
local function open_log_file()
|
||
if config.log_path == "" then
|
||
return nil
|
||
end
|
||
|
||
-- 如果已经有打开的文件句柄,直接返回
|
||
if config.log_file_handle then
|
||
return config.log_file_handle
|
||
end
|
||
|
||
-- 尝试打开文件(追加模式)
|
||
local file, err = io.open(config.log_path, "a")
|
||
if not file then
|
||
ngx.log(ngx.ERR, "[IP_RATE_LIMIT] Failed to open log file: ", config.log_path, " error: ", err)
|
||
return nil
|
||
end
|
||
|
||
config.log_file_handle = file
|
||
return file
|
||
end
|
||
|
||
-- 关闭日志文件
|
||
local function close_log_file()
|
||
if config.log_file_handle then
|
||
config.log_file_handle:flush()
|
||
config.log_file_handle:close()
|
||
config.log_file_handle = nil
|
||
end
|
||
end
|
||
|
||
-- 写入日志
|
||
local function write_log(log_msg, ngx_level)
|
||
-- 如果配置了日志文件路径,写入文件
|
||
if config.log_path ~= "" then
|
||
local file = open_log_file()
|
||
if file then
|
||
file:write(log_msg)
|
||
file:flush()
|
||
end
|
||
end
|
||
|
||
-- 同时写入Nginx error.log(用于调试和备份)
|
||
ngx.log(ngx_level, log_msg)
|
||
end
|
||
|
||
-- 记录日志
|
||
local function log(level, message, extra_info)
|
||
if not should_log(level) then
|
||
return
|
||
end
|
||
|
||
local log_msg = format_message(level, message, extra_info)
|
||
local ngx_level = log_levels[level] or ngx.INFO
|
||
|
||
write_log(log_msg, ngx_level)
|
||
end
|
||
|
||
-- ============================================
|
||
-- 公共API
|
||
-- ============================================
|
||
|
||
-- 记录放行日志
|
||
function _M.log_allowed(client_ip, request_count, time_window)
|
||
if not config.log_allowed then
|
||
return
|
||
end
|
||
|
||
log(_M.LOG_DEBUG, "Request allowed", {
|
||
ip = client_ip,
|
||
count = tostring(request_count),
|
||
window = tostring(time_window) .. "s",
|
||
action = "ALLOWED"
|
||
})
|
||
end
|
||
|
||
-- 记录限流日志
|
||
function _M.log_rate_limited(client_ip, request_count, max_requests, time_window)
|
||
log(_M.LOG_WARN, "Rate limit exceeded", {
|
||
ip = client_ip,
|
||
count = tostring(request_count),
|
||
max = tostring(max_requests),
|
||
window = tostring(time_window) .. "s",
|
||
action = "RATE_LIMITED",
|
||
status = "429"
|
||
})
|
||
end
|
||
|
||
-- 记录封禁日志
|
||
function _M.log_blocked(client_ip, request_count, max_requests, time_window)
|
||
log(_M.LOG_WARN, "Access blocked", {
|
||
ip = client_ip,
|
||
count = tostring(request_count),
|
||
max = tostring(max_requests),
|
||
window = tostring(time_window) .. "s",
|
||
action = "BLOCKED",
|
||
status = "403"
|
||
})
|
||
end
|
||
|
||
-- 记录请求详情
|
||
function _M.log_request_details(client_ip, uri, method, user_agent)
|
||
if not config.log_request_details then
|
||
return
|
||
end
|
||
|
||
log(_M.LOG_DEBUG, "Request details", {
|
||
ip = client_ip,
|
||
uri = sanitize_string(uri),
|
||
method = method,
|
||
ua = sanitize_string(user_agent or "")
|
||
})
|
||
end
|
||
|
||
-- 记录错误日志
|
||
function _M.log_error(message, error_info)
|
||
log(_M.LOG_ERROR, message, error_info)
|
||
end
|
||
|
||
-- 记录警告日志
|
||
function _M.log_warn(message, warn_info)
|
||
log(_M.LOG_WARN, message, warn_info)
|
||
end
|
||
|
||
-- 记录信息日志
|
||
function _M.log_info(message, info)
|
||
log(_M.LOG_INFO, message, info)
|
||
end
|
||
|
||
-- 刷新日志(确保所有日志已写入)
|
||
function _M.flush()
|
||
if config.log_file_handle then
|
||
config.log_file_handle:flush()
|
||
end
|
||
end
|
||
|
||
-- 清理资源(在worker退出时调用)
|
||
function _M.cleanup()
|
||
close_log_file()
|
||
end
|
||
|
||
return _M
|