|
2 | 2 | --- |
3 | 3 | ---@brief [[ |
4 | 4 | --- |
5 | | ----Logging library for rest.nvim, slightly inspired by rmagatti/logger.nvim |
| 5 | +---Logging library for rest.nvim, inspired by nvim-neorocks/rocks.nvim |
6 | 6 | ---Intended for use by internal and third-party modules. |
7 | 7 | --- |
8 | | ----Default logger instance is made during the `setup` and can be accessed |
9 | | ----by anyone through the `_G._rest_nvim.logger` configuration field |
10 | | ----that is set automatically. |
11 | | ---- |
12 | | ---------------------------------------------------------------------------------- |
13 | | ---- |
14 | | ----Usage: |
15 | | ---- |
16 | | ----```lua |
17 | | ----local logger = require("rest-nvim.logger"):new({ level = "debug" }) |
18 | | ---- |
19 | | ----logger:set_log_level("info") |
20 | | ---- |
21 | | ----logger:info("This is an info log") |
22 | | ---- -- [rest.nvim] INFO: This is an info log |
23 | | ----``` |
24 | | ---- |
25 | 8 | ---@brief ]] |
26 | 9 |
|
27 | | ----@class Logger |
28 | 10 | local logger = {} |
29 | 11 |
|
30 | | --- NOTE: vim.loop has been renamed to vim.uv in Neovim >= 0.10 and will be removed later |
31 | | -local uv = vim.uv or vim.loop |
| 12 | +---@type fun(any) |
| 13 | +function logger.trace(_) end |
| 14 | +---@type fun(any) |
| 15 | +function logger.debug(_) end |
| 16 | +---@type fun(any) |
| 17 | +function logger.info(_) end |
| 18 | +---@type fun(any) |
| 19 | +function logger.warn(_) end |
| 20 | +---@type fun(any) |
| 21 | +function logger.error(_) end |
32 | 22 |
|
33 | | ----@see vim.log.levels |
34 | | ----@class LoggerLevels |
35 | | -local levels = { |
36 | | - trace = vim.log.levels.TRACE, |
37 | | - debug = vim.log.levels.DEBUG, |
38 | | - info = vim.log.levels.INFO, |
39 | | - warn = vim.log.levels.WARN, |
40 | | - error = vim.log.levels.ERROR, |
41 | | -} |
| 23 | +local default_log_path = vim.fn.stdpath("log") --[[@as string]] |
42 | 24 |
|
43 | | ----@class LoggerConfig |
44 | | ----@field level_name string Logging level name. Default is `"info"` |
45 | | ----@field save_logs boolean Whether to save log messages into a `.log` file. Default is `true` |
46 | | -local default_config = { |
47 | | - level_name = "info", |
48 | | - save_logs = true, |
49 | | -} |
| 25 | +local LARGE = 1e9 |
50 | 26 |
|
51 | | -local default_log_path = vim.fn.stdpath("log") --[[@as string]] |
| 27 | +local log_date_format = "%F %H:%M:%S" |
52 | 28 |
|
53 | 29 | ---Get the rest.nvim log file path. |
54 | 30 | ---@package |
55 | 31 | ---@return string filepath |
56 | 32 | function logger.get_logfile() |
57 | | - return vim.fs.joinpath(default_log_path, "rest-nvim.log") |
58 | | -end |
59 | | - |
60 | | ----Store the logger output in a file at `vim.fn.stdpath("log")` |
61 | | ----@see vim.fn.stdpath |
62 | | ----@param msg string Logger message to be saved |
63 | | -local function store_log(msg) |
64 | | - local date = os.date("%F %r") -- 2024-01-26 01:25:05 PM |
65 | | - local log_msg = date .. " | " .. msg .. "\n" |
66 | | - local log_path = logger.get_logfile() |
67 | | - |
68 | | - -- 644 sets read and write permissions for the owner, and it sets read-only |
69 | | - -- mode for the group and others |
70 | | - uv.fs_open(log_path, "a+", tonumber(644, 8), function(err, file) |
71 | | - if file and not err then |
72 | | - local file_pipe = uv.new_pipe(false) |
73 | | - ---@cast file_pipe uv_pipe_t |
74 | | - uv.pipe_open(file_pipe, file) |
75 | | - uv.write(file_pipe, log_msg) |
76 | | - uv.fs_close(file) |
77 | | - end |
78 | | - end) |
| 33 | + return vim.fs.joinpath(default_log_path, "rest-nvim.log") |
79 | 34 | end |
80 | 35 |
|
81 | | ----Create a new logger instance |
82 | | ----@param opts LoggerConfig Logger configuration |
83 | | ----@return Logger |
84 | | -function logger:new(opts) |
85 | | - opts = opts or {} |
86 | | - local conf = vim.tbl_deep_extend("force", default_config, opts) |
87 | | - self.level = levels[conf.level_name] |
88 | | - self.save_logs = conf.save_logs |
89 | | - |
90 | | - self.__index = function(_, index) |
91 | | - if type(self[index]) == "function" then |
92 | | - return function(...) |
93 | | - -- Make any logger function call with "." access result in the syntactic sugar ":" access |
94 | | - self[index](self, ...) |
95 | | - end |
96 | | - else |
97 | | - return self[index] |
98 | | - end |
| 36 | +local logfile, openerr |
| 37 | +---@private |
| 38 | +---Opens log file. Returns true if file is open, false on error |
| 39 | +---@return boolean |
| 40 | +local function open_logfile() |
| 41 | + -- Try to open file only once |
| 42 | + if logfile then |
| 43 | + return true |
99 | 44 | end |
100 | | - setmetatable(opts, self) |
101 | | - |
102 | | - return self |
103 | | -end |
104 | | - |
105 | | ----Set the log level for the logger |
106 | | ----@param level string New logging level |
107 | | ----@see vim.log.levels |
108 | | -function logger:set_log_level(level) |
109 | | - self.level = levels[level] |
110 | | -end |
111 | | - |
112 | | ----Log a trace message |
113 | | ----@param msg string Log message |
114 | | -function logger:trace(msg) |
115 | | - msg = "[rest.nvim] TRACE: " .. msg |
116 | | - if self.level == vim.log.levels.TRACE then |
117 | | - vim.notify(msg, levels.trace) |
| 45 | + if openerr then |
| 46 | + return false |
118 | 47 | end |
119 | 48 |
|
120 | | - if self.save_logs then |
121 | | - store_log(msg) |
| 49 | + vim.fn.mkdir(default_log_path, "-p") |
| 50 | + logfile, openerr = io.open(logger.get_logfile(), "w+") |
| 51 | + if not logfile then |
| 52 | + local err_msg = string.format("Failed to open rest.nvim log file: %s", openerr) |
| 53 | + vim.notify(err_msg, vim.log.levels.ERROR) |
| 54 | + return false |
122 | 55 | end |
123 | | -end |
124 | 56 |
|
125 | | ----Log a debug message |
126 | | ----@param msg string Log message |
127 | | -function logger:debug(msg) |
128 | | - msg = "[rest.nvim] DEBUG: " .. msg |
129 | | - if self.level == vim.log.levels.DEBUG then |
130 | | - vim.notify(msg, levels.debug) |
| 57 | + local log_info = vim.uv.fs_stat(logger.get_logfile()) |
| 58 | + if log_info and log_info.size > LARGE then |
| 59 | + local warn_msg = |
| 60 | + string.format("rest.nvim log is large (%d MB): %s", log_info.size / (1000 * 1000), logger.get_logfile()) |
| 61 | + vim.notify(warn_msg, vim.log.levels.WARN) |
131 | 62 | end |
132 | 63 |
|
133 | | - if self.save_logs then |
134 | | - store_log(msg) |
135 | | - end |
| 64 | + -- Start message for logging |
| 65 | + logfile:write(string.format("[START][%s] rest.nvim logging initiated\n", os.date(log_date_format))) |
| 66 | + return true |
136 | 67 | end |
137 | 68 |
|
138 | | ----Log an info message |
139 | | ----@param msg string Log message |
140 | | -function logger:info(msg) |
141 | | - msg = "[rest.nvim] INFO: " .. msg |
142 | | - local valid_levels = { vim.log.levels.INFO, vim.log.levels.DEBUG } |
143 | | - if vim.tbl_contains(valid_levels, self.level) then |
144 | | - vim.notify(msg, levels.info) |
145 | | - end |
146 | | - |
147 | | - if self.save_logs then |
148 | | - store_log(msg) |
149 | | - end |
| 69 | +local log_levels = vim.deepcopy(vim.log.levels) |
| 70 | +for levelstr, levelnr in pairs(log_levels) do |
| 71 | + log_levels[levelnr] = levelstr |
150 | 72 | end |
151 | 73 |
|
152 | | ----Log a warning message |
153 | | ----@param msg string Log message |
154 | | -function logger:warn(msg) |
155 | | - msg = "[rest.nvim] WARN: " .. msg |
156 | | - local valid_levels = { vim.log.levels.INFO, vim.log.levels.DEBUG, vim.log.levels.WARN } |
157 | | - if vim.tbl_contains(valid_levels, self.level) then |
158 | | - vim.notify(msg, levels.warn) |
159 | | - end |
160 | | - |
161 | | - if self.save_logs then |
162 | | - store_log(msg) |
| 74 | +---Set the log level for the logger |
| 75 | +---@param level (string|integer) New logging level |
| 76 | +---@see vim.log.levels |
| 77 | +function logger.set_log_level(level) |
| 78 | + if type(level) == "string" then |
| 79 | + logger.level = assert(log_levels[level:upper()], string.format("rest.nvim: Invalid log level: %q", level)) |
| 80 | + else |
| 81 | + assert(log_levels[level], string.format("rest.nvim: Invalid log level: %d", level)) |
| 82 | + logger.level = level |
163 | 83 | end |
164 | 84 | end |
165 | 85 |
|
166 | | ----Log an error message |
167 | | ----@param msg string Log message |
168 | | -function logger:error(msg) |
169 | | - msg = "[rest.nvim] ERROR: " .. msg |
170 | | - vim.notify(msg, levels.error) |
171 | | - |
172 | | - if self.save_logs then |
173 | | - store_log(msg) |
| 86 | +for level, levelnr in pairs(vim.log.levels) do |
| 87 | + logger[level:lower()] = function(...) |
| 88 | + if logger.level == vim.log.levels.OFF or not open_logfile() then |
| 89 | + return false |
| 90 | + end |
| 91 | + local argc = select("#", ...) |
| 92 | + if levelnr < logger.level then |
| 93 | + return false |
| 94 | + end |
| 95 | + if argc == 0 then |
| 96 | + return true |
| 97 | + end |
| 98 | + local info = debug.getinfo(2, "Sl") |
| 99 | + local fileinfo = string.format("%s:%s", info.short_src, info.currentline) |
| 100 | + local parts = { level, "|", os.date(log_date_format), "|", fileinfo, "|" } |
| 101 | + for i = 1, argc do |
| 102 | + local arg = select(i, ...) |
| 103 | + if arg == nil then |
| 104 | + table.insert(parts, "<nil>") |
| 105 | + elseif type(arg) == "string" then |
| 106 | + table.insert(parts, arg) |
| 107 | + else |
| 108 | + table.insert(parts, vim.inspect(arg)) |
| 109 | + end |
| 110 | + end |
| 111 | + logfile:write(table.concat(parts, " "), "\n") |
| 112 | + logfile:flush() |
174 | 113 | end |
175 | 114 | end |
176 | 115 |
|
| 116 | +logger.set_log_level(vim.tbl_get(_G, "_rest_nvim", "_log_level") or vim.log.levels.WARN) |
| 117 | + |
177 | 118 | return logger |
0 commit comments