From fbf6441f3d713b03fa3877a917f4a4dcef6d9362 Mon Sep 17 00:00:00 2001 From: Smirnov Oleksandr Date: Sat, 15 Jun 2024 12:18:26 +0300 Subject: [PATCH] feat: add logger (#64) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(health): keep in mind new way of health check (#63) * feat(log): add logger module * refactor(utils): remove unused code * refactor(log, utils): get plugin name from config * refactor(logger): add some type annotations * refactor(utils): log notifications * feat: LOGGERâ„¢ * feat(config): TYPES * refactor(log): dont give a thing about var that is not even declared * feat(log): add easy way to open log * refactor(log): some types * update types * docs: regen * fix(log): make setting log level by config work * feat(iferr): write error to log file if occur * feat(gotests): add logger --- doc/gopher.nvim.txt | 14 +-- lua/gopher/_utils/init.lua | 19 ++--- lua/gopher/_utils/log.lua | 170 +++++++++++++++++++++++++++++++++++++ lua/gopher/comment.lua | 4 + lua/gopher/config.lua | 13 +++ lua/gopher/gotests.lua | 3 + lua/gopher/iferr.lua | 2 + lua/gopher/init.lua | 8 +- plugin/gopher.vim | 1 + spec/units/utils_spec.lua | 10 --- 10 files changed, 213 insertions(+), 31 deletions(-) create mode 100644 lua/gopher/_utils/log.lua diff --git a/doc/gopher.nvim.txt b/doc/gopher.nvim.txt index 7118e3c..d4a82d0 100644 --- a/doc/gopher.nvim.txt +++ b/doc/gopher.nvim.txt @@ -20,13 +20,15 @@ Table of Contents ------------------------------------------------------------------------------ *gopher.nvim-setup* - `gopher.setup` + `gopher.setup`({user_config}) Setup function. This method simply merges default configs with opts table. You can read more about configuration at |gopher.nvim-config| Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| Usage ~ `require("gopher").setup {}` (replace `{}` with your `config` table) +Parameters ~ +{user_config} gopher.Config ------------------------------------------------------------------------------ *gopher.nvim-install-deps* @@ -50,6 +52,10 @@ You can look at default options |gopher.nvim-config-defaults| local default_config = { --minidoc_replace_end + -- log level, you might consider using DEBUG or TRACE for degugging the plugin + ---@type number + log_level = vim.log.levels.INFO, + -- user specified paths to binaries ---@class gopher.ConfigCommand commands = { @@ -81,12 +87,6 @@ You can look at default options |gopher.nvim-config-defaults| Class ~ {gopher.Config} ------------------------------------------------------------------------------- - *config.setup()* - `config.setup`({user_config}) -Parameters ~ -{user_config} `(optional)` gopher.Config - ============================================================================== ------------------------------------------------------------------------------ diff --git a/lua/gopher/_utils/init.lua b/lua/gopher/_utils/init.lua index 136717d..a3b567c 100644 --- a/lua/gopher/_utils/init.lua +++ b/lua/gopher/_utils/init.lua @@ -1,23 +1,15 @@ +local c = require "gopher.config" +local log = require "gopher._utils.log" local utils = {} -local TITLE = "gopher.nvim" - ----@param t table ----@return boolean -function utils.is_tbl_empty(t) - if t == nil then - return true - end - return next(t) == nil -end - ---@param msg string ---@param lvl number function utils.deferred_notify(msg, lvl) vim.defer_fn(function() vim.notify(msg, lvl, { - title = TITLE, + title = c.___plugin_name, }) + log.debug(msg) end, 0) end @@ -26,8 +18,9 @@ end function utils.notify(msg, lvl) lvl = lvl or vim.log.levels.INFO vim.notify(msg, lvl, { - title = TITLE, + title = c.___plugin_name, }) + log.debug(msg) end -- safe require diff --git a/lua/gopher/_utils/log.lua b/lua/gopher/_utils/log.lua new file mode 100644 index 0000000..c7dccd7 --- /dev/null +++ b/lua/gopher/_utils/log.lua @@ -0,0 +1,170 @@ +-- thanks https://github.com/tjdevries/vlog.nvim +-- and https://github.com/williamboman/mason.nvim +-- for the code i have stolen(or have inspected by idk) +local c = require "gopher.config" + +---@class Gopher.Logger +---@field get_outfile fun():string +---@field trace fun(...) +---@field fmt_trace fun(...) +---@field debug fun(...) +---@field fmt_debug fun(...) +---@field info fun(...) +---@field fmt_info fun(...) +---@field warn fun(...) +---@field fmt_warn fun(...) +---@field error fun(...) +---@field fmt_error fun(...) + +local config = { + -- Name of the plugin. Prepended to log messages + name = c.___plugin_name, + + -- Should print the output to neovim while running + -- values: 'sync','async',false + use_console = vim.env.GOPHER_VERBOSE_LOGS == "1", + + -- Should highlighting be used in console (using echohl) + highlights = true, + + -- Should write to a file + use_file = true, + + -- Level configuration + modes = { + { name = "trace", hl = "Comment", level = vim.log.levels.TRACE }, + { name = "debug", hl = "Comment", level = vim.log.levels.DEBUG }, + { name = "info", hl = "None", level = vim.log.levels.INFO }, + { name = "warn", hl = "WarningMsg", level = vim.log.levels.WARN }, + { name = "error", hl = "ErrorMsg", level = vim.log.levels.ERROR }, + }, + + -- Can limit the number of decimals displayed for floats + float_precision = 0.01, +} + +---@type Gopher.Logger +---@diagnostic disable-next-line: missing-fields +local log = {} + +---@return string +function log.get_outfile() + return table.concat { + (vim.fn.has "nvim-0.8.0" == 1) and vim.fn.stdpath "log" or vim.fn.stdpath "cache", + ("/%s.log"):format(config.name), + } +end + +-- selene: allow(incorrect_standard_library_use) +local unpack = unpack or table.unpack + +do + local round = function(x, increment) + increment = increment or 1 + x = x / increment + return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment + end + + local tbl_has_tostring = function(tbl) + local mt = getmetatable(tbl) + return mt and mt.__tostring ~= nil + end + + local make_string = function(...) + local t = {} + for i = 1, select("#", ...) do + local x = select(i, ...) + + if type(x) == "number" and config.float_precision then + x = tostring(round(x, config.float_precision)) + elseif type(x) == "table" and not tbl_has_tostring(x) then + x = vim.inspect(x) + else + x = tostring(x) + end + + t[#t + 1] = x + end + return table.concat(t, " ") + end + + local log_at_level = function(level_config, message_maker, ...) + -- Return early if we're below the current_log_level + -- + -- the log level source get from config directly because otherwise it doesnt work + if level_config.level < c.log_level then + return + end + local nameupper = level_config.name:upper() + + local msg = message_maker(...) + local info = debug.getinfo(2, "Sl") + local lineinfo = info.short_src .. ":" .. info.currentline + + -- Output to console + if config.use_console then + local log_to_console = function() + local console_string = + string.format("[%-6s%s] %s: %s", nameupper, os.date "%H:%M:%S", lineinfo, msg) + + if config.highlights and level_config.hl then + vim.cmd(string.format("echohl %s", level_config.hl)) + end + + local split_console = vim.split(console_string, "\n") + for _, v in ipairs(split_console) do + local formatted_msg = string.format("[%s] %s", config.name, vim.fn.escape(v, [["\]])) + + ---@diagnostic disable-next-line: param-type-mismatch + local ok = pcall(vim.cmd, string.format([[echom "%s"]], formatted_msg)) + if not ok then + vim.api.nvim_out_write(msg .. "\n") + end + end + + if config.highlights and level_config.hl then + vim.cmd "echohl NONE" + end + end + if config.use_console == "sync" and not vim.in_fast_event() then + log_to_console() + else + vim.schedule(log_to_console) + end + end + + -- Output to log file + if config.use_file then + local fp = assert(io.open(log.get_outfile(), "a")) + local str = string.format("[%-6s%s] %s: %s\n", nameupper, os.date(), lineinfo, msg) + fp:write(str) + fp:close() + end + end + + for _, x in ipairs(config.modes) do + -- log.info("these", "are", "separated") + log[x.name] = function(...) ---@diagnostic disable-line: assign-type-mismatch + return log_at_level(x, make_string, ...) + end + + -- log.fmt_info("These are %s strings", "formatted") + log[("fmt_%s"):format(x.name)] = function(...) ---@diagnostic disable-line: assign-type-mismatch + return log_at_level(x, function(...) + local passed = { ... } + local fmt = table.remove(passed, 1) + local inspected = {} + for _, v in ipairs(passed) do + if type(v) == "table" and tbl_has_tostring(v) then + table.insert(inspected, v) + else + table.insert(inspected, vim.inspect(v)) + end + end + return string.format(fmt, unpack(inspected)) + end, ...) + end + end +end + +return log diff --git a/lua/gopher/comment.lua b/lua/gopher/comment.lua index 002894c..8754405 100644 --- a/lua/gopher/comment.lua +++ b/lua/gopher/comment.lua @@ -3,6 +3,8 @@ ---@usage Execute `:GoCmt` to generate a comment for the current function/method/struct/etc on this line. ---@text This module provides a way to generate comments for Go code. +local log = require "gopher._utils.log" + local function generate(row, col) local ts_utils = require "gopher._utils.ts" local comment, ns = nil, nil @@ -38,6 +40,8 @@ return function() local row, col = unpack(vim.api.nvim_win_get_cursor(0)) local comment, ns = generate(row + 1, col + 1) + log.debug("generated comment: " .. comment) + vim.api.nvim_win_set_cursor(0, { ns.dim.s.r, ns.dim.s.c, diff --git a/lua/gopher/config.lua b/lua/gopher/config.lua index c0a0dcc..9c20843 100644 --- a/lua/gopher/config.lua +++ b/lua/gopher/config.lua @@ -29,6 +29,10 @@ local config = {} local default_config = { --minidoc_replace_end + -- log level, you might consider using DEBUG or TRACE for degugging the plugin + ---@type number + log_level = vim.log.levels.INFO, + -- user specified paths to binaries ---@class gopher.ConfigCommand commands = { @@ -62,7 +66,16 @@ local default_config = { ---@private local _config = default_config +-- I am kinda secret so don't tell anyone about me +-- even dont use me +-- +-- if you don't belive me that i am secret see +-- the line below it says @private +---@private +_config.___plugin_name = "gopher.nvim" ---@diagnostic disable-line: inject-field + ---@param user_config? gopher.Config +---@private function config.setup(user_config) _config = vim.tbl_deep_extend("force", default_config, user_config or {}) end diff --git a/lua/gopher/gotests.lua b/lua/gopher/gotests.lua index 3d96a05..da4753d 100644 --- a/lua/gopher/gotests.lua +++ b/lua/gopher/gotests.lua @@ -43,6 +43,7 @@ local c = require "gopher.config" local ts_utils = require "gopher._utils.ts" local r = require "gopher._utils.runner" local u = require "gopher._utils" +local log = require "gopher._utils.log" local gotests = {} ---@param args table @@ -65,6 +66,8 @@ local function add_test(args) table.insert(args, "-w") table.insert(args, vim.fn.expand "%") + log.debug("generating tests with args: ", args) + return r.sync(c.commands.gotests, { args = args, on_exit = function(data, status) diff --git a/lua/gopher/iferr.lua b/lua/gopher/iferr.lua index 56e9644..a1cdf9f 100644 --- a/lua/gopher/iferr.lua +++ b/lua/gopher/iferr.lua @@ -4,6 +4,7 @@ ---@usage execute `:GoIfErr` near any err variable to insert the check local c = require "gopher.config" +local log = require "gopher._utils.log" local iferr = {} -- That's Lua implementation: github.com/koron/iferr @@ -14,6 +15,7 @@ function iferr.iferr() local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%") if vim.v.shell_error ~= 0 then error("iferr failed: " .. data) + log.error("failed. output: " .. data) end vim.fn.append(pos, data) diff --git a/lua/gopher/init.lua b/lua/gopher/init.lua index df0d174..2aa138e 100644 --- a/lua/gopher/init.lua +++ b/lua/gopher/init.lua @@ -9,6 +9,7 @@ ---@tag gopher.nvim-table-of-contents ---@toc +local log = require "gopher._utils.log" local tags = require "gopher.struct_tags" local tests = require "gopher.gotests" local gocmd = require("gopher._utils.runner.gocmd").run @@ -21,7 +22,12 @@ local gopher = {} --- Calling this function is optional, if you ok with default settings. Look |gopher.nvim.config-defaults| --- ---@usage `require("gopher").setup {}` (replace `{}` with your `config` table) -gopher.setup = require("gopher.config").setup +---@param user_config gopher.Config +gopher.setup = function(user_config) + log.debug "setting up config" + require("gopher.config").setup(user_config) + log.debug(vim.inspect(user_config)) +end ---@toc_entry Install dependencies ---@tag gopher.nvim-install-deps diff --git a/plugin/gopher.vim b/plugin/gopher.vim index 997ec94..a219a1c 100644 --- a/plugin/gopher.vim +++ b/plugin/gopher.vim @@ -11,3 +11,4 @@ command! -nargs=* GoGenerate :lua require"gopher".generate() command! GoCmt :lua require"gopher".comment() command! GoIfErr :lua require"gopher".iferr() command! GoInstallDeps :lua require"gopher".install_deps() +command! GopherLog :lua vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile()) diff --git a/spec/units/utils_spec.lua b/spec/units/utils_spec.lua index dcf94f2..ea2f30c 100644 --- a/spec/units/utils_spec.lua +++ b/spec/units/utils_spec.lua @@ -1,16 +1,6 @@ describe("gopher._utils", function() local u = require "gopher._utils" - describe(".is_tbl_empty()", function() - it("it is empty", function() - assert.are.same(true, u.is_tbl_empty {}) - end) - - it("it is not empty", function() - assert.are.same(false, u.is_tbl_empty { first = "1", second = 2 }) - end) - end) - describe(".sreq()", function() it("can require existing module", function() assert.are.same(require "gopher", u.sreq "gopher")