281 lines
6.1 KiB
Lua
281 lines
6.1 KiB
Lua
---@type boolean
|
|
local is_windows = vim.loop.os_uname().version:match("Windows")
|
|
|
|
---@type string
|
|
local sep = is_windows and "\\" or "/"
|
|
|
|
---@return string
|
|
local join = function(...)
|
|
local joined = table.concat({ ... }, sep)
|
|
if is_windows then
|
|
joined = joined:gsub("\\\\+", "\\")
|
|
else
|
|
joined = joined:gsub("//+", "/")
|
|
end
|
|
return joined
|
|
end
|
|
|
|
---@class LogHandler
|
|
---@field type string
|
|
---@field level integer
|
|
---@field formatter? fun(level: integer, msg: string, ...: any)
|
|
---@field handle? fun(level: integer, text: string)
|
|
local LogHandler = {}
|
|
|
|
local levels_reverse = {}
|
|
for k, v in pairs(vim.log.levels) do
|
|
levels_reverse[v] = k
|
|
end
|
|
|
|
function LogHandler.new(opts)
|
|
vim.validate({
|
|
type = { opts.type, "s" },
|
|
handle = { opts.handle, "f" },
|
|
formatter = { opts.formatter, "f" },
|
|
level = { opts.level, "n", true },
|
|
})
|
|
return setmetatable({
|
|
type = opts.type,
|
|
handle = opts.handle,
|
|
formatter = opts.formatter,
|
|
level = opts.level or vim.log.levels.INFO,
|
|
}, { __index = LogHandler })
|
|
end
|
|
|
|
function LogHandler:log(level, msg, ...)
|
|
if self.level <= level then
|
|
local text = self.formatter(level, msg, ...)
|
|
self.handle(level, text)
|
|
end
|
|
end
|
|
|
|
local function default_formatter(level, msg, ...)
|
|
local args = vim.F.pack_len(...)
|
|
for i = 1, args.n do
|
|
local v = args[i]
|
|
if type(v) == "table" then
|
|
args[i] = vim.inspect(v)
|
|
elseif v == nil then
|
|
args[i] = "nil"
|
|
end
|
|
end
|
|
local ok, text = pcall(string.format, msg, vim.F.unpack_len(args))
|
|
if ok then
|
|
local str_level = levels_reverse[level]
|
|
return string.format("[%s] %s\n%s", str_level, os.date("%Y-%m-%d %H:%M:%S"), text)
|
|
else
|
|
return string.format("[ERROR] error formatting log line: '%s' args %s", msg, vim.inspect(args))
|
|
end
|
|
end
|
|
|
|
---@param opts table
|
|
---@return LogHandler
|
|
local function create_file_handler(opts)
|
|
vim.validate({
|
|
filename = { opts.filename, "s" },
|
|
})
|
|
local ok, stdpath = pcall(vim.fn.stdpath, "log")
|
|
if not ok then
|
|
stdpath = vim.fn.stdpath("cache")
|
|
end
|
|
local filepath = join(stdpath, opts.filename)
|
|
local logfile, openerr = io.open(filepath, "a+")
|
|
if not logfile then
|
|
local err_msg = string.format("Failed to open the CodeCompanion log file: %s", openerr)
|
|
vim.notify(err_msg, vim.log.levels.ERROR)
|
|
opts.handle = function() end
|
|
else
|
|
opts.handle = function(level, text)
|
|
logfile:write(text)
|
|
logfile:write("\n")
|
|
logfile:flush()
|
|
end
|
|
end
|
|
return LogHandler.new(opts)
|
|
end
|
|
|
|
---@param opts table
|
|
---@return LogHandler
|
|
local function create_notify_handler(opts)
|
|
opts.handle = function(level, text)
|
|
vim.notify(text, level)
|
|
end
|
|
return LogHandler.new(opts)
|
|
end
|
|
|
|
---@param opts table
|
|
---@return LogHandler
|
|
local function create_echo_handler(opts)
|
|
opts.handle = function(level, text)
|
|
local hl = "Normal"
|
|
if level == vim.log.levels.ERROR then
|
|
hl = "DiagnosticError"
|
|
elseif level == vim.log.levels.WARN then
|
|
hl = "DiagnosticWarn"
|
|
end
|
|
vim.api.nvim_echo({ { text, hl } }, true, {})
|
|
end
|
|
return LogHandler.new(opts)
|
|
end
|
|
|
|
---@return LogHandler
|
|
local function create_null_handler()
|
|
return LogHandler.new({
|
|
formatter = function() end,
|
|
handle = function() end,
|
|
})
|
|
end
|
|
|
|
---@param opts table
|
|
---@return LogHandler
|
|
local function create_handler(opts)
|
|
vim.validate({
|
|
type = { opts.type, "s" },
|
|
})
|
|
if not opts.formatter then
|
|
opts.formatter = default_formatter
|
|
end
|
|
if opts.type == "file" then
|
|
return create_file_handler(opts)
|
|
elseif opts.type == "notify" then
|
|
return create_notify_handler(opts)
|
|
elseif opts.type == "echo" then
|
|
return create_echo_handler(opts)
|
|
else
|
|
vim.notify(string.format("Unknown log handler %s", opts.type), vim.log.levels.ERROR)
|
|
return create_null_handler()
|
|
end
|
|
end
|
|
|
|
---@class Logger
|
|
---@field handlers LogHandler[]
|
|
local Logger = {}
|
|
|
|
---@class LoggerArgs
|
|
---@field handlers LogHandler[]
|
|
---@field level nil|integer
|
|
|
|
---@param opts LoggerArgs
|
|
function Logger.new(opts)
|
|
vim.validate({
|
|
handlers = { opts.handlers, "t" },
|
|
level = { opts.level, "n", true },
|
|
})
|
|
local handlers = {}
|
|
for _, defn in ipairs(opts.handlers) do
|
|
table.insert(handlers, create_handler(defn))
|
|
end
|
|
local log = setmetatable({
|
|
handlers = handlers,
|
|
}, { __index = Logger })
|
|
if opts.level then
|
|
log:set_level(opts.level)
|
|
end
|
|
return log
|
|
end
|
|
|
|
---@param level integer
|
|
function Logger:set_level(level)
|
|
for _, handler in ipairs(self.handlers) do
|
|
handler.level = level
|
|
end
|
|
end
|
|
|
|
---@return LogHandler[]
|
|
function Logger:get_handlers()
|
|
return self.handlers
|
|
end
|
|
|
|
---@param level integer
|
|
---@param msg string
|
|
---@param ... any[]
|
|
function Logger:log(level, msg, ...)
|
|
for _, handler in ipairs(self.handlers) do
|
|
handler:log(level, msg, ...)
|
|
end
|
|
end
|
|
|
|
---@param msg string
|
|
---@param ... any
|
|
function Logger:trace(msg, ...)
|
|
self:log(vim.log.levels.TRACE, msg, ...)
|
|
end
|
|
|
|
---@param msg string
|
|
---@param ... any
|
|
function Logger:debug(msg, ...)
|
|
self:log(vim.log.levels.DEBUG, msg, ...)
|
|
end
|
|
|
|
---@param msg string
|
|
---@param ... any
|
|
function Logger:info(msg, ...)
|
|
self:log(vim.log.levels.INFO, msg, ...)
|
|
end
|
|
|
|
---@param msg string
|
|
---@param ... any
|
|
function Logger:warn(msg, ...)
|
|
self:log(vim.log.levels.WARN, msg, ...)
|
|
end
|
|
|
|
---@param msg string
|
|
---@param ... any
|
|
function Logger:error(msg, ...)
|
|
self:log(vim.log.levels.ERROR, msg, ...)
|
|
end
|
|
|
|
---@generic T : any
|
|
---@param cb T
|
|
---@param message nil|string
|
|
---@return T
|
|
function Logger:wrap_cb(cb, message)
|
|
return function(err, ...)
|
|
if err then
|
|
self:error(message or "Error: %s", err)
|
|
end
|
|
return cb(err, ...)
|
|
end
|
|
end
|
|
|
|
local root = Logger.new({
|
|
handlers = {
|
|
{
|
|
type = "echo",
|
|
level = vim.log.levels.WARN,
|
|
},
|
|
},
|
|
})
|
|
|
|
---@class Logger
|
|
local M = {}
|
|
|
|
M.new = Logger.new
|
|
|
|
M.get_logfile = function()
|
|
local ok, stdpath = pcall(vim.fn.stdpath, "log")
|
|
if not ok then
|
|
stdpath = vim.fn.stdpath("cache")
|
|
end
|
|
|
|
return join(stdpath, "persisted.log")
|
|
end
|
|
|
|
---@param logger Logger
|
|
M.set_root = function(logger)
|
|
root = logger
|
|
end
|
|
|
|
---@return Logger
|
|
M.get_root = function()
|
|
return root
|
|
end
|
|
|
|
setmetatable(M, {
|
|
__index = function(_, key)
|
|
return root[key]
|
|
end,
|
|
})
|
|
|
|
return M
|