diff options
Diffstat (limited to 'runtime/lua/vim/lsp/log.lua')
-rw-r--r-- | runtime/lua/vim/lsp/log.lua | 214 |
1 files changed, 110 insertions, 104 deletions
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 6d2e0bc292..9f2bd71158 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -2,138 +2,144 @@ local log = {} +local log_levels = vim.log.levels + --- Log level dictionary with reverse lookup as well. --- --- Can be used to lookup the number from the name or the name from the number. --- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" --- Level numbers begin with "TRACE" at 0 +--- @type table<string|integer, string|integer> --- @nodoc -log.levels = vim.deepcopy(vim.log.levels) +log.levels = vim.deepcopy(log_levels) -- Default log level is warn. -local current_log_level = log.levels.WARN +local current_log_level = log_levels.WARN + local log_date_format = '%F %H:%M:%S' -local format_func = function(arg) + +local function format_func(arg) return vim.inspect(arg, { newline = '' }) end -do - local function notify(msg, level) - if vim.in_fast_event() then - vim.schedule(function() - vim.notify(msg, level) - end) - else +local function notify(msg, level) + if vim.in_fast_event() then + vim.schedule(function() vim.notify(msg, level) - end + end) + else + vim.notify(msg, level) end +end + +local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'lsp.log') + +-- TODO: Ideally the directory should be created in open_logfile(), right +-- before opening the log file, but open_logfile() can be called from libuv +-- callbacks, where using fn.mkdir() is not allowed. +vim.fn.mkdir(vim.fn.stdpath('log'), 'p') + +--- Returns the log filename. +---@return string log filename +function log.get_filename() + return logfilename +end - local path_sep = vim.uv.os_uname().version:match('Windows') and '\\' or '/' - local function path_join(...) - return table.concat(vim.tbl_flatten({ ... }), path_sep) +--- @type file*?, string? +local logfile, openerr + +--- Opens log file. Returns true if file is open, false on error +local function open_logfile() + -- Try to open file only once + if logfile then + return true + end + if openerr then + return false end - local logfilename = path_join(vim.fn.stdpath('log'), 'lsp.log') - -- TODO: Ideally the directory should be created in open_logfile(), right - -- before opening the log file, but open_logfile() can be called from libuv - -- callbacks, where using fn.mkdir() is not allowed. - vim.fn.mkdir(vim.fn.stdpath('log'), 'p') + logfile, openerr = io.open(logfilename, 'a+') + if not logfile then + local err_msg = string.format('Failed to open LSP client log file: %s', openerr) + notify(err_msg, log_levels.ERROR) + return false + end - --- Returns the log filename. - ---@return string log filename - function log.get_filename() - return logfilename + local log_info = vim.uv.fs_stat(logfilename) + if log_info and log_info.size > 1e9 then + local warn_msg = string.format( + 'LSP client log is large (%d MB): %s', + log_info.size / (1000 * 1000), + logfilename + ) + notify(warn_msg) end - local logfile, openerr - --- Opens log file. Returns true if file is open, false on error - local function open_logfile() - -- Try to open file only once - if logfile then + -- Start message for logging + logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format))) + return true +end + +for level, levelnr in pairs(log_levels) do + -- Also export the log level on the root object. + log[level] = levelnr + + -- Add a reverse lookup. + log.levels[levelnr] = level +end + +--- @param level string +--- @param levelnr integer +--- @return fun(...:any): boolean? +local function create_logger(level, levelnr) + return function(...) + if levelnr < current_log_level then + return false + end + local argc = select('#', ...) + if argc == 0 then return true end - if openerr then + if not open_logfile() then return false end - - logfile, openerr = io.open(logfilename, 'a+') - if not logfile then - local err_msg = string.format('Failed to open LSP client log file: %s', openerr) - notify(err_msg, vim.log.levels.ERROR) - return false + local info = debug.getinfo(2, 'Sl') + local header = string.format( + '[%s][%s] ...%s:%s', + level, + os.date(log_date_format), + info.short_src:sub(-16), + info.currentline + ) + local parts = { header } + for i = 1, argc do + local arg = select(i, ...) + table.insert(parts, arg == nil and 'nil' or format_func(arg)) end + assert(logfile) + logfile:write(table.concat(parts, '\t'), '\n') + logfile:flush() + end +end - local log_info = vim.uv.fs_stat(logfilename) - if log_info and log_info.size > 1e9 then - local warn_msg = string.format( - 'LSP client log is large (%d MB): %s', - log_info.size / (1000 * 1000), - logfilename - ) - notify(warn_msg) - end +-- If called without arguments, it will check whether the log level is +-- greater than or equal to this one. When called with arguments, it will +-- log at that level (if applicable, it is checked either way). - -- Start message for logging - logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format))) - return true - end +--- @nodoc +log.debug = create_logger('DEBUG', log_levels.DEBUG) - for level, levelnr in pairs(log.levels) do - -- Also export the log level on the root object. - log[level] = levelnr - -- FIXME: DOC - -- Should be exposed in the vim docs. - -- - -- Set the lowercase name as the main use function. - -- If called without arguments, it will check whether the log level is - -- greater than or equal to this one. When called with arguments, it will - -- log at that level (if applicable, it is checked either way). - -- - -- Recommended usage: - -- ``` - -- local _ = log.warn() and log.warn("123") - -- ``` - -- - -- This way you can avoid string allocations if the log level isn't high enough. - if level ~= 'OFF' then - log[level:lower()] = function(...) - local argc = select('#', ...) - if levelnr < current_log_level then - return false - end - if argc == 0 then - return true - end - if not open_logfile() then - return false - end - local info = debug.getinfo(2, 'Sl') - local header = string.format( - '[%s][%s] ...%s:%s', - level, - os.date(log_date_format), - string.sub(info.short_src, #info.short_src - 15), - info.currentline - ) - local parts = { header } - for i = 1, argc do - local arg = select(i, ...) - if arg == nil then - table.insert(parts, 'nil') - else - table.insert(parts, format_func(arg)) - end - end - logfile:write(table.concat(parts, '\t'), '\n') - logfile:flush() - end - end - end -end +--- @nodoc +log.error = create_logger('ERROR', log_levels.ERROR) --- This is put here on purpose after the loop above so that it doesn't --- interfere with iterating the levels -vim.tbl_add_reverse_lookup(log.levels) +--- @nodoc +log.info = create_logger('INFO', log_levels.INFO) + +--- @nodoc +log.trace = create_logger('TRACE', log_levels.TRACE) + +--- @nodoc +log.warn = create_logger('WARN', log_levels.WARN) --- Sets the current log level. ---@param level (string|integer) One of `vim.lsp.log.levels` @@ -163,7 +169,7 @@ end --- Checks whether the level is sufficient for logging. ---@param level integer log level ----@returns (bool) true if would log, false if not +---@return bool : true if would log, false if not function log.should_log(level) return level >= current_log_level end |