log.lua 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. local Log = {}
  2. Log.levels = {
  3. TRACE = 1,
  4. DEBUG = 2,
  5. INFO = 3,
  6. WARN = 4,
  7. ERROR = 5,
  8. }
  9. vim.tbl_add_reverse_lookup(Log.levels)
  10. local notify_opts = {}
  11. local log_notify_as_notification = false
  12. function Log:set_level(level)
  13. local logger_ok, logger = pcall(function()
  14. return require("structlog").get_logger "lvim"
  15. end)
  16. local log_level = Log.levels[level:upper()]
  17. if logger_ok and logger and log_level then
  18. for _, pipeline in ipairs(logger.pipelines) do
  19. pipeline.level = log_level
  20. end
  21. end
  22. end
  23. function Log:init()
  24. local status_ok, structlog = pcall(require, "structlog")
  25. if not status_ok then
  26. return nil
  27. end
  28. local log_level = Log.levels[(lvim.log.level):upper() or "WARN"]
  29. structlog.configure {
  30. lvim = {
  31. pipelines = {
  32. {
  33. level = log_level,
  34. processors = {
  35. structlog.processors.StackWriter({ "line", "file" }, { max_parents = 0, stack_level = 2 }),
  36. structlog.processors.Timestamper "%H:%M:%S",
  37. },
  38. formatter = structlog.formatters.FormatColorizer( --
  39. "%s [%-5s] %s: %-30s",
  40. { "timestamp", "level", "logger_name", "msg" },
  41. { level = structlog.formatters.FormatColorizer.color_level() }
  42. ),
  43. sink = structlog.sinks.Console(false), -- async=false
  44. },
  45. {
  46. level = log_level,
  47. processors = {
  48. structlog.processors.StackWriter({ "line", "file" }, { max_parents = 3, stack_level = 2 }),
  49. structlog.processors.Timestamper "%F %H:%M:%S",
  50. },
  51. formatter = structlog.formatters.Format( --
  52. "%s [%-5s] %s: %-30s",
  53. { "timestamp", "level", "logger_name", "msg" }
  54. ),
  55. sink = structlog.sinks.File(self:get_path()),
  56. },
  57. },
  58. },
  59. }
  60. local logger = structlog.get_logger "lvim"
  61. -- Overwrite `vim.notify` to use the logger
  62. if lvim.log.override_notify then
  63. vim.notify = function(msg, vim_log_level, opts)
  64. notify_opts = opts or {}
  65. -- vim_log_level can be omitted
  66. if vim_log_level == nil then
  67. vim_log_level = Log.levels["INFO"]
  68. elseif type(vim_log_level) == "string" then
  69. vim_log_level = Log.levels[(vim_log_level):upper()] or Log.levels["INFO"]
  70. else
  71. -- https://github.com/neovim/neovim/blob/685cf398130c61c158401b992a1893c2405cd7d2/runtime/lua/vim/lsp/log.lua#L5
  72. vim_log_level = vim_log_level + 1
  73. end
  74. self:add_entry(vim_log_level, msg)
  75. end
  76. end
  77. return logger
  78. end
  79. --- Configure the sink in charge of logging notifications
  80. ---@param nvim_notify table The nvim-notify instance
  81. function Log:configure_notifications(nvim_notify)
  82. local status_ok, structlog = pcall(require, "structlog")
  83. if not status_ok then
  84. return
  85. end
  86. local function log_writer(log)
  87. local opts = { title = log.logger_name }
  88. opts = vim.tbl_deep_extend("force", opts, notify_opts)
  89. notify_opts = {}
  90. if log_notify_as_notification then
  91. nvim_notify(log.msg, log.level, opts)
  92. end
  93. end
  94. local notif_pipeline = structlog.Pipeline(
  95. structlog.level.INFO,
  96. {},
  97. structlog.formatters.Format( --
  98. "%s",
  99. { "msg" },
  100. { blacklist_all = true }
  101. ),
  102. structlog.sinks.Adapter(log_writer)
  103. )
  104. self.__handle:add_pipeline(notif_pipeline)
  105. end
  106. --- Adds a log entry using Plenary.log
  107. ---@param level integer [same as vim.log.levels]
  108. ---@param msg any
  109. ---@param event any
  110. function Log:add_entry(level, msg, event)
  111. local logger = self:get_logger()
  112. if not logger then
  113. return
  114. end
  115. logger:log(level, vim.inspect(msg), event)
  116. end
  117. ---Retrieves the handle of the logger object
  118. ---@return table|nil logger handle if found
  119. function Log:get_logger()
  120. local logger_ok, logger = pcall(function()
  121. return require("structlog").get_logger "lvim"
  122. end)
  123. if logger_ok and logger then
  124. return logger
  125. end
  126. logger = self:init()
  127. if not logger then
  128. return
  129. end
  130. self.__handle = logger
  131. return logger
  132. end
  133. ---Retrieves the path of the logfile
  134. ---@return string path of the logfile
  135. function Log:get_path()
  136. return string.format("%s/%s.log", get_cache_dir(), "lvim")
  137. end
  138. ---Add a log entry at TRACE level
  139. ---@param msg any
  140. ---@param event any
  141. function Log:trace(msg, event)
  142. self:add_entry(self.levels.TRACE, msg, event)
  143. end
  144. ---Add a log entry at DEBUG level
  145. ---@param msg any
  146. ---@param event any
  147. function Log:debug(msg, event)
  148. self:add_entry(self.levels.DEBUG, msg, event)
  149. end
  150. ---Add a log entry at INFO level
  151. ---@param msg any
  152. ---@param event any
  153. function Log:info(msg, event)
  154. self:add_entry(self.levels.INFO, msg, event)
  155. end
  156. ---Add a log entry at WARN level
  157. ---@param msg any
  158. ---@param event any
  159. function Log:warn(msg, event)
  160. self:add_entry(self.levels.WARN, msg, event)
  161. end
  162. ---Add a log entry at ERROR level
  163. ---@param msg any
  164. ---@param event any
  165. function Log:error(msg, event)
  166. self:add_entry(self.levels.ERROR, msg, event)
  167. end
  168. setmetatable({}, Log)
  169. return Log