first commit
This commit is contained in:
230
lib/logger.lua
Normal file
230
lib/logger.lua
Normal file
@@ -0,0 +1,230 @@
|
||||
-- 日志记录模块
|
||||
-- 记录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
|
||||
Reference in New Issue
Block a user