Skip to content

Commit f14ac20

Browse files
authored
fix(log): close log file later (#1883)
1 parent 9130e58 commit f14ac20

File tree

2 files changed

+90
-37
lines changed

2 files changed

+90
-37
lines changed

lua/neo-tree/health/init.lua

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ function M.check_config(config)
6868
end
6969
end
7070
end,
71+
---@generic T
72+
---@param literals T[]
73+
---@return fun(a: T):boolean
7174
literal = function(literals)
7275
return function(value)
7376
return vim.tbl_contains(literals, value),
@@ -76,6 +79,20 @@ function M.check_config(config)
7679
end,
7780
}
7881
local schema = {
82+
LogLevel = v.literal({
83+
"trace",
84+
"debug",
85+
"info",
86+
"warn",
87+
"error",
88+
"fatal",
89+
vim.log.levels.TRACE,
90+
vim.log.levels.DEBUG,
91+
vim.log.levels.INFO,
92+
vim.log.levels.WARN,
93+
vim.log.levels.ERROR,
94+
vim.log.levels.ERROR + 1,
95+
}),
7996
Filesystem = {
8097
---@param follow_current_file neotree.Config.Filesystem.FollowCurrentFile
8198
FollowCurrentFile = function(follow_current_file)
@@ -104,6 +121,17 @@ function M.check_config(config)
104121
},
105122
Renderers = v.array("table"),
106123
}
124+
---@param log_level neotree.Logger.Config.Level
125+
schema.ConfigLogLevel = function(log_level)
126+
if type(log_level) == "table" then
127+
return validate("log_level", log_level, function(ll)
128+
validate("console", ll.console, schema.LogLevel)
129+
validate("file", ll.file, schema.LogLevel)
130+
end)
131+
else
132+
validate("log_level", log_level, schema.LogLevel)
133+
end
134+
end
107135

108136
if not validate("config", cfg, "table") then
109137
health.error("Config does not exist")
@@ -129,12 +157,7 @@ function M.check_config(config)
129157
end)
130158
validate("hide_root_node", cfg.hide_root_node, "boolean")
131159
validate("retain_hidden_root_indent", cfg.retain_hidden_root_indent, "boolean")
132-
validate(
133-
"log_level",
134-
cfg.log_level,
135-
v.literal({ "trace", "debug", "info", "warn", "error", "fatal" }),
136-
true
137-
)
160+
validate("log_level", cfg.log_level, schema.ConfigLogLevel, true)
138161
validate("log_to_file", cfg.log_to_file, { "boolean", "string" })
139162
validate("open_files_in_last_window", cfg.open_files_in_last_window, "boolean")
140163
validate(

lua/neo-tree/log.lua

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ local default_config = {
7878

7979
local log_maker = {}
8080

81+
---@param x number
82+
---@param increment number
83+
---@return number rounded
84+
local round = function(x, increment)
85+
increment = increment or 1
86+
x = x / increment
87+
return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment
88+
end
89+
8190
---@class (partial) neotree.Logger.PartialConfig : neotree.Logger.Config
8291
---@param config neotree.Logger.PartialConfig|neotree.Logger.Config
8392
---@return neotree.Logger
@@ -113,12 +122,6 @@ log_maker.new = function(config)
113122
log.use_file(initial_filepath)
114123
end
115124

116-
local round = function(x, increment)
117-
increment = increment or 1
118-
x = x / increment
119-
return (x > 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)) * increment
120-
end
121-
122125
local last_logfile_check_time = 0
123126
local current_logfile_inode = -1
124127
local logfile_check_interval = 20 -- TODO: probably use filesystem events rather than this
@@ -127,36 +130,34 @@ log_maker.new = function(config)
127130
---@param log_type string
128131
---@param msg string
129132
local log_to_file = function(log_type, msg)
133+
if not log.file then
134+
vim.schedule(function()
135+
vim.notify_once("[neo-tree] Could not open log file: " .. log.outfile)
136+
end)
137+
return
138+
end
130139
local info = debug.getinfo(3, "Sl")
131140
local lineinfo = info.short_src .. ":" .. info.currentline
132-
local str =
133-
string.format("[%-6s%s] %s%s: %s\n", log_type, os.date("%F-%T"), prefix, lineinfo, msg)
134-
if log.file and assert(log.file:write(str)) then
135-
local curtime = os.time()
136-
-- make sure the file is valid every so often
137-
if os.difftime(curtime, last_logfile_check_time) >= logfile_check_interval then
138-
last_logfile_check_time = curtime
139-
log.use_file(log.outfile, true)
140-
end
141-
return
141+
local str = ("[%-6s%s] %s%s: %s\n"):format(log_type, os.date("%F-%T"), prefix, lineinfo, msg)
142+
local _, writeerr = log.file:write(str)
143+
if writeerr then
144+
-- Assume that subsequent writes will fail too, so stop logging to file.
145+
log.use_file(false, true)
146+
log.error("Error writing to log:", writeerr)
147+
log.file:close()
142148
end
143149

144-
vim.schedule(function()
145-
vim.notify_once("[neo-tree] Could not open log file: " .. log.outfile)
146-
end)
150+
local curtime = os.time()
151+
-- make sure the file is valid every so often
152+
if os.difftime(curtime, last_logfile_check_time) >= logfile_check_interval then
153+
last_logfile_check_time = curtime
154+
log.use_file(log.outfile, true)
155+
end
147156
end
148157

149158
---@type { file: vim.log.levels, console: vim.log.levels }
150159
log.minimum_level = nil
151160

152-
vim.api.nvim_create_autocmd("VimLeavePre", {
153-
callback = function()
154-
if log.file then
155-
log.file:close()
156-
end
157-
end,
158-
})
159-
160161
local make_string = function(...)
161162
local tbl = {}
162163
for i = 1, select("#", ...) do
@@ -186,27 +187,33 @@ log_maker.new = function(config)
186187
---@param log_level vim.log.levels
187188
---@param message_maker fun(...):string
188189
local logfunc = function(log_level, message_maker)
189-
if log_level < log.minimum_level.file and log_level < log.minimum_level.console then
190+
local can_log_to_file = log_level >= log.minimum_level.file
191+
local can_log_to_console = log_level >= log.minimum_level.console
192+
if not can_log_to_file and not can_log_to_console then
190193
return function() end
191194
end
195+
192196
local level_config = config.level_configs[log_level]
193197
local name_upper = level_config.name:upper()
194198
return function(...)
195199
-- Return early if we're below the config.level
196200
-- Ignore this if vim is exiting
197201
if vim.v.dying > 0 or vim.v.exiting ~= vim.NIL then
198-
return
202+
if log.file then
203+
config.use_file = false
204+
log.file:close()
205+
end
199206
end
200207

201208
local msg = message_maker(...)
202209

203210
-- Output to log file
204-
if config.use_file and log_level >= log.minimum_level.file then
211+
if config.use_file and can_log_to_file then
205212
log_to_file(name_upper, msg)
206213
end
207214

208215
-- Output to console
209-
if config.use_console and log_level >= log.minimum_level.console then
216+
if config.use_console and can_log_to_console then
210217
vim.schedule(function()
211218
notify(msg, log_level)
212219
end)
@@ -252,30 +259,47 @@ log_maker.new = function(config)
252259
---@field name string
253260
---@field hl string
254261

262+
---Log trace-level information.
255263
log.trace = logfunc(Levels.TRACE, make_string)
264+
---Log debug information.
256265
log.debug = logfunc(Levels.DEBUG, make_string)
266+
---Log useful information/UI feedback.
257267
log.info = logfunc(Levels.INFO, make_string)
268+
---Log a warning.
258269
log.warn = logfunc(Levels.WARN, make_string)
270+
---Log at an "error" level. Doesn't actually raise an error.
259271
log.error = logfunc(Levels.ERROR, make_string)
272+
---Unused, kept around for compatibility at the moment. Remove in v4.0.
260273
log.fatal = logfunc(Levels.FATAL, make_string)
261274
-- tree-sitter queries recognize any .format and highlight it w/ string.format highlights
275+
---@type table<string, { format: fun(fmt: string?, ...: any) }>
262276
log.at = {
263277
trace = {
278+
---Log trace-level information, but like string.format.
279+
---@see string.format
264280
format = logfunc(Levels.TRACE, string.format),
265281
},
266282
debug = {
283+
---Log debug information, but like string.format.
284+
---@see string.format
267285
format = logfunc(Levels.DEBUG, string.format),
268286
},
269287
info = {
288+
---Log useful information/UI feedback, but like string.format.
289+
---@see string.format
270290
format = logfunc(Levels.INFO, string.format),
271291
},
272292
warn = {
293+
---Log a warning, but like string.format.
294+
---@see string.format
273295
format = logfunc(Levels.WARN, string.format),
274296
},
275297
error = {
298+
---Log an error, but like string.format.
276299
format = logfunc(Levels.ERROR, string.format),
277300
},
278301
fatal = {
302+
---Unused, kept around for compatibility at the moment. Remove in v4.0.
279303
format = logfunc(Levels.FATAL, string.format),
280304
},
281305
}
@@ -295,6 +319,8 @@ log_maker.new = function(config)
295319
return config.use_file
296320
end
297321
log.outfile = type(file) == "string" and file or initial_filepath
322+
log.outfile = vim.fn.expand(log.outfile)
323+
log.outfile = vim.fn.fnamemodify(log.outfile, ":p")
298324
local fp, err = io.open(log.outfile, "a+")
299325

300326
if not fp then
@@ -307,10 +333,14 @@ log_maker.new = function(config)
307333
if not stat then
308334
config.use_file = false
309335
log.warn("Could not stat log file:", log.outfile, stat_err)
336+
fp:close()
310337
return config.use_file
311338
end
312339

313340
if stat.ino ~= current_logfile_inode then
341+
if log.file then
342+
log.file:close()
343+
end
314344
-- the fp is pointing to a different file
315345
log.file = fp
316346
log.file:setvbuf("line")

0 commit comments

Comments
 (0)