|
|
@@ -0,0 +1,789 @@
|
|
|
+--[[
|
|
|
+@module exmtn
|
|
|
+@summary 运维日志扩展库,负责日志的持久化存储
|
|
|
+@version 1.0
|
|
|
+@date 2025.12.9
|
|
|
+@author zengeshuai
|
|
|
+@usage
|
|
|
+exmtn.init(1, 0) -- 初始化,1个块,缓存写入
|
|
|
+exmtn.log("info", "tag", "message", 123) -- 输出运维日志
|
|
|
+]]
|
|
|
+
|
|
|
+local exmtn = {}
|
|
|
+
|
|
|
+-- 常量定义
|
|
|
+local LOG_MTN_CACHE_SIZE = 4096
|
|
|
+local LOG_MTN_FILE_COUNT = 4
|
|
|
+local LOG_MTN_CONFIG_FILE = "/exmtn.trc"
|
|
|
+local LOG_MTN_DEFAULT_BLOCKS_DIVISOR = 40
|
|
|
+local LOG_MTN_ADD_WRITE_THRESHOLD = 256
|
|
|
+local LOG_MTN_CONFIG_VERSION = 1
|
|
|
+
|
|
|
+-- 写入方式常量
|
|
|
+exmtn.CACHE_WRITE = 0
|
|
|
+exmtn.ADD_WRITE = 1
|
|
|
+
|
|
|
+-- 内部状态
|
|
|
+local ctx = {
|
|
|
+ inited = false,
|
|
|
+ enabled = false,
|
|
|
+ cur_index = 1, -- 1-4
|
|
|
+ block_size = 4096, -- 默认块大小
|
|
|
+ blocks_per_file = 1, -- 每文件块数
|
|
|
+ file_limit = 4096, -- 每文件大小限制
|
|
|
+ write_way = 0, -- 0=缓存写入, 1=直接追加
|
|
|
+ cache = "", -- 缓存缓冲区
|
|
|
+ cache_used = 0, -- 缓存已使用字节数
|
|
|
+}
|
|
|
+
|
|
|
+-- 重置缓存
|
|
|
+local function reset_cache()
|
|
|
+ ctx.cache = ""
|
|
|
+ ctx.cache_used = 0
|
|
|
+end
|
|
|
+
|
|
|
+-- 获取当前文件路径
|
|
|
+local function get_file_path(index)
|
|
|
+ return string.format("/hzmtn%d.trc", index or ctx.cur_index)
|
|
|
+end
|
|
|
+
|
|
|
+-- 获取当前文件大小
|
|
|
+local function get_current_file_size()
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "rb")
|
|
|
+ if not file then
|
|
|
+ return 0
|
|
|
+ end
|
|
|
+ local size = file:seek("end")
|
|
|
+ file:close()
|
|
|
+ -- file:seek("end") 返回文件大小,如果失败返回 nil
|
|
|
+ if size and size > 0 then
|
|
|
+ return size
|
|
|
+ end
|
|
|
+ return 0
|
|
|
+end
|
|
|
+
|
|
|
+-- 检查文件是否存在
|
|
|
+local function file_exists(path)
|
|
|
+ local file = io.open(path, "rb")
|
|
|
+ if file then
|
|
|
+ file:close()
|
|
|
+ return true
|
|
|
+ end
|
|
|
+ return false
|
|
|
+end
|
|
|
+
|
|
|
+-- 检查所有日志文件是否存在
|
|
|
+local function files_exist()
|
|
|
+ for i = 1, LOG_MTN_FILE_COUNT do
|
|
|
+ local path = get_file_path(i)
|
|
|
+ if file_exists(path) then
|
|
|
+ return true
|
|
|
+ end
|
|
|
+ end
|
|
|
+ return false
|
|
|
+end
|
|
|
+
|
|
|
+-- 删除所有日志文件
|
|
|
+local function remove_files()
|
|
|
+ for i = 1, LOG_MTN_FILE_COUNT do
|
|
|
+ local path = get_file_path(i)
|
|
|
+ os.remove(path)
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- 创建空文件
|
|
|
+local function create_files()
|
|
|
+ for i = 1, LOG_MTN_FILE_COUNT do
|
|
|
+ local path = get_file_path(i)
|
|
|
+ local file = io.open(path, "wb")
|
|
|
+ if file then
|
|
|
+ file:close()
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- 读取配置文件
|
|
|
+local function load_config()
|
|
|
+ local file = io.open(LOG_MTN_CONFIG_FILE, "rb")
|
|
|
+ if not file then
|
|
|
+ return nil -- 文件不存在,返回 nil
|
|
|
+ end
|
|
|
+
|
|
|
+ local content = file:read("*a")
|
|
|
+ file:close()
|
|
|
+
|
|
|
+ if not content or #content == 0 then
|
|
|
+ return nil -- 文件为空
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 解析配置:格式为 "VERSION=1\nINDEX=2\nBLOCKS=10\nWRITE_WAY=0\n"
|
|
|
+ local config = {}
|
|
|
+ for line in content:gmatch("[^\r\n]+") do
|
|
|
+ -- 移除首尾空白字符
|
|
|
+ line = line:match("^%s*(.-)%s*$") or line
|
|
|
+ local key, value = line:match("([^=]+)=(.+)")
|
|
|
+ if key and value then
|
|
|
+ -- 移除 key 和 value 的首尾空白字符
|
|
|
+ key = key:match("^%s*(.-)%s*$") or key
|
|
|
+ value = value:match("^%s*(.-)%s*$") or value
|
|
|
+ local num_value = tonumber(value)
|
|
|
+ if num_value then
|
|
|
+ config[key] = num_value
|
|
|
+ else
|
|
|
+ config[key] = value
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 验证版本号
|
|
|
+ if config.VERSION ~= LOG_MTN_CONFIG_VERSION then
|
|
|
+ return nil -- 版本不匹配
|
|
|
+ end
|
|
|
+
|
|
|
+ return config
|
|
|
+end
|
|
|
+
|
|
|
+-- 保存配置文件
|
|
|
+local function save_config(index, blocks, write_way)
|
|
|
+ local content = string.format("VERSION=%d\nINDEX=%d\nBLOCKS=%d\nWRITE_WAY=%d\n",
|
|
|
+ LOG_MTN_CONFIG_VERSION, index, blocks, write_way)
|
|
|
+
|
|
|
+ local file = io.open(LOG_MTN_CONFIG_FILE, "wb")
|
|
|
+ if not file then
|
|
|
+ log.warn("exmtn", "无法打开配置文件: " .. LOG_MTN_CONFIG_FILE)
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ local ok = file:write(content)
|
|
|
+ file:close()
|
|
|
+
|
|
|
+ if not ok then
|
|
|
+ log.warn("exmtn", "写入配置文件失败: " .. LOG_MTN_CONFIG_FILE)
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 更新索引(同时保存完整配置)
|
|
|
+local function update_index(index)
|
|
|
+ return save_config(index, ctx.blocks_per_file, ctx.write_way)
|
|
|
+end
|
|
|
+
|
|
|
+-- 格式化时间戳
|
|
|
+-- 返回格式: [2025-11-05 15:06:49.947][00000027.994]
|
|
|
+local function format_timestamp()
|
|
|
+ -- 获取系统运行时间(毫秒)
|
|
|
+ local ticks_ms = 0
|
|
|
+ if mcu and mcu.ticks then
|
|
|
+ local ticks = mcu.ticks()
|
|
|
+ if ticks then
|
|
|
+ ticks_ms = ticks
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 获取当前日期时间
|
|
|
+ local date_time_str = ""
|
|
|
+ local ms = 0
|
|
|
+
|
|
|
+ if os and os.date then
|
|
|
+ -- 获取当前日期时间字符串: 2025-11-05 15:06:49
|
|
|
+ local dt = os.date("%Y-%m-%d %H:%M:%S")
|
|
|
+ if dt then
|
|
|
+ -- 计算毫秒:使用系统运行时间的毫秒部分
|
|
|
+ -- 如果 RTC 已设置,时间会更准确
|
|
|
+ ms = ticks_ms % 1000
|
|
|
+ date_time_str = string.format("%s.%03d", dt, ms)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 如果无法获取日期时间,使用默认格式
|
|
|
+ if date_time_str == "" then
|
|
|
+ date_time_str = "1970-01-01 00:00:00.000"
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 计算系统运行时间(秒.毫秒)
|
|
|
+ local uptime_sec = math.floor(ticks_ms / 1000)
|
|
|
+ local uptime_ms = ticks_ms % 1000
|
|
|
+
|
|
|
+ -- 格式化运行时间部分: 00000027.994(固定宽度,9位整数+3位小数)
|
|
|
+ local uptime_str = string.format("%09d.%03d", uptime_sec, uptime_ms)
|
|
|
+
|
|
|
+ -- 返回完整时间戳
|
|
|
+ return string.format("[%s][%s]", date_time_str, uptime_str)
|
|
|
+end
|
|
|
+
|
|
|
+-- 格式化调试信息
|
|
|
+local function format_debug_info(level, include_level)
|
|
|
+ local info = debug.getinfo(2, "Sl")
|
|
|
+ if not info or not info.source then
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+
|
|
|
+ local src = info.source
|
|
|
+ -- 跳过第一个字符(@ 或 =)
|
|
|
+ if src:sub(1, 1) == "@" or src:sub(1, 1) == "=" then
|
|
|
+ src = src:sub(2)
|
|
|
+ end
|
|
|
+
|
|
|
+ local line = info.currentline or 0
|
|
|
+ if line > 64 * 1024 then
|
|
|
+ line = 0
|
|
|
+ end
|
|
|
+
|
|
|
+ if include_level and level then
|
|
|
+ return string.format("%s/%s:%d", level, src, line)
|
|
|
+ else
|
|
|
+ return string.format("%s:%d", src, line)
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- 格式化消息(与 log.info/warn/error 格式一致,但添加时间戳前缀)
|
|
|
+local function format_message(level, tag, ...)
|
|
|
+ local argc = select("#", ...)
|
|
|
+
|
|
|
+ -- 获取 log.style 配置
|
|
|
+ local log_style = 0
|
|
|
+ if log and log.style then
|
|
|
+ log_style = log.style() or 0
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 根据级别确定日志标识
|
|
|
+ local level_char = "I" -- 默认 info
|
|
|
+ if level == "warn" then
|
|
|
+ level_char = "W"
|
|
|
+ elseif level == "error" then
|
|
|
+ level_char = "E"
|
|
|
+ end
|
|
|
+
|
|
|
+ local msg = ""
|
|
|
+ local dbg_info_with_level = format_debug_info(level_char, true)
|
|
|
+ local dbg_info_only = format_debug_info(nil, false)
|
|
|
+
|
|
|
+ if log_style == 0 then
|
|
|
+ -- LOG_STYLE_NORMAL: "I/user.tag arg1 arg2 ...\n"
|
|
|
+ msg = string.format("%s/user.%s", level_char, tag)
|
|
|
+ for i = 1, argc do
|
|
|
+ local arg = select(i, ...)
|
|
|
+ msg = msg .. " " .. tostring(arg)
|
|
|
+ end
|
|
|
+ elseif log_style == 1 then
|
|
|
+ -- LOG_STYLE_DEBUG_INFO: "I/file.lua:123 tag arg1 arg2 ...\n"
|
|
|
+ if dbg_info_with_level then
|
|
|
+ msg = dbg_info_with_level
|
|
|
+ else
|
|
|
+ msg = level_char
|
|
|
+ end
|
|
|
+ msg = msg .. " " .. tag
|
|
|
+ for i = 1, argc do
|
|
|
+ local arg = select(i, ...)
|
|
|
+ msg = msg .. " " .. tostring(arg)
|
|
|
+ end
|
|
|
+ else
|
|
|
+ -- LOG_STYLE_FULL: "I/user.tag file.lua:123 arg1 arg2 ...\n"
|
|
|
+ msg = string.format("%s/user.%s", level_char, tag)
|
|
|
+ if dbg_info_only then
|
|
|
+ msg = msg .. " " .. dbg_info_only
|
|
|
+ end
|
|
|
+ for i = 1, argc do
|
|
|
+ local arg = select(i, ...)
|
|
|
+ msg = msg .. " " .. tostring(arg)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ msg = msg .. "\n"
|
|
|
+
|
|
|
+ -- 添加时间戳前缀
|
|
|
+ local timestamp = format_timestamp()
|
|
|
+ return timestamp .. " " .. msg
|
|
|
+end
|
|
|
+
|
|
|
+-- 刷新缓存到文件
|
|
|
+local function flush_cache()
|
|
|
+ if ctx.cache_used == 0 then
|
|
|
+ return true
|
|
|
+ end
|
|
|
+
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "ab")
|
|
|
+ if not file then
|
|
|
+ log.warn("exmtn", "无法打开文件: " .. path)
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- file:write 返回 true/false 或 nil,不返回字节数
|
|
|
+ local ok = file:write(ctx.cache)
|
|
|
+ file:close()
|
|
|
+
|
|
|
+ if not ok then
|
|
|
+ log.warn("exmtn", "写入文件失败: " .. path)
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ reset_cache()
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 直接写入文件(ADD_WRITE 模式)
|
|
|
+local function direct_write(data)
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "ab")
|
|
|
+ if not file then
|
|
|
+ log.warn("exmtn", "无法打开文件: " .. path)
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- file:write 返回 true/false 或 nil,不返回字节数
|
|
|
+ local ok = file:write(data)
|
|
|
+ file:close()
|
|
|
+
|
|
|
+ if not ok then
|
|
|
+ log.warn("exmtn", "写入文件失败: " .. path)
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 将数据追加到缓存或直接写入
|
|
|
+local function buffer_append(data)
|
|
|
+ if not data or #data == 0 then
|
|
|
+ return true
|
|
|
+ end
|
|
|
+
|
|
|
+ local len = #data
|
|
|
+
|
|
|
+ -- ADD_WRITE 模式:直接写入文件
|
|
|
+ if ctx.write_way == exmtn.ADD_WRITE then
|
|
|
+ -- 小数据先缓存,累积到阈值再写入
|
|
|
+ if len < LOG_MTN_ADD_WRITE_THRESHOLD then
|
|
|
+ if ctx.cache_used + len > LOG_MTN_CACHE_SIZE then
|
|
|
+ if not flush_cache() then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ end
|
|
|
+ ctx.cache = ctx.cache .. data
|
|
|
+ ctx.cache_used = ctx.cache_used + len
|
|
|
+ -- 如果累积到阈值,立即写入
|
|
|
+ if ctx.cache_used >= LOG_MTN_ADD_WRITE_THRESHOLD then
|
|
|
+ return flush_cache()
|
|
|
+ end
|
|
|
+ return true
|
|
|
+ end
|
|
|
+ -- 大数据直接写入
|
|
|
+ return direct_write(data)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- CACHE_WRITE 模式:原有逻辑
|
|
|
+ if len > LOG_MTN_CACHE_SIZE then
|
|
|
+ -- 先刷新缓存
|
|
|
+ if not flush_cache() then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ -- 大数据直接写入
|
|
|
+ return direct_write(data)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检查缓存是否足够
|
|
|
+ if ctx.cache_used + len > LOG_MTN_CACHE_SIZE then
|
|
|
+ if not flush_cache() then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ ctx.cache = ctx.cache .. data
|
|
|
+ ctx.cache_used = ctx.cache_used + len
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+-- 写入日志到文件
|
|
|
+local function write_to_file(msg)
|
|
|
+ if not ctx.enabled then
|
|
|
+ return true -- 未启用时返回成功,不写入
|
|
|
+ end
|
|
|
+
|
|
|
+ local len = #msg
|
|
|
+
|
|
|
+ -- CACHE_WRITE 模式
|
|
|
+ if ctx.write_way == exmtn.CACHE_WRITE then
|
|
|
+ -- 检查文件大小 + 缓存大小 + 当前数据是否会超过限制
|
|
|
+ -- 如果会超过,先刷新缓存
|
|
|
+ if ctx.cache_used > 0 then
|
|
|
+ local file_sz = get_current_file_size()
|
|
|
+ if file_sz + ctx.cache_used + len > ctx.file_limit then
|
|
|
+ -- 先刷新缓存
|
|
|
+ if not flush_cache() then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ -- 重新获取文件大小
|
|
|
+ file_sz = get_current_file_size()
|
|
|
+ -- 检查文件是否已满
|
|
|
+ if file_sz >= ctx.file_limit then
|
|
|
+ -- 文件已满,切换到下一个文件
|
|
|
+ ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "wb")
|
|
|
+ if file then
|
|
|
+ file:close()
|
|
|
+ end
|
|
|
+ if not update_index(ctx.cur_index) then
|
|
|
+ log.warn("exmtn", "更新索引失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ reset_cache()
|
|
|
+ end
|
|
|
+ end
|
|
|
+ else
|
|
|
+ -- 缓存为空,检查文件大小
|
|
|
+ local file_sz = get_current_file_size()
|
|
|
+ if file_sz + len > ctx.file_limit then
|
|
|
+ -- 文件已满,切换到下一个文件
|
|
|
+ ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "wb")
|
|
|
+ if file then
|
|
|
+ file:close()
|
|
|
+ end
|
|
|
+ if not update_index(ctx.cur_index) then
|
|
|
+ log.warn("exmtn", "更新索引失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ reset_cache()
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 如果加入这条数据后缓存会满,先刷新缓存
|
|
|
+ if ctx.cache_used + len > LOG_MTN_CACHE_SIZE then
|
|
|
+ if not flush_cache() then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 刷新后再次检查文件大小
|
|
|
+ local file_sz = get_current_file_size()
|
|
|
+ if file_sz >= ctx.file_limit then
|
|
|
+ -- 文件已满,切换到下一个文件
|
|
|
+ ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "wb")
|
|
|
+ if file then
|
|
|
+ file:close()
|
|
|
+ end
|
|
|
+ if not update_index(ctx.cur_index) then
|
|
|
+ log.warn("exmtn", "更新索引失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ reset_cache()
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 加入缓存
|
|
|
+ return buffer_append(msg)
|
|
|
+ else
|
|
|
+ -- ADD_WRITE 模式:先刷新缓存,确保文件大小准确
|
|
|
+ if ctx.cache_used > 0 then
|
|
|
+ if not flush_cache() then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 获取当前文件大小
|
|
|
+ local file_sz = get_current_file_size()
|
|
|
+
|
|
|
+ -- 检查当前文件是否已写满
|
|
|
+ if file_sz >= ctx.file_limit then
|
|
|
+ -- 文件已满,切换到下一个文件
|
|
|
+ ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "wb")
|
|
|
+ if file then
|
|
|
+ file:close()
|
|
|
+ end
|
|
|
+ if not update_index(ctx.cur_index) then
|
|
|
+ log.warn("exmtn", "更新索引失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ reset_cache()
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检查当前数据是否能放入当前文件
|
|
|
+ if file_sz + len > ctx.file_limit then
|
|
|
+ -- 当前数据放不下,切换到下一个文件
|
|
|
+ ctx.cur_index = (ctx.cur_index % LOG_MTN_FILE_COUNT) + 1
|
|
|
+ local path = get_file_path()
|
|
|
+ local file = io.open(path, "wb")
|
|
|
+ if file then
|
|
|
+ file:close()
|
|
|
+ end
|
|
|
+ if not update_index(ctx.cur_index) then
|
|
|
+ log.warn("exmtn", "更新索引失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ reset_cache()
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 加入缓存或直接写入(buffer_append 会根据大小决定)
|
|
|
+ return buffer_append(msg)
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+--[[
|
|
|
+初始化运维日志
|
|
|
+@api exmtn.init(blocks, write_way)
|
|
|
+@int blocks 每个文件的块数,0表示禁用,正整数表示块数量
|
|
|
+@int write_way 写入方式,可选参数。exmtn.CACHE_WRITE(0)表示缓存写入,exmtn.ADD_WRITE(1)表示直接追加写入,默认为exmtn.CACHE_WRITE
|
|
|
+@return boolean 成功返回true,失败返回false
|
|
|
+@usage
|
|
|
+exmtn.init(1, exmtn.CACHE_WRITE) -- 初始化,1个块,缓存写入
|
|
|
+]]
|
|
|
+function exmtn.init(blocks, write_way)
|
|
|
+ -- 参数校验
|
|
|
+ if blocks == nil then
|
|
|
+ blocks = 0
|
|
|
+ end
|
|
|
+ blocks = math.floor(blocks)
|
|
|
+ if blocks < 0 then
|
|
|
+ log.warn("exmtn", "无效的块数")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ write_way = write_way or exmtn.CACHE_WRITE
|
|
|
+ if write_way ~= exmtn.CACHE_WRITE and write_way ~= exmtn.ADD_WRITE then
|
|
|
+ write_way = exmtn.CACHE_WRITE
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 如果禁用
|
|
|
+ if blocks == 0 then
|
|
|
+ reset_cache()
|
|
|
+ remove_files()
|
|
|
+ ctx.enabled = false
|
|
|
+ ctx.cur_index = 1
|
|
|
+ -- 删除配置文件
|
|
|
+ os.remove(LOG_MTN_CONFIG_FILE)
|
|
|
+ ctx.inited = true
|
|
|
+ return true
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 读取文件系统信息
|
|
|
+ if not ctx.inited then
|
|
|
+ ctx.block_size = 4096
|
|
|
+ ctx.blocks_per_file = 1
|
|
|
+
|
|
|
+ -- 尝试获取文件系统信息(需要 fs 模块支持)
|
|
|
+ -- fs.fsstat 返回: success, total_blocks, used_blocks, block_size, fs_type
|
|
|
+ if fs and fs.fsstat then
|
|
|
+ local success, total_blocks, used_blocks, block_size, fs_type = fs.fsstat("/")
|
|
|
+ if success and block_size and block_size > 0 then
|
|
|
+ ctx.block_size = block_size
|
|
|
+ if total_blocks and total_blocks > 0 then
|
|
|
+ local def_blocks = math.floor(total_blocks / LOG_MTN_DEFAULT_BLOCKS_DIVISOR)
|
|
|
+ if def_blocks > 0 then
|
|
|
+ ctx.blocks_per_file = def_blocks
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 读取配置文件(仅在首次初始化时读取)
|
|
|
+ if not ctx.inited then
|
|
|
+ local config = load_config()
|
|
|
+ if config then
|
|
|
+ -- 读取索引
|
|
|
+ if config.INDEX and config.INDEX >= 1 and config.INDEX <= LOG_MTN_FILE_COUNT then
|
|
|
+ ctx.cur_index = config.INDEX
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 读取块数配置
|
|
|
+ if config.BLOCKS and config.BLOCKS > 0 then
|
|
|
+ ctx.blocks_per_file = config.BLOCKS
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 读取写入方式配置
|
|
|
+ if config.WRITE_WAY == 0 or config.WRITE_WAY == 1 then
|
|
|
+ ctx.write_way = config.WRITE_WAY
|
|
|
+ end
|
|
|
+
|
|
|
+ log.info("exmtn", "读取索引", ctx.cur_index)
|
|
|
+ log.info("exmtn", "读取块数配置", ctx.blocks_per_file)
|
|
|
+ log.info("exmtn", "读取写入方式配置", ctx.write_way)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 检查配置是否变化
|
|
|
+ -- 如果已初始化,比较当前配置和新配置;如果未初始化,不需要判断(首次初始化总是"变化"的)
|
|
|
+ local config_changed = false
|
|
|
+ if ctx.inited then
|
|
|
+ -- 已初始化:比较当前配置和新传入的配置
|
|
|
+ config_changed = (ctx.blocks_per_file ~= blocks) or (ctx.write_way ~= write_way)
|
|
|
+ end
|
|
|
+ -- 未初始化:config_changed 保持为 false,因为首次初始化不算"变化"
|
|
|
+
|
|
|
+ log.info("exmtn", "配置变化", config_changed)
|
|
|
+ -- 更新配置
|
|
|
+ ctx.blocks_per_file = blocks
|
|
|
+ ctx.write_way = write_way
|
|
|
+ ctx.file_limit = ctx.block_size * ctx.blocks_per_file
|
|
|
+ if ctx.file_limit == 0 then
|
|
|
+ ctx.file_limit = LOG_MTN_CACHE_SIZE
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 处理文件的三种情况
|
|
|
+ if config_changed then
|
|
|
+ -- 情况1:配置变化,清空文件
|
|
|
+ log.info("exmtn", "配置变化,清空文件")
|
|
|
+ reset_cache()
|
|
|
+ remove_files()
|
|
|
+ create_files()
|
|
|
+ ctx.cur_index = 1
|
|
|
+ elseif files_exist() then
|
|
|
+ -- 情况2:配置没有变化,文件存在,根据配置文件中保存的文件指针继续写
|
|
|
+ log.info("exmtn", "配置未变化,文件存在,继续写入")
|
|
|
+ -- ctx.cur_index 已经从配置文件读取(如果是首次初始化)或保持当前值(如果已初始化),不需要重置
|
|
|
+ else
|
|
|
+ -- 情况3:配置没有变化,文件不存在,创建文件
|
|
|
+ log.info("exmtn", "配置未变化,文件不存在,创建文件")
|
|
|
+ create_files()
|
|
|
+ -- ctx.cur_index 已经从配置文件读取(如果是首次初始化)或保持当前值(如果已初始化),不需要重置
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 保存配置到文件
|
|
|
+ if not save_config(ctx.cur_index, blocks, write_way) then
|
|
|
+ log.warn("exmtn", "保存配置失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ ctx.enabled = true
|
|
|
+ ctx.inited = true
|
|
|
+
|
|
|
+ -- 打印初始化信息
|
|
|
+ if blocks > 0 then
|
|
|
+ local total_size = ctx.file_limit * LOG_MTN_FILE_COUNT
|
|
|
+ local file_size_mb = ctx.file_limit / (1024 * 1024)
|
|
|
+ local total_size_mb = total_size / (1024 * 1024)
|
|
|
+ local file_size_kb = ctx.file_limit / 1024
|
|
|
+ local total_size_kb = total_size / 1024
|
|
|
+
|
|
|
+ if ctx.file_limit >= 1024 * 1024 then
|
|
|
+ log.info("exmtn", string.format("初始化成功: 每个文件 %.2f MB (%d 块 × %d 字节), 总空间 %.2f MB (%d 个文件)",
|
|
|
+ file_size_mb, ctx.blocks_per_file, ctx.block_size, total_size_mb, LOG_MTN_FILE_COUNT))
|
|
|
+ elseif ctx.file_limit >= 1024 then
|
|
|
+ log.info("exmtn", string.format("初始化成功: 每个文件 %.2f KB (%d 块 × %d 字节), 总空间 %.2f KB (%d 个文件)",
|
|
|
+ file_size_kb, ctx.blocks_per_file, ctx.block_size, total_size_kb, LOG_MTN_FILE_COUNT))
|
|
|
+ else
|
|
|
+ log.info("exmtn", string.format("初始化成功: 每个文件 %d 字节 (%d 块 × %d 字节), 总空间 %d 字节 (%d 个文件)",
|
|
|
+ ctx.file_limit, ctx.blocks_per_file, ctx.block_size, total_size, LOG_MTN_FILE_COUNT))
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+--[[
|
|
|
+输出运维日志并写入文件
|
|
|
+@api exmtn.log(level, tag, ...)
|
|
|
+@string level 日志级别,必须是 "info", "warn", 或 "error"
|
|
|
+@string tag 日志标识,必须是字符串
|
|
|
+@... 需打印的参数
|
|
|
+@return boolean 成功返回true,失败返回false
|
|
|
+@usage
|
|
|
+exmtn.log("info", "message", 123)
|
|
|
+exmtn.log("warn", "message", 456)
|
|
|
+exmtn.log("error", "message", 789)
|
|
|
+]]
|
|
|
+function exmtn.log(level, tag, ...)
|
|
|
+ if not level or type(level) ~= "string" then
|
|
|
+ log.warn("exmtn", "level 必须是字符串")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ if not tag or type(tag) ~= "string" then
|
|
|
+ log.warn("exmtn", "tag 必须是字符串")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 根据级别调用对应的底层函数(会被日志级别过滤)
|
|
|
+ if level == "info" then
|
|
|
+ log.info(tag, ...)
|
|
|
+ elseif level == "warn" then
|
|
|
+ log.warn(tag, ...)
|
|
|
+ elseif level == "error" then
|
|
|
+ log.error(tag, ...)
|
|
|
+ else
|
|
|
+ log.warn("exmtn", "level 必须是 'info', 'warn' 或 'error'")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 格式化消息(用于文件写入)
|
|
|
+ local msg = format_message(level, tag, ...)
|
|
|
+ if not msg then
|
|
|
+ log.warn("exmtn", "格式化消息失败")
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 写入文件(不受日志级别影响)
|
|
|
+ return write_to_file(msg)
|
|
|
+end
|
|
|
+
|
|
|
+--[[
|
|
|
+获取当前配置
|
|
|
+@api exmtn.get_config()
|
|
|
+@return table|nil 配置信息,失败返回nil
|
|
|
+@usage
|
|
|
+local config = exmtn.get_config()
|
|
|
+if config then
|
|
|
+ log.info("exmtn", "blocks:", config.blocks, "write_way:", config.write_way)
|
|
|
+end
|
|
|
+]]
|
|
|
+function exmtn.get_config()
|
|
|
+ if not ctx.inited then
|
|
|
+ return nil
|
|
|
+ end
|
|
|
+ return {
|
|
|
+ enabled = ctx.enabled,
|
|
|
+ cur_index = ctx.cur_index,
|
|
|
+ block_size = ctx.block_size,
|
|
|
+ blocks_per_file = ctx.blocks_per_file,
|
|
|
+ file_limit = ctx.file_limit,
|
|
|
+ write_way = ctx.write_way,
|
|
|
+ }
|
|
|
+end
|
|
|
+
|
|
|
+--[[
|
|
|
+清除所有运维日志文件
|
|
|
+@api exmtn.clear()
|
|
|
+@return boolean 成功返回true,失败返回false
|
|
|
+@usage
|
|
|
+local ok = exmtn.clear()
|
|
|
+if ok then
|
|
|
+ log.info("exmtn", "日志文件已清除")
|
|
|
+end
|
|
|
+]]
|
|
|
+function exmtn.clear()
|
|
|
+ -- 如果已初始化,先刷新缓存(确保数据不丢失)
|
|
|
+ if ctx.inited and ctx.cache_used > 0 then
|
|
|
+ if not flush_cache() then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- 删除所有日志文件
|
|
|
+ remove_files()
|
|
|
+
|
|
|
+ -- 重新创建空文件
|
|
|
+ create_files()
|
|
|
+
|
|
|
+ -- 重置索引为1
|
|
|
+ ctx.cur_index = 1
|
|
|
+
|
|
|
+ -- 更新配置文件
|
|
|
+ if not save_config(1, ctx.blocks_per_file, ctx.write_way) then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ log.info("exmtn", "运维日志文件已清除")
|
|
|
+ return true
|
|
|
+end
|
|
|
+
|
|
|
+return exmtn
|
|
|
+
|