lib.nvim · Foundation · vimdoc
:help lib.nvim-logger
Structured logging, diagnostics & crash dumps
doc/lib.nvim-logger.txt — rendered from the plugin's own vimdoc
*lib.nvim-logger.txt* Structured logging, diagnostics & crash dumps lib.nvim.logger *lib.nvim-logger*
CONTENTS
1. Introduction ......................... |lib.nvim-logger-intro| 2. Creating a logger .................... |lib.nvim-logger-new| 3. Logging .............................. |lib.nvim-logger-log| 4. Switches (enable/disable, tags) ...... |lib.nvim-logger-switches| 5. Crash capture ........................ |lib.nvim-logger-capture| 6. File sink ............................ |lib.nvim-logger-file| 7. Command .............................. |lib.nvim-logger-command|
1. INTRODUCTION
lib.nvim.logger is a richer sibling oflib.nvim.notify. Next to a normal notify it records a message plus a structured context table into a bounded in-memory ring buffer, optionally streams JSONL to a file, and can dump that ring when a guarded entrypoint fails. It is designed so that logging — all of it, or only parts selected by level or by tag — can be switched off with de-facto zero runtime cost, so a plugin can ship with debug logging left in place. Cross-platform: paths resolve throughstdpath("log"); writes go throughlib.nvim.fs.write.append; no shell-outs.
2. CREATING A LOGGER
local log = require("lib.nvim.logger").new({
name = "myplugin", -- scope / prefix
level = "debug", -- min level to RECORD
notify_level = "warn", -- min level to also vim.notify
file = nil, -- nil=stdpath default, false=off, string=path
capture = true, -- flush ring on VimLeavePre
history = 200, -- ring-buffer size
redact = { "token" }, -- context keys to scrub
})
Levels accept a number (|vim.log.levels|) or a name: "trace", "debug", "info",
"warn", "error", "off".
3. LOGGING
log.info("cache warm", { entries = 128, took_ms = 12 })
log.warn("slow query", { ms = 340 })
log.error("write failed", { path = p, err = err })
log.debug("state", function() return expensive_snapshot() end) -- thunk
log.info("tagged", { x = 1 }, { tags = { "net" } }) -- call opts
log.<level>(msg, ctx?, opts?)—ctxis a table OR a function returning one (the thunk is only invoked when the level is active, so expensive context is free when the level is off).optsis |lib.nvim-logger| CallOpts:tags,to(per-call file override),notify(force/suppress). Extras:
log.once("k", "warn", "shown once") -- log a key at most once
local stop = log.timer("index build") -- returns stop() logging elapsed ms
stop({ files = 42 })
log.assert(cond, "must hold", ctx) -- log + raise on falsy
4. SWITCHES
Global (affect every logger; the master switch is checked first in the hot path, so a disabled logger costs one comparison):
local L = require("lib.nvim.logger")
L.set_enabled(false) -- master kill switch (zero-cost when off)
L.is_enabled() -- current master-switch state
L.set_level("warn") -- global min-level override; nil clears
L.disable_tag("net") -- drop every record carrying tag "net"
L.enable_tag("net")
L.only_tags({ "cache" }) -- whitelist: keep only these tags; nil clears
L.tags() -- { disabled = {...}, only = {...}|nil }
L.setup({ level = "info" }) -- merge into every FUTURE logger's defaults
L.loggers() -- every logger created so far (the registry)
setup(opts)also acceptsenabled/min_levelshorthand for the global switches above; every other key becomes a new default merged into options passed to futurenew()calls (does not touch already-created loggers). Per-logger:
log.set_enabled(false)
log.is_enabled()
log.set_level("error")
5. CRASH CAPTURE
There is no global "uncaught error" hook in Neovim, so capture combines:
1. Synchronous file writes — every record (incl. errors) is durable the
moment it is logged, so context survives a subsequent crash.
2. guard()/wrap() — xpcall wrappers that log an ERROR with a traceback and
flush the ring before re-raising (guard) or swallowing (wrap):
vim.keymap.set("n", "<leader>x", log.guard(function() risky() end))
M.run = log.wrap(M.run, "run")
3. VimLeavePre flush — the ring is written to the file sink on exit.log.flush()writes the whole ring to the file now;log.snapshot()returns a copy;log.clear()empties it.
6. FILE SINK
Records are appended as JSONL (one JSON object per line). Default location isstdpath("log")/lib-logger/<name>.jsonl; passfile = "/path"to override, orfile = falseto disable. Context tables are sanitized before encoding (functions/userdata stringified, cycles broken, depth/width capped, redacted keys scrubbed).
7. COMMAND
:LibLoggeris installed on the firstlogger.new()call:
:LibLogger show [n] open the n most recent records in a float
:LibLogger on|off global enable / disable
:LibLogger level <l> set the global min level
:LibLogger dump flush every logger's ring to its file
:LibLogger clear empty every ring buffer
:LibLogger tags show disabled / whitelisted tags