chore: add logging

main
olimorris 2024-07-02 19:32:36 +01:00
parent 7c30116fc1
commit 4df3f8a036
3 changed files with 312 additions and 9 deletions

View File

@ -1,6 +1,7 @@
local M = {}
local defaults = {
log_level = "ERROR", -- One of "TRACE", "DEBUG", "ERROR"
save_dir = vim.fn.expand(vim.fn.stdpath("data") .. "/sessions/"), -- directory where session files are saved
silent = false, -- silent nvim message when sourcing session file

View File

@ -1,5 +1,6 @@
local utils = require("persisted.utils")
local config = require("persisted.config")
local log = require("persisted.log")
local utils = require("persisted.utils")
local M = {}
@ -143,14 +144,8 @@ function M.get_branch(dir)
if vim.fn.filereadable(branch_session) ~= 0 then
return branch
else
vim.notify(
string.format("[Persisted.nvim]: Trying to load a session for branch %s", config.options.default_branch),
vim.log.levels.INFO
)
vim.notify(
string.format("[Persisted.nvim]: Could not load a session for branch %s.", git_branch[1]),
vim.log.levels.WARN
)
log:debug("Trying to load a session for branch: %s", config.options.default_branch)
log:error("Could not load a session for branch: %s", git_branch[1])
vim.g.persisted_branch_session = branch_session
return config.options.branch_separator .. config.options.default_branch
end
@ -183,6 +178,21 @@ end
---@return nil
function M.setup(opts)
config.setup(opts)
log.set_root(log.new({
handlers = {
{
type = "echo",
level = vim.log.levels.WARN,
},
{
type = "file",
filename = "persisted.log",
level = vim.log.levels[config.options.log_level],
},
},
}))
local dir = session_dir()
local branch = get_branchname()
@ -192,6 +202,7 @@ function M.setup(opts)
and not ignore_branch(branch)
and args_check()
then
log:trace("Starting session")
M.start()
end
end
@ -210,19 +221,24 @@ function M.load(opt, dir)
session = opt.session
local session_data = utils.make_session_data(session)
branch = session_data and session_data.branch or ""
log:trace("Session branch %s", branch)
log:trace("Session data %s", session_data)
if not branch then
vim.notify(string.format("[Persisted.nvim]: Invalid session file %s", session), vim.log.levels.WARN)
end
else
branch = get_branchname()
session = opt.last and get_last() or get_current(dir)
log:trace("Session branch: %s", branch)
end
if session then
if vim.fn.filereadable(session) ~= 0 then
vim.g.persisting_session = not config.options.follow_cwd and session or nil
log:trace("Session load: %s", session)
utils.load_session(session, config.options.silent)
elseif type(config.options.on_autoload_no_session) == "function" then
log:trace("No session to load")
config.options.on_autoload_no_session()
end
end
@ -241,6 +257,7 @@ function M.autoload()
if config.options.autoload and args_check() then
if allow_dir(dir) and not ignore_dir(dir) and not ignore_branch(branch) then
log:trace("Autoloading from %s", dir)
M.load({}, dir)
end
end
@ -268,6 +285,7 @@ local function write(session)
vim.api.nvim_exec_autocmds("User", { pattern = "PersistedSavePre" })
vim.cmd("mks! " .. e(session))
vim.g.persisting = true
log:trace("Session saved")
vim.api.nvim_exec_autocmds("User", { pattern = "PersistedSavePost" })
end
@ -317,6 +335,7 @@ function M.delete(dir)
vim.api.nvim_exec_autocmds("User", { pattern = "PersistedDeletePre", data = session_data })
vim.schedule(function()
log:trace("Deleting session %s", session)
M.stop()
vim.fn.system("rm " .. e(session))
end)
@ -334,13 +353,16 @@ function M.toggle(dir)
dir = dir or session_dir()
if vim.g.persisting == nil then
log:trace("Toggling load")
return M.load({}, dir)
end
if vim.g.persisting then
log:trace("Toggling stop")
return M.stop()
end
log:trace("Toggling start")
return M.start()
end

280
lua/persisted/log.lua Normal file
View File

@ -0,0 +1,280 @@
---@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