Skip to content

Commit 2124ae2

Browse files
committed
Log stderr of adapters to dedicated files; improve spawn error messages
1 parent a6070b4 commit 2124ae2

File tree

7 files changed

+311
-147
lines changed

7 files changed

+311
-147
lines changed

lua/dap.lua

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ local lazy = setmetatable({
2626
})
2727

2828

29+
---@return dap.log.Log
2930
local function log()
3031
return require('dap.log').create_logger('dap.log')
3132
end
@@ -429,25 +430,31 @@ local function add_reset_session_hook(lsession)
429430
end
430431

431432

432-
local function run_adapter(adapter, configuration, opts)
433-
local name = configuration.name or '[no name]'
434-
local options = adapter.options or {}
435-
opts = vim.tbl_extend('keep', opts, {
436-
cwd = options.cwd,
437-
env = options.env
438-
})
433+
---@param adapter dap.Adapter
434+
---@param config dap.Configuration
435+
---@param opts table
436+
local function run_adapter(adapter, config, opts)
437+
local name = config.name or '[no name]'
439438
if adapter.type == 'executable' then
440439
lazy.progress.report('Running: ' .. name)
441-
M.launch(adapter, configuration, opts)
440+
---@cast adapter dap.ExecutableAdapter
441+
local options = adapter.options or {}
442+
opts = vim.tbl_extend('keep', opts, {
443+
cwd = options.cwd,
444+
env = options.env
445+
})
446+
M.launch(adapter, config, opts)
442447
elseif adapter.type == 'server' then
443448
lazy.progress.report('Running: ' .. name)
444-
M.attach(adapter, configuration, opts)
449+
---@cast adapter dap.ServerAdapter
450+
M.attach(adapter, config, opts)
445451
elseif adapter.type == "pipe" then
446452
lazy.progress.report("Running: " .. name)
447453
local lsession
448-
lsession = require("dap.session").pipe(adapter, opts, function(err)
454+
---@cast adapter dap.PipeAdapter
455+
lsession = require("dap.session").pipe(adapter, config, opts, function(err)
449456
if not err then
450-
lsession:initialize(configuration)
457+
lsession:initialize(config)
451458
end
452459
end)
453460
add_reset_session_hook(lsession)
@@ -762,7 +769,7 @@ local function terminate(lsession, opts)
762769
end
763770

764771
if lsession.closed then
765-
log().warn('User called terminate on already closed session that is still in use')
772+
log():warn('User called terminate on already closed session that is still in use')
766773
sessions[lsession.id] = nil
767774
M.set_session(nil)
768775
on_done()
@@ -776,7 +783,7 @@ local function terminate(lsession, opts)
776783
local timeout_ms = timeout_sec * 1000
777784
lsession:request_with_timeout('terminate', args, timeout_ms, function(err)
778785
if err then
779-
log().warn(lazy.utils.fmt_error(err))
786+
log():warn(lazy.utils.fmt_error(err))
780787
end
781788
if not lsession.closed then
782789
lsession:close()
@@ -839,7 +846,7 @@ function M.terminate(opts, disconnect_opts, cb)
839846
if not lsession then
840847
local _, s = next(sessions)
841848
if s then
842-
log().info("Terminate called without active session, switched to", s.id)
849+
log():info("Terminate called without active session, switched to", s.id)
843850
end
844851
lsession = s
845852
end
@@ -935,7 +942,7 @@ function M.list_breakpoints(openqf)
935942
end
936943
vim.fn.setqflist({}, action, {
937944
items = qf_list,
938-
context = DAP_QUICKFIX_CONTEXT,
945+
context = { DAP_QUICKFIX_CONTEXT },
939946
title = DAP_QUICKFIX_TITLE
940947
})
941948
if openqf then
@@ -1179,6 +1186,7 @@ function M.disconnect(opts, cb)
11791186
end
11801187

11811188

1189+
---@private
11821190
--- Connect to a debug adapter via TCP
11831191
---@param adapter dap.ServerAdapter
11841192
---@param config dap.Configuration
@@ -1190,7 +1198,7 @@ function M.attach(adapter, config, opts)
11901198
end
11911199
assert(adapter.port, 'Adapter used with attach must have a port property')
11921200
local s
1193-
s = require('dap.session'):connect(adapter, opts, function(err)
1201+
s = require('dap.session').connect(adapter, config, opts, function(err)
11941202
if err then
11951203
notify(
11961204
string.format("Couldn't connect to %s:%s: %s", adapter.host or '127.0.0.1', adapter.port, err),
@@ -1208,13 +1216,17 @@ function M.attach(adapter, config, opts)
12081216
end
12091217

12101218

1219+
---@private
12111220
--- Launch an executable debug adapter and initialize a session
12121221
---
12131222
---@param adapter dap.ExecutableAdapter
12141223
---@param config dap.Configuration
12151224
---@param opts table
12161225
function M.launch(adapter, config, opts)
1217-
local s = require('dap.session'):spawn(adapter, opts)
1226+
local s = require('dap.session').spawn(adapter, opts, config)
1227+
if not s then
1228+
return
1229+
end
12181230
add_reset_session_hook(s)
12191231
M.set_session(s)
12201232
s:initialize(config)
@@ -1223,7 +1235,7 @@ end
12231235

12241236

12251237
function M.set_log_level(level)
1226-
log().set_level(level)
1238+
log():set_level(level)
12271239
end
12281240

12291241

lua/dap/_cmds.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,13 @@ function M.yank_evalname()
158158
end
159159

160160

161+
function M.show_logs()
162+
local log = require("dap.log")
163+
log.create_logger("dap.log")
164+
for _, logger in pairs(log._loggers) do
165+
vim.cmd.tabnew(logger._path)
166+
end
167+
end
168+
169+
161170
return M

lua/dap/log.lua

Lines changed: 143 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,168 @@
1-
-- Similar to lsp/log.lua in neovim,
2-
-- but allows to create multiple loggers with different filenames each
3-
41
local M = {}
2+
3+
---@type table<string, dap.log.Log>
54
local loggers = {}
65

6+
M._loggers = loggers
7+
8+
---@enum dap.log.Level
79
M.levels = {
8-
TRACE = 0;
9-
DEBUG = 1;
10-
INFO = 2;
11-
WARN = 3;
12-
ERROR = 4;
10+
TRACE = 0,
11+
DEBUG = 1,
12+
INFO = 2,
13+
WARN = 3,
14+
ERROR = 4,
1315
}
1416

15-
local log_date_format = "%FT%H:%M:%SZ%z"
17+
local log_date_format = "!%FT%H:%M:%S"
1618

17-
function M.create_logger(filename)
18-
local logger = loggers[filename]
19-
if logger then
20-
return logger
19+
20+
---@class dap.log.Log
21+
---@field _fname string
22+
---@field _path string
23+
---@field _file file*?
24+
---@field _level dap.log.Level
25+
26+
27+
---@class dap.log.Log
28+
local Log = {}
29+
local log_mt = {
30+
__index = Log
31+
}
32+
33+
34+
function Log:write(...)
35+
self:open()
36+
self._file:write(...)
37+
end
38+
39+
function Log:open()
40+
if not self._file then
41+
local f = assert(io.open(self._path, "w+"))
42+
self._file = f
2143
end
22-
logger = {}
23-
loggers[filename] = logger
44+
end
2445

46+
---@param level dap.log.Level|string
47+
function Log:set_level(level)
48+
if type(level) == "string" then
49+
self._level = assert(
50+
M.levels[tostring(level):upper()],
51+
string.format('Log level must be one of (trace, debug, info, warn, error), got: %q', level)
52+
)
53+
else
54+
self._level = level
55+
end
56+
end
57+
58+
function Log:get_path()
59+
return self._path
60+
end
61+
62+
63+
function Log:close()
64+
if self._file then
65+
self._file:flush()
66+
self._file:close()
67+
self._file = nil
68+
end
69+
end
70+
71+
function Log:remove()
72+
self:close()
73+
os.remove(self._path)
74+
loggers[self._fname] = nil
75+
end
76+
77+
78+
---@param level dap.log.Level
79+
---@return boolean
80+
function Log:_log(level, ...)
81+
local argc = select('#', ...)
82+
if level < self._level then
83+
return false
84+
end
85+
if argc == 0 then
86+
return true
87+
end
88+
local info = debug.getinfo(2, 'Sl')
89+
local fileinfo = string.format('%s:%s', info.short_src, info.currentline)
90+
local parts = {
91+
table.concat({'[', level, ']', os.date(log_date_format), ']', fileinfo, ']'}, ' ')
92+
}
93+
for i = 1, argc do
94+
local arg = select(i, ...)
95+
if arg == nil then
96+
table.insert(parts, "nil")
97+
else
98+
table.insert(parts, vim.inspect(arg))
99+
end
100+
end
101+
self:write(table.concat(parts, '\t'), '\n')
102+
return true
103+
end
104+
105+
106+
107+
--- Not generating methods below in a loop to help out luals
108+
109+
110+
function Log:trace(...)
111+
self:_log(M.levels.TRACE, ...)
112+
end
113+
114+
function Log:debug(...)
115+
self:_log(M.levels.DEBUG, ...)
116+
end
117+
118+
function Log:info(...)
119+
self:_log(M.levels.INFO, ...)
120+
end
121+
122+
function Log:warn(...)
123+
self:_log(M.levels.WARN, ...)
124+
end
125+
126+
function Log:error(...)
127+
self:_log(M.levels.ERROR, ...)
128+
end
129+
130+
131+
---@param fname string
132+
---@return string path
133+
---@return string cache_dir
134+
local function getpath(fname)
25135
local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
26136
local joinpath = (vim.fs or {}).joinpath or function(...)
27137
---@diagnostic disable-next-line: deprecated
28138
return table.concat(vim.tbl_flatten{...}, path_sep)
29139
end
30140
local cache_dir = vim.fn.stdpath('cache')
31141
assert(type(cache_dir) == "string")
32-
local logfilename = joinpath(cache_dir, filename)
33-
34-
local current_log_level = M.levels.INFO
142+
return joinpath(cache_dir, fname), cache_dir
143+
end
35144

36-
function logger.set_level(level)
37-
current_log_level = assert(
38-
M.levels[tostring(level):upper()],
39-
string.format('Log level must be one of (trace, debug, info, warn, error), got: %q', level)
40-
)
41-
end
42145

43-
function logger.get_filename()
44-
return logfilename
146+
---@return dap.log.Log
147+
function M.create_logger(filename)
148+
local logger = loggers[filename]
149+
if logger then
150+
logger:open()
151+
return logger
45152
end
153+
local path, cache_dir = getpath(filename)
154+
local log = {
155+
_fname = filename,
156+
_path = path,
157+
_level = M.levels.INFO
158+
}
159+
logger = setmetatable(log, log_mt)
160+
loggers[filename] = logger
46161

47162
vim.fn.mkdir(cache_dir, "p")
48-
local logfile = assert(io.open(logfilename, "a+"))
49-
for level, levelnr in pairs(M.levels) do
50-
logger[level:lower()] = function(...)
51-
local argc = select('#', ...)
52-
if levelnr < current_log_level then
53-
return false
54-
end
55-
if argc == 0 then
56-
return true
57-
end
58-
local info = debug.getinfo(2, 'Sl')
59-
local fileinfo = string.format('%s:%s', info.short_src, info.currentline)
60-
local parts = {
61-
table.concat({'[', level, ']', os.date(log_date_format), ']', fileinfo, ']'}, ' ')
62-
}
63-
for i = 1, argc do
64-
local arg = select(i, ...)
65-
if arg == nil then
66-
table.insert(parts, "nil")
67-
else
68-
table.insert(parts, vim.inspect(arg))
69-
end
70-
end
71-
logfile:write(table.concat(parts, '\t'), '\n')
72-
logfile:flush()
73-
end
74-
end
75-
logfile:write('\n')
163+
logger:open()
76164
return logger
77165
end
78166

167+
79168
return M

0 commit comments

Comments
 (0)