More often than I’d like to, I run into issues with some LSP server that either does not work at all or stops working. When that happens, I will try not to break out of flow and, eventually, try to debug it so it doesn’t happen again.

When using nvim, :h lsp-log provides these useful commands to trace protocol messages:

vim.lsp.set_log_level 'trace'
require('vim.lsp.log').set_format_func(vim.inspect)

The problem is that tracing slows down the editor, bloats the logfile, and won’t have the best ergonomics to parse the messages.

Instead, I have written and improved over time a small Nix derivation that wraps the LSP to dump its standard input, output, and error to files so that I can easily take a look at them later.

Here it is, in case it’s useful to anyone:

wrapLSP =
{
  lsp,
  cmd ? (pkgs.lib.meta.getExe lsp),
}:
pkgs.writeShellApplication {
  name = lsp.meta.mainProgram;
  runtimeInputs = [
    lsp
  ];

  text = ''
    coproc LSP_SERVER { ${cmd} "$@" 2> /tmp/${lsp.name}_error.log; }

    # first some necessary file-descriptors fiddling
    exec {srv_input}>&"''${LSP_SERVER[1]}"-
    exec {srv_output}<&"''${LSP_SERVER[0]}"-

    # background commands to relay normal stdin/stdout activity
    tee /tmp/${lsp.name}_input.log <&0 >&''${srv_input} &
    tee /tmp/${lsp.name}_output.log <&''${srv_output} &

    while true; do sleep infinity; done
  '';
};

To use it, call it with an LSP as an argument (e.g., wrapLSP pkgs.ctags-lsp), build the derivation, and then add the result to PATH: PATH=$(readlink -f result/bin)/:$PATH when launching your editor.