local Log = {} Log.levels = { TRACE = 1, DEBUG = 2, INFO = 3, WARN = 4, ERROR = 5, } local notify_opts = {} local log_notify_as_notification = false function Log:set_level(level) if not pcall(function() local logger_ok, logger = pcall(function() return require("structlog").get_logger "lvim" end) local log_level = Log.levels[level:upper()] if logger_ok and logger and log_level then for _, pipeline in ipairs(logger.pipelines) do pipeline.level = log_level end end end) then vim.notify "structlog version too old, run `:Lazy sync`" end end function Log:init() local status_ok, structlog = pcall(require, "structlog") if not status_ok then return nil end local log_level = Log.levels[(lvim.log.level):upper() or "WARN"] structlog.configure { lvim = { pipelines = { { level = log_level, processors = { structlog.processors.StackWriter({ "line", "file" }, { max_parents = 0, stack_level = 2 }), structlog.processors.Timestamper "%H:%M:%S", }, formatter = structlog.formatters.FormatColorizer( "%s [%-5s] %s: %-30s", { "timestamp", "level", "logger_name", "msg" }, { level = structlog.formatters.FormatColorizer.color_level() } ), sink = structlog.sinks.Console(false), -- async=false }, { level = log_level, processors = { structlog.processors.StackWriter({ "line", "file" }, { max_parents = 3, stack_level = 2 }), structlog.processors.Timestamper "%F %H:%M:%S", }, formatter = structlog.formatters.Format( "%s [%-5s] %s: %-30s", { "timestamp", "level", "logger_name", "msg" } ), sink = structlog.sinks.File(self:get_path()), }, }, }, } local logger = structlog.get_logger "lvim" -- Overwrite `vim.notify` to use the logger if lvim.log.override_notify then vim.notify = function(msg, vim_log_level, opts) notify_opts = opts or {} -- vim_log_level can be omitted if vim_log_level == nil then vim_log_level = Log.levels["INFO"] elseif type(vim_log_level) == "string" then vim_log_level = Log.levels[(vim_log_level):upper()] or Log.levels["INFO"] else -- https://github.com/neovim/neovim/blob/685cf398130c61c158401b992a1893c2405cd7d2/runtime/lua/vim/lsp/log.lua#L5 vim_log_level = vim_log_level + 1 end self:add_entry(vim_log_level, msg) end end return logger end --- Configure the sink in charge of logging notifications ---@param nvim_notify table The nvim-notify instance function Log:configure_notifications(nvim_notify) local status_ok, structlog = pcall(require, "structlog") if not status_ok then return end local function log_writer(log) local opts = { title = log.logger_name } opts = vim.tbl_deep_extend("force", opts, notify_opts) notify_opts = {} if log_notify_as_notification then nvim_notify(log.msg, log.level, opts) end end local notif_pipeline = structlog.Pipeline( structlog.level.INFO, {}, structlog.formatters.Format("%s", { "msg" }, { blacklist_all = true }), structlog.sinks.Adapter(log_writer) ) self.__handle:add_pipeline(notif_pipeline) end --- Adds a log entry using Plenary.log ---@param level integer [same as vim.log.levels] ---@param msg any ---@param event any function Log:add_entry(level, msg, event) if not pcall(function() local logger = self:get_logger() if not logger then return end logger:log(level, vim.inspect(msg), event) end) then vim.notify "structlog version too old, run `:Lazy sync`" end end ---Retrieves the handle of the logger object ---@return table|nil logger handle if found function Log:get_logger() local logger_ok, logger = pcall(function() return require("structlog").get_logger "lvim" end) if logger_ok and logger then return logger end logger = self:init() if not logger then return end self.__handle = logger return logger end ---Retrieves the path of the logfile ---@return string path of the logfile function Log:get_path() return string.format("%s/%s.log", get_cache_dir(), "lvim") end ---Add a log entry at TRACE level ---@param msg any ---@param event any function Log:trace(msg, event) self:add_entry(self.levels.TRACE, msg, event) end ---Add a log entry at DEBUG level ---@param msg any ---@param event any function Log:debug(msg, event) self:add_entry(self.levels.DEBUG, msg, event) end ---Add a log entry at INFO level ---@param msg any ---@param event any function Log:info(msg, event) self:add_entry(self.levels.INFO, msg, event) end ---Add a log entry at WARN level ---@param msg any ---@param event any function Log:warn(msg, event) self:add_entry(self.levels.WARN, msg, event) end ---Add a log entry at ERROR level ---@param msg any ---@param event any function Log:error(msg, event) self:add_entry(self.levels.ERROR, msg, event) end setmetatable({}, Log) return Log