Repository: lightningnetwork/lnd (language: go)

Issue:
Logging timestamps should be optional
lnd always prints timestamps in log messages.
This is redundant in systemd services (where log messages are automatically timestamped) and should be made optional.
This [SO post](https://stackoverflow.com/a/48630122/3806468) shows how this can be achieved 


Discussion / hints:
Check this: https://newreleases.io/project/github/lightningnetwork/lnd/release/v0.13.0-beta.rc3.

Your Logging timestamps is useful for your peer. He should know when was your last log.
@redek-zelton, how is the link relevant to this issue?
ah sorry my bad, it's a suggestion.
Can I work on this? @Roasbeef 
Note to implementers:
- `bitcoind` has option `logtimestamps` (default: true)
- `clightning` has option `log-timestamps`(default: true)

`lnd` should use one of these names.
Unfortunately the command line flag parsing library we use cannot have boolean values with default value `true` (because you cannot specify `--myflag=false`). So it would need to be inverted. I'm voting for `disable-log-timestamps`.
@guggero looks like the convention is to use the `--no` prefix flag (i.e `--no-macaroons` or `--no-listen'`)

Fix patch:
diff --git a/autopilot/log.go b/autopilot/log.go
index 34e53c8462..1d86eb5254 100644
--- a/autopilot/log.go
+++ b/autopilot/log.go
@@ -1,7 +1,7 @@
 package autopilot
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/build/config.go b/build/config.go
new file mode 100644
index 0000000000..66a0b31444
--- /dev/null
+++ b/build/config.go
@@ -0,0 +1,65 @@
+//go:build !dev
+// +build !dev
+
+package build
+
+import "github.com/btcsuite/btclog/v2"
+
+const (
+	callSiteOff   = "off"
+	callSiteShort = "short"
+	callSiteLong  = "long"
+)
+
+// LogConfig holds logging configuration options.
+//
+//nolint:lll
+type LogConfig struct {
+	Console *LoggerConfig `group:"console" namespace:"console" description:"The logger writing to stdout and stderr."`
+	File    *LoggerConfig `group:"file" namespace:"file" description:"The logger writing to LND's standard log file."`
+}
+
+// DefaultLogConfig returns the default logging config options.
+func DefaultLogConfig() *LogConfig {
+	return &LogConfig{
+		Console: &LoggerConfig{
+			CallSite: callSiteOff,
+		},
+		File: &LoggerConfig{
+			CallSite: callSiteOff,
+		},
+	}
+}
+
+// LoggerConfig holds options for a particular logger.
+//
+//nolint:lll
+type LoggerConfig struct {
+	Disable      bool   `long:"disable" description:"Disable this logger."`
+	NoTimestamps bool   `long:"no-timestamps" description:"Omit timestamps from log lines."`
+	CallSite     string `long:"call-site" description:"Include the call-site of each log line." choice:"off" choice:"short" choice:"long"`
+}
+
+// HandlerOptions returns the set of btclog.HandlerOptions that the state of the
+// config struct translates to.
+func (cfg *LoggerConfig) HandlerOptions() []btclog.HandlerOption {
+	opts := []btclog.HandlerOption{
+		// The default skip depth used by the logging library is 6 but
+		// since we wrap the logging handlers with another level of
+		// abstraction with the handlerSet, we increase the skip depth
+		// to 7 here.
+		btclog.WithCallSiteSkipDepth(7),
+	}
+	if cfg.NoTimestamps {
+		opts = append(opts, btclog.WithNoTimestamp())
+	}
+
+	switch cfg.CallSite {
+	case callSiteShort:
+		opts = append(opts, btclog.WithCallerFlags(btclog.Lshortfile))
+	case callSiteLong:
+		opts = append(opts, btclog.WithCallerFlags(btclog.Llongfile))
+	}
+
+	return opts
+}
diff --git a/build/config_dev.go b/build/config_dev.go
new file mode 100644
index 0000000000..daa5b1fde2
--- /dev/null
+++ b/build/config_dev.go
@@ -0,0 +1,162 @@
+//go:build dev
+// +build dev
+
+package build
+
+import (
+	"fmt"
+	"strings"
+
+	btclogv1 "github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
+)
+
+const (
+	resetSeq = "0"
+	boldSeq  = "1"
+	faintSeq = "2"
+	esc      = '\x1b'
+	csi      = string(esc) + "["
+
+	callSiteOff   = "off"
+	callSiteShort = "short"
+	callSiteLong  = "long"
+)
+
+// LogConfig holds logging configuration options.
+//
+//nolint:lll
+type LogConfig struct {
+	Console *consoleLoggerCfg `group:"console" namespace:"console" description:"The logger writing to stdout and stderr."`
+	File    *LoggerConfig     `group:"file" namespace:"file" description:"The logger writing to LND's standard log file."`
+}
+
+// DefaultLogConfig returns the default logging config options.
+func DefaultLogConfig() *LogConfig {
+	return &LogConfig{
+		Console: &consoleLoggerCfg{
+			LoggerConfig: LoggerConfig{
+				CallSite: callSiteShort,
+			},
+		},
+		File: &LoggerConfig{
+			CallSite: callSiteOff,
+		},
+	}
+}
+
+// LoggerConfig holds options for a particular logger.
+//
+//nolint:lll
+type LoggerConfig struct {
+	Disable      bool   `long:"disable" description:"Disable this logger."`
+	NoTimestamps bool   `long:"no-timestamps" description:"Omit timestamps from log lines."`
+	CallSite     string `long:"call-site" description:"Include the call-site of each log line." choice:"off" choice:"short" choice:"long"`
+}
+
+// HandlerOptions returns the set of btclog.HandlerOptions that the state of the
+// config struct translates to.
+func (cfg *LoggerConfig) HandlerOptions() []btclog.HandlerOption {
+	opts := []btclog.HandlerOption{
+		// The default skip depth used by the logging library is 6 but
+		// since we wrap the logging handlers with another level of
+		// abstraction with the handlerSet, we increase the skip depth
+		// to 7 here.
+		btclog.WithCallSiteSkipDepth(7),
+	}
+
+	if cfg.NoTimestamps {
+		opts = append(opts, btclog.WithNoTimestamp())
+	}
+
+	switch cfg.CallSite {
+	case callSiteShort:
+		opts = append(opts, btclog.WithCallerFlags(btclog.Lshortfile))
+	case callSiteLong:
+		opts = append(opts, btclog.WithCallerFlags(btclog.Llongfile))
+	}
+
+	return opts
+}
+
+// consoleLoggerCfg extends the LoggerConfig struct by adding a Color option
+// which is only available for a console logger.
+//
+//nolint:lll
+type consoleLoggerCfg struct {
+	LoggerConfig
+	Style bool `long:"style" description:"If set, the output will be styled with color and fonts"`
+}
+
+// HandlerOptions returns the set of btclog.HandlerOptions that the state of the
+// config struct translates to.
+func (cfg *consoleLoggerCfg) HandlerOptions() []btclog.HandlerOption {
+	opts := cfg.LoggerConfig.HandlerOptions()
+
+	if !cfg.Style {
+		return opts
+	}
+
+	return append(
+		opts, btclog.WithStyledLevel(
+			func(l btclogv1.Level) string {
+				return styleString(
+					fmt.Sprintf("[%s]", l),
+					boldSeq,
+					string(ansiColoSeq(l)),
+				)
+			},
+		),
+		btclog.WithStyledCallSite(
+			func(file string, line int) string {
+				str := fmt.Sprintf("%s:%d", file, line)
+
+				return styleString(str, faintSeq)
+			},
+		),
+		btclog.WithStyledKeys(func(key string) string {
+			return styleString(key, faintSeq)
+		}),
+	)
+}
+
+func styleString(s string, styles ...string) string {
+	if len(styles) == 0 {
+		return s
+	}
+
+	seq := strings.Join(styles, ";")
+	if seq == "" {
+		return s
+	}
+
+	return fmt.Sprintf("%s%sm%s%sm", csi, seq, s, csi+resetSeq)
+}
+
+type ansiColorSeq string
+
+const (
+	ansiColorSeqDarkTeal  ansiColorSeq = "38;5;30"
+	ansiColorSeqDarkBlue  ansiColorSeq = "38;5;63"
+	ansiColorSeqLightBlue ansiColorSeq = "38;5;86"
+	ansiColorSeqYellow    ansiColorSeq = "38;5;192"
+	ansiColorSeqRed       ansiColorSeq = "38;5;204"
+	ansiColorSeqPink      ansiColorSeq = "38;5;134"
+)
+
+func ansiColoSeq(l btclogv1.Level) ansiColorSeq {
+	switch l {
+	case btclog.LevelTrace:
+		return ansiColorSeqDarkTeal
+	case btclog.LevelDebug:
+		return ansiColorSeqDarkBlue
+	case btclog.LevelWarn:
+		return ansiColorSeqYellow
+	case btclog.LevelError:
+		return ansiColorSeqRed
+	case btclog.LevelCritical:
+		return ansiColorSeqPink
+	default:
+		return ansiColorSeqLightBlue
+	}
+}
diff --git a/build/handler_sets.go b/build/handler_sets.go
new file mode 100644
index 0000000000..d9786a8580
--- /dev/null
+++ b/build/handler_sets.go
@@ -0,0 +1,201 @@
+package build
+
+import (
+	"context"
+	"log/slog"
+
+	btclogv1 "github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
+)
+
+// handlerSet is an implementation of Handler that abstracts away multiple
+// Handlers.
+type handlerSet struct {
+	level btclogv1.Level
+	set   []btclog.Handler
+}
+
+// newHandlerSet constructs a new HandlerSet.
+func newHandlerSet(level btclogv1.Level, set ...btclog.Handler) *handlerSet {
+	h := &handlerSet{
+		set:   set,
+		level: level,
+	}
+	h.SetLevel(level)
+
+	return h
+}
+
+// Enabled reports whether the handler handles records at the given level.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (h *handlerSet) Enabled(ctx context.Context, level slog.Level) bool {
+	for _, handler := range h.set {
+		if !handler.Enabled(ctx, level) {
+			return false
+		}
+	}
+
+	return true
+}
+
+// Handle handles the Record.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (h *handlerSet) Handle(ctx context.Context, record slog.Record) error {
+	for _, handler := range h.set {
+		if err := handler.Handle(ctx, record); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// WithAttrs returns a new Handler whose attributes consist of both the
+// receiver's attributes and the arguments.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (h *handlerSet) WithAttrs(attrs []slog.Attr) slog.Handler {
+	newSet := &reducedSet{set: make([]slog.Handler, len(h.set))}
+	for i, handler := range h.set {
+		newSet.set[i] = handler.WithAttrs(attrs)
+	}
+
+	return newSet
+}
+
+// WithGroup returns a new Handler with the given group appended to the
+// receiver's existing groups.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (h *handlerSet) WithGroup(name string) slog.Handler {
+	newSet := &reducedSet{set: make([]slog.Handler, len(h.set))}
+	for i, handler := range h.set {
+		newSet.set[i] = handler.WithGroup(name)
+	}
+
+	return newSet
+}
+
+// SubSystem creates a new Handler with the given sub-system tag.
+//
+// NOTE: this is part of the Handler interface.
+func (h *handlerSet) SubSystem(tag string) btclog.Handler {
+	newSet := &handlerSet{set: make([]btclog.Handler, len(h.set))}
+	for i, handler := range h.set {
+		newSet.set[i] = handler.SubSystem(tag)
+	}
+
+	return newSet
+}
+
+// SetLevel changes the logging level of the Handler to the passed
+// level.
+//
+// NOTE: this is part of the btclog.Handler interface.
+func (h *handlerSet) SetLevel(level btclogv1.Level) {
+	for _, handler := range h.set {
+		handler.SetLevel(level)
+	}
+	h.level = level
+}
+
+// Level returns the current logging level of the Handler.
+//
+// NOTE: this is part of the btclog.Handler interface.
+func (h *handlerSet) Level() btclogv1.Level {
+	return h.level
+}
+
+// A compile-time check to ensure that handlerSet implements btclog.Handler.
+var _ btclog.Handler = (*handlerSet)(nil)
+
+// reducedSet is an implementation of the slog.Handler interface which is
+// itself backed by multiple slog.Handlers. This is used by the handlerSet
+// WithGroup and WithAttrs methods so that we can apply the WithGroup and
+// WithAttrs to the underlying handlers in the set. These calls, however,
+// produce slog.Handlers and not btclog.Handlers. So the reducedSet represents
+// the resulting set produced.
+type reducedSet struct {
+	set []slog.Handler
+}
+
+// Enabled reports whether the handler handles records at the given level.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (r *reducedSet) Enabled(ctx context.Context, level slog.Level) bool {
+	for _, handler := range r.set {
+		if !handler.Enabled(ctx, level) {
+			return false
+		}
+	}
+
+	return true
+}
+
+// Handle handles the Record.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (r *reducedSet) Handle(ctx context.Context, record slog.Record) error {
+	for _, handler := range r.set {
+		if err := handler.Handle(ctx, record); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// WithAttrs returns a new Handler whose attributes consist of both the
+// receiver's attributes and the arguments.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (r *reducedSet) WithAttrs(attrs []slog.Attr) slog.Handler {
+	newSet := &reducedSet{set: make([]slog.Handler, len(r.set))}
+	for i, handler := range r.set {
+		newSet.set[i] = handler.WithAttrs(attrs)
+	}
+
+	return newSet
+}
+
+// WithGroup returns a new Handler with the given group appended to the
+// receiver's existing groups.
+//
+// NOTE: this is part of the slog.Handler interface.
+func (r *reducedSet) WithGroup(name string) slog.Handler {
+	newSet := &reducedSet{set: make([]slog.Handler, len(r.set))}
+	for i, handler := range r.set {
+		newSet.set[i] = handler.WithGroup(name)
+	}
+
+	return newSet
+}
+
+// A compile-time check to ensure that handlerSet implements slog.Handler.
+var _ slog.Handler = (*reducedSet)(nil)
+
+// subLogGenerator implements the SubLogCreator backed by a Handler.
+type subLogGenerator struct {
+	handler btclog.Handler
+}
+
+// newSubLogGenerator constructs a new subLogGenerator from a Handler.
+func newSubLogGenerator(handler btclog.Handler) *subLogGenerator {
+	return &subLogGenerator{
+		handler: handler,
+	}
+}
+
+// Logger returns a new logger for a particular sub-system.
+//
+// NOTE: this is part of the SubLogCreator interface.
+func (b *subLogGenerator) Logger(subsystemTag string) btclog.Logger {
+	handler := b.handler.SubSystem(subsystemTag)
+
+	return btclog.NewSLogger(handler)
+}
+
+// A compile-time check to ensure that handlerSet implements slog.Handler.
+var _ SubLogCreator = (*subLogGenerator)(nil)
diff --git a/build/handlers.go b/build/handlers.go
new file mode 100644
index 0000000000..9ffbdd70a6
--- /dev/null
+++ b/build/handlers.go
@@ -0,0 +1,23 @@
+package build
+
+import (
+	"os"
+
+	"github.com/btcsuite/btclog/v2"
+)
+
+// NewDefaultLogHandlers returns the standard console logger and rotating log
+// writer handlers that we generally want to use. It also applies the various
+// config options to the loggers.
+func NewDefaultLogHandlers(cfg *LogConfig, rotator *RotatingLogWriter) (
+	btclog.Handler, btclog.Handler) {
+
+	consoleLogHandler := btclog.NewDefaultHandler(
+		os.Stdout, cfg.Console.HandlerOptions()...,
+	)
+	logFileHandler := btclog.NewDefaultHandler(
+		rotator, cfg.File.HandlerOptions()...,
+	)
+
+	return consoleLogHandler, logFileHandler
+}
diff --git a/build/log.go b/build/log.go
index 484b48e422..568f9f618c 100644
--- a/build/log.go
+++ b/build/log.go
@@ -1,11 +1,9 @@
 package build
 
 import (
-	"fmt"
-	"io"
-	"strings"
+	"os"
 
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // LogType is an indicating the type of logging specified by the build flag.
@@ -62,17 +60,6 @@ func SuportedLogCompressor(logCompressor string) bool {
 	return ok
 }
 
-// LogWriter is a stub type whose behavior can be changed using the build flags
-// "stdlog" and "nolog". The default behavior is to write to both stdout and the
-// RotatorPipe. Passing "stdlog" will cause it only to write to stdout, and
-// "nolog" implements Write as a no-op.
-type LogWriter struct {
-	// RotatorPipe is the write-end pipe for writing to the log rotator.  It
-	// is written to by the Write method of the LogWriter type. This only
-	// needs to be set if neither the stdlog or nolog builds are set.
-	RotatorPipe *io.PipeWriter
-}
-
 // NewSubLogger constructs a new subsystem log from the current LogWriter
 // implementation. This is primarily intended for use with stdlog, as the actual
 // writer is shared amongst all instantiations.
@@ -106,8 +93,10 @@ func NewSubLogger(subsystem string,
 		// that they share the same backend, since all output is written
 		// to std out.
 		case LogTypeStdOut:
-			backend := btclog.NewBackend(&LogWriter{})
-			logger := backend.Logger(subsystem)
+			backend := btclog.NewDefaultHandler(os.Stdout)
+			logger := btclog.NewSLogger(
+				backend.SubSystem(subsystem),
+			)
 
 			// Set the logging level of the stdout logger to use the
 			// configured logging level specified by build flags.
@@ -121,114 +110,3 @@ func NewSubLogger(subsystem string,
 	// For any other configurations, we'll disable logging.
 	return btclog.Disabled
 }
-
-// SubLoggers is a type that holds a map of subsystem loggers keyed by their
-// subsystem name.
-type SubLoggers map[string]btclog.Logger
-
-// LeveledSubLogger provides the ability to retrieve the subsystem loggers of
-// a logger and set their log levels individually or all at once.
-type LeveledSubLogger interface {
-	// SubLoggers returns the map of all registered subsystem loggers.
-	SubLoggers() SubLoggers
-
-	// SupportedSubsystems returns a slice of strings containing the names
-	// of the supported subsystems. Should ideally correspond to the keys
-	// of the subsystem logger map and be sorted.
-	SupportedSubsystems() []string
-
-	// SetLogLevel assigns an individual subsystem logger a new log level.
-	SetLogLevel(subsystemID string, logLevel string)
-
-	// SetLogLevels assigns all subsystem loggers the same new log level.
-	SetLogLevels(logLevel string)
-}
-
-// ParseAndSetDebugLevels attempts to parse the specified debug level and set
-// the levels accordingly on the given logger. An appropriate error is returned
-// if anything is invalid.
-func ParseAndSetDebugLevels(level string, logger LeveledSubLogger) error {
-	// Split at the delimiter.
-	levels := strings.Split(level, ",")
-	if len(levels) == 0 {
-		return fmt.Errorf("invalid log level: %v", level)
-	}
-
-	// If the first entry has no =, treat is as the log level for all
-	// subsystems.
-	globalLevel := levels[0]
-	if !strings.Contains(globalLevel, "=") {
-		// Validate debug log level.
-		if !validLogLevel(globalLevel) {
-			str := "the specified debug level [%v] is invalid"
-			return fmt.Errorf(str, globalLevel)
-		}
-
-		// Change the logging level for all subsystems.
-		logger.SetLogLevels(globalLevel)
-
-		// The rest will target specific subsystems.
-		levels = levels[1:]
-	}
-
-	// Go through the subsystem/level pairs while detecting issues and
-	// update the log levels accordingly.
-	for _, logLevelPair := range levels {
-		if !strings.Contains(logLevelPair, "=") {
-			str := "the specified debug level contains an " +
-				"invalid subsystem/level pair [%v]"
-			return fmt.Errorf(str, logLevelPair)
-		}
-
-		// Extract the specified subsystem and log level.
-		fields := strings.Split(logLevelPair, "=")
-		if len(fields) != 2 {
-			str := "the specified debug level has an invalid " +
-				"format [%v] -- use format subsystem1=level1," +
-				"subsystem2=level2"
-			return fmt.Errorf(str, logLevelPair)
-		}
-		subsysID, logLevel := fields[0], fields[1]
-		subLoggers := logger.SubLoggers()
-
-		// Validate subsystem.
-		if _, exists := subLoggers[subsysID]; !exists {
-			str := "the specified subsystem [%v] is invalid -- " +
-				"supported subsystems are %v"
-			return fmt.Errorf(
-				str, subsysID, logger.SupportedSubsystems(),
-			)
-		}
-
-		// Validate log level.
-		if !validLogLevel(logLevel) {
-			str := "the specified debug level [%v] is invalid"
-			return fmt.Errorf(str, logLevel)
-		}
-
-		logger.SetLogLevel(subsysID, logLevel)
-	}
-
-	return nil
-}
-
-// validLogLevel returns whether or not logLevel is a valid debug log level.
-func validLogLevel(logLevel string) bool {
-	switch logLevel {
-	case "trace":
-		fallthrough
-	case "debug":
-		fallthrough
-	case "info":
-		fallthrough
-	case "warn":
-		fallthrough
-	case "error":
-		fallthrough
-	case "critical":
-		fallthrough
-	case "off":
-		return true
-	}
-	return false
-}
diff --git a/build/log_default.go b/build/log_default.go
index bbb6d4c390..6e027e0dd0 100644
--- a/build/log_default.go
+++ b/build/log_default.go
@@ -3,17 +3,6 @@
 
 package build
 
-import "os"
-
 // LoggingType is a log type that writes to both stdout and the log rotator, if
 // present.
 const LoggingType = LogTypeDefault
-
-// Write writes the byte slice to both stdout and the log rotator, if present.
-func (w *LogWriter) Write(b []byte) (int, error) {
-	os.Stdout.Write(b)
-	if w.RotatorPipe != nil {
-		w.RotatorPipe.Write(b)
-	}
-	return len(b), nil
-}
diff --git a/build/log_nolog.go b/build/log_nolog.go
index 32199ada00..75f17bf66b 100644
--- a/build/log_nolog.go
+++ b/build/log_nolog.go
@@ -5,8 +5,3 @@ package build
 
 // LoggingType is a log type that writes no logs.
 const LoggingType = LogTypeNone
-
-// Write is a noop.
-func (w *LogWriter) Write(b []byte) (int, error) {
-	return len(b), nil
-}
diff --git a/build/log_shutdown.go b/build/log_shutdown.go
index e5a63410bc..dfabe1cb6f 100644
--- a/build/log_shutdown.go
+++ b/build/log_shutdown.go
@@ -1,7 +1,9 @@
 package build
 
 import (
-	"github.com/btcsuite/btclog"
+	"context"
+
+	"github.com/btcsuite/btclog/v2"
 )
 
 // ShutdownLogger wraps an existing logger with a shutdown function which will
@@ -41,3 +43,16 @@ func (s *ShutdownLogger) Critical(v ...interface{}) {
 	s.Logger.Info("Sending request for shutdown")
 	s.shutdown()
 }
+
+// CriticalS writes a structured log with the given message and key-value pair
+// attributes with LevelCritical to the log. It will then call the shutdown
+// logger's shutdown function to prompt safe shutdown.
+//
+// Note: it is part of the btclog.Logger interface.
+func (s *ShutdownLogger) CriticalS(ctx context.Context, msg string, err error,
+	attr ...interface{}) {
+
+	s.Logger.CriticalS(ctx, msg, err, attr...)
+	s.Logger.Info("Sending request for shutdown")
+	s.shutdown()
+}
diff --git a/build/log_stdlog.go b/build/log_stdlog.go
index 34a8c1bf02..461474376d 100644
--- a/build/log_stdlog.go
+++ b/build/log_stdlog.go
@@ -3,13 +3,5 @@
 
 package build
 
-import "os"
-
 // LoggingType is a log type that only writes to stdout.
 const LoggingType = LogTypeStdOut
-
-// Write writes the provided byte slice to stdout.
-func (w *LogWriter) Write(b []byte) (int, error) {
-	os.Stdout.Write(b)
-	return len(b), nil
-}
diff --git a/build/logrotator.go b/build/logrotator.go
index aa4683bba2..2ae105fde0 100644
--- a/build/logrotator.go
+++ b/build/logrotator.go
@@ -6,9 +6,7 @@ import (
 	"io"
 	"os"
 	"path/filepath"
-	"sort"
 
-	"github.com/btcsuite/btclog"
 	"github.com/jrick/logrotate/rotator"
 	"github.com/klauspost/compress/zstd"
 )
@@ -16,45 +14,18 @@ import (
 // RotatingLogWriter is a wrapper around the LogWriter that supports log file
 // rotation.
 type RotatingLogWriter struct {
-	logWriter *LogWriter
+	// pipe is the write-end pipe for writing to the log rotator.
+	pipe *io.PipeWriter
 
-	backendLog *btclog.Backend
-
-	logRotator *rotator.Rotator
-
-	subsystemLoggers SubLoggers
+	rotator *rotator.Rotator
 }
 
-// A compile time check to ensure RotatingLogWriter implements the
-// LeveledSubLogger interface.
-var _ LeveledSubLogger = (*RotatingLogWriter)(nil)
-
 // NewRotatingLogWriter creates a new file rotating log writer.
 //
 // NOTE: `InitLogRotator` must be called to set up log rotation after creating
 // the writer.
 func NewRotatingLogWriter() *RotatingLogWriter {
-	logWriter := &LogWriter{}
-	backendLog := btclog.NewBackend(logWriter)
-	return &RotatingLogWriter{
-		logWriter:        logWriter,
-		backendLog:       backendLog,
-		subsystemLoggers: SubLoggers{},
-	}
-}
-
-// GenSubLogger creates a new sublogger. A shutdown callback function
-// is provided to be able to shutdown in case of a critical error.
-func (r *RotatingLogWriter) GenSubLogger(tag string, shutdown func()) btclog.Logger {
-	logger := r.backendLog.Logger(tag)
-	return NewShutdownLogger(logger, shutdown)
-}
-
-// RegisterSubLogger registers a new subsystem logger.
-func (r *RotatingLogWriter) RegisterSubLogger(subsystem string,
-	logger btclog.Logger) {
-
-	r.subsystemLoggers[subsystem] = logger
+	return &RotatingLogWriter{}
 }
 
 // InitLogRotator initializes the log file rotator to write logs to logFile and
@@ -68,7 +39,8 @@ func (r *RotatingLogWriter) InitLogRotator(logFile, logCompressor string,
 	if err != nil {
 		return fmt.Errorf("failed to create log directory: %w", err)
 	}
-	r.logRotator, err = rotator.New(
+
+	r.rotator, err = rotator.New(
 		logFile, int64(maxLogFileSize*1024), false, maxLogFiles,
 	)
 	if err != nil {
@@ -94,7 +66,7 @@ func (r *RotatingLogWriter) InitLogRotator(logFile, logCompressor string,
 	}
 
 	// Apply the compressor and its file suffix to the log rotator.
-	r.logRotator.SetCompressor(c, logCompressors[logCompressor])
+	r.rotator.SetCompressor(c, logCompressors[logCompressor])
 
 	// Run rotator as a goroutine now but make sure we catch any errors
 	// that happen in case something with the rotation goes wrong during
@@ -102,75 +74,32 @@ func (r *RotatingLogWriter) InitLogRotator(logFile, logCompressor string,
 	// create a new logfile for whatever reason).
 	pr, pw := io.Pipe()
 	go func() {
-		err := r.logRotator.Run(pr)
+		err := r.rotator.Run(pr)
 		if err != nil {
 			_, _ = fmt.Fprintf(os.Stderr,
 				"failed to run file rotator: %v\n", err)
 		}
 	}()
 
-	r.logWriter.RotatorPipe = pw
-	return nil
-}
+	r.pipe = pw
 
-// Close closes the underlying log rotator if it has already been created.
-func (r *RotatingLogWriter) Close() error {
-	if r.logRotator != nil {
-		return r.logRotator.Close()
-	}
 	return nil
 }
 
-// SubLoggers returns all currently registered subsystem loggers for this log
-// writer.
-//
-// NOTE: This is part of the LeveledSubLogger interface.
-func (r *RotatingLogWriter) SubLoggers() SubLoggers {
-	return r.subsystemLoggers
-}
-
-// SupportedSubsystems returns a sorted string slice of all keys in the
-// subsystems map, corresponding to the names of the subsystems.
-//
-// NOTE: This is part of the LeveledSubLogger interface.
-func (r *RotatingLogWriter) SupportedSubsystems() []string {
-	// Convert the subsystemLoggers map keys to a string slice.
-	subsystems := make([]string, 0, len(r.subsystemLoggers))
-	for subsysID := range r.subsystemLoggers {
-		subsystems = append(subsystems, subsysID)
+// Write writes the byte slice to the log rotator, if present.
+func (r *RotatingLogWriter) Write(b []byte) (int, error) {
+	if r.rotator != nil {
+		return r.rotator.Write(b)
 	}
 
-	// Sort the subsystems for stable display.
-	sort.Strings(subsystems)
-	return subsystems
+	return len(b), nil
 }
 
-// SetLogLevel sets the logging level for provided subsystem. Invalid
-// subsystems are ignored. Uninitialized subsystems are dynamically created as
-// needed.
-//
-// NOTE: This is part of the LeveledSubLogger interface.
-func (r *RotatingLogWriter) SetLogLevel(subsystemID string, logLevel string) {
-	// Ignore invalid subsystems.
-	logger, ok := r.subsystemLoggers[subsystemID]
-	if !ok {
-		return
+// Close closes the underlying log rotator if it has already been created.
+func (r *RotatingLogWriter) Close() error {
+	if r.rotator != nil {
+		return r.rotator.Close()
 	}
 
-	// Defaults to info if the log level is invalid.
-	level, _ := btclog.LevelFromString(logLevel)
-	logger.SetLevel(level)
-}
-
-// SetLogLevels sets the log level for all subsystem loggers to the passed
-// level. It also dynamically creates the subsystem loggers as needed, so it
-// can be used to initialize the logging system.
-//
-// NOTE: This is part of the LeveledSubLogger interface.
-func (r *RotatingLogWriter) SetLogLevels(logLevel string) {
-	// Configure all sub-systems with the new logging level. Dynamically
-	// create loggers as needed.
-	for subsystemID := range r.subsystemLoggers {
-		r.SetLogLevel(subsystemID, logLevel)
-	}
+	return nil
 }
diff --git a/build/prefix_log.go b/build/prefix_log.go
index 926b3f58f3..3db114ffcf 100644
--- a/build/prefix_log.go
+++ b/build/prefix_log.go
@@ -1,18 +1,25 @@
 package build
 
-import "github.com/btcsuite/btclog"
+import (
+	"context"
+
+	btclogv1 "github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
+)
 
 // PrefixLog is a pass-through logger that adds a prefix to every logged line.
 type PrefixLog struct {
 	log    btclog.Logger
 	prefix string
+	attr   []any
 }
 
 // NewPrefixLog instantiates a new prefixed logger.
-func NewPrefixLog(prefix string, log btclog.Logger) *PrefixLog {
+func NewPrefixLog(prefix string, log btclog.Logger, attrs ...any) *PrefixLog {
 	return &PrefixLog{
-		prefix: prefix,
 		log:    log,
+		prefix: prefix,
+		attr:   attrs,
 	}
 }
 
@@ -22,10 +29,16 @@ func (p *PrefixLog) addFormatPrefix(s string) string {
 }
 
 // addArgsPrefix prepends the prefix to a list of arguments.
-func (p *PrefixLog) addArgsPrefix(args []interface{}) []interface{} {
+func (p *PrefixLog) addArgsPrefix(args []any) []any {
 	return append([]interface{}{p.prefix}, args...)
 }
 
+// mergeAttr merges the given set of attributes with any attributes that the
+// logger was initialised with.
+func (p *PrefixLog) mergeAttr(attrs []any) []any {
+	return append(append([]any{}, attrs...), p.attr...)
+}
+
 // Tracef formats message according to format specifier and writes to to log
 // with LevelTrace.
 func (p *PrefixLog) Tracef(format string, params ...interface{}) {
@@ -98,13 +111,55 @@ func (p *PrefixLog) Critical(v ...interface{}) {
 	p.log.Critical(p.addArgsPrefix(v)...)
 }
 
+// TraceS writes a structured log with the given message and key-value pair
+// attributes with LevelTrace to the log.
+func (p *PrefixLog) TraceS(ctx context.Context, msg string, attrs ...any) {
+	p.log.TraceS(ctx, p.addFormatPrefix(msg), p.mergeAttr(attrs)...)
+}
+
+// DebugS writes a structured log with the given message and key-value pair
+// attributes with LevelDebug to the log.
+func (p *PrefixLog) DebugS(ctx context.Context, msg string, attrs ...any) {
+	p.log.DebugS(ctx, p.addFormatPrefix(msg), p.mergeAttr(attrs)...)
+}
+
+// InfoS writes a structured log with the given message and key-value pair
+// attributes with LevelInfo to the log.
+func (p *PrefixLog) InfoS(ctx context.Context, msg string, attrs ...any) {
+	p.log.InfoS(ctx, p.addFormatPrefix(msg), p.mergeAttr(attrs)...)
+}
+
+// WarnS writes a structured log with the given message and key-value pair
+// attributes with LevelWarn to the log.
+func (p *PrefixLog) WarnS(ctx context.Context, msg string, err error,
+	attrs ...any) {
+
+	p.log.WarnS(ctx, p.addFormatPrefix(msg), err, p.mergeAttr(attrs)...)
+}
+
+// ErrorS writes a structured log with the given message and key-value pair
+// attributes with LevelError to the log.
+func (p *PrefixLog) ErrorS(ctx context.Context, msg string, err error,
+	attrs ...any) {
+
+	p.log.ErrorS(ctx, p.addFormatPrefix(msg), err, p.mergeAttr(attrs)...)
+}
+
+// CriticalS writes a structured log with the given message and key-value pair
+// attributes with LevelCritical to the log.
+func (p *PrefixLog) CriticalS(ctx context.Context, msg string, err error,
+	attrs ...any) {
+
+	p.log.CriticalS(ctx, p.addFormatPrefix(msg), err, p.mergeAttr(attrs)...)
+}
+
 // Level returns the current logging level.
-func (p *PrefixLog) Level() btclog.Level {
+func (p *PrefixLog) Level() btclogv1.Level {
 	return p.log.Level()
 }
 
 // SetLevel changes the logging level to the passed level.
-func (p *PrefixLog) SetLevel(level btclog.Level) {
+func (p *PrefixLog) SetLevel(level btclogv1.Level) {
 	p.log.SetLevel(level)
 }
 
diff --git a/build/sub_logger.go b/build/sub_logger.go
new file mode 100644
index 0000000000..2a68e6cd55
--- /dev/null
+++ b/build/sub_logger.go
@@ -0,0 +1,263 @@
+package build
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+	"sync"
+
+	"github.com/btcsuite/btclog/v2"
+)
+
+// SubLogCreator can be used to create a new logger for a particular subsystem.
+type SubLogCreator interface {
+	// Logger returns a new logger for a particular subsytem.
+	Logger(subsystemTag string) btclog.Logger
+}
+
+// SubLoggerManager manages a set of subsystem loggers. Level updates will be
+// applied to all the loggers managed by the manager.
+type SubLoggerManager struct {
+	genLogger SubLogCreator
+
+	loggers SubLoggers
+	mu      sync.Mutex
+}
+
+// A compile time check to ensure SubLoggerManager implements the
+// LeveledSubLogger interface.
+var _ LeveledSubLogger = (*SubLoggerManager)(nil)
+
+// NewSubLoggerManager constructs a new SubLoggerManager.
+func NewSubLoggerManager(handlers ...btclog.Handler) *SubLoggerManager {
+	return &SubLoggerManager{
+		loggers: make(SubLoggers),
+		genLogger: newSubLogGenerator(
+			newHandlerSet(btclog.LevelInfo, handlers...),
+		),
+	}
+}
+
+// GenSubLogger creates a new sub-logger and adds it to the set managed by the
+// SubLoggerManager. A shutdown callback function is provided to be able to shut
+// down in case of a critical error.
+func (r *SubLoggerManager) GenSubLogger(subsystem string,
+	shutdown func()) btclog.Logger {
+
+	// Create a new logger with the given subsystem tag.
+	logger := r.genLogger.Logger(subsystem)
+
+	// Wrap the new logger in a Shutdown logger so that the shutdown
+	// call back is called if a critical log is ever written via this new
+	// logger.
+	l := NewShutdownLogger(logger, shutdown)
+
+	r.RegisterSubLogger(subsystem, l)
+
+	return l
+}
+
+// RegisterSubLogger registers the given logger under the given subsystem name.
+func (r *SubLoggerManager) RegisterSubLogger(subsystem string,
+	logger btclog.Logger) {
+
+	// Add the new logger to the set of loggers managed by the manager.
+	r.mu.Lock()
+	r.loggers[subsystem] = logger
+	r.mu.Unlock()
+}
+
+// SubLoggers returns all currently registered subsystem loggers for this log
+// writer.
+//
+// NOTE: This is part of the LeveledSubLogger interface.
+func (r *SubLoggerManager) SubLoggers() SubLoggers {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	return r.loggers
+}
+
+// SupportedSubsystems returns a sorted string slice of all keys in the
+// subsystems map, corresponding to the names of the subsystems.
+//
+// NOTE: This is part of the LeveledSubLogger interface.
+func (r *SubLoggerManager) SupportedSubsystems() []string {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	// Convert the subsystemLoggers map keys to a string slice.
+	subsystems := make([]string, 0, len(r.loggers))
+	for subsysID := range r.loggers {
+		subsystems = append(subsystems, subsysID)
+	}
+
+	// Sort the subsystems for stable display.
+	sort.Strings(subsystems)
+
+	return subsystems
+}
+
+// SetLogLevel sets the logging level for provided subsystem. Invalid
+// subsystems are ignored. Uninitialized subsystems are dynamically created as
+// needed.
+//
+// NOTE: This is part of the LeveledSubLogger interface.
+func (r *SubLoggerManager) SetLogLevel(subsystemID string, logLevel string) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	r.setLogLevelUnsafe(subsystemID, logLevel)
+}
+
+// setLogLevelUnsafe sets the logging level for provided subsystem. Invalid
+// subsystems are ignored. Uninitialized subsystems are dynamically created as
+// needed.
+//
+// NOTE: the SubLoggerManager mutex must be held before calling this method.
+func (r *SubLoggerManager) setLogLevelUnsafe(subsystemID string,
+	logLevel string) {
+
+	// Ignore invalid subsystems.
+	logger, ok := r.loggers[subsystemID]
+	if !ok {
+		return
+	}
+
+	// Defaults to info if the log level is invalid.
+	level, _ := btclog.LevelFromString(logLevel)
+
+	logger.SetLevel(level)
+}
+
+// SetLogLevels sets the log level for all subsystem loggers to the passed
+// level. It also dynamically creates the subsystem loggers as needed, so it
+// can be used to initialize the logging system.
+//
+// NOTE: This is part of the LeveledSubLogger interface.
+func (r *SubLoggerManager) SetLogLevels(logLevel string) {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	// Configure all sub-systems with the new logging level. Dynamically
+	// create loggers as needed.
+	for subsystemID := range r.loggers {
+		r.setLogLevelUnsafe(subsystemID, logLevel)
+	}
+}
+
+// SubLoggers is a type that holds a map of subsystem loggers keyed by their
+// subsystem name.
+type SubLoggers map[string]btclog.Logger
+
+// LeveledSubLogger provides the ability to retrieve the subsystem loggers of
+// a logger and set their log levels individually or all at once.
+type LeveledSubLogger interface {
+	// SubLoggers returns the map of all registered subsystem loggers.
+	SubLoggers() SubLoggers
+
+	// SupportedSubsystems returns a slice of strings containing the names
+	// of the supported subsystems. Should ideally correspond to the keys
+	// of the subsystem logger map and be sorted.
+	SupportedSubsystems() []string
+
+	// SetLogLevel assigns an individual subsystem logger a new log level.
+	SetLogLevel(subsystemID string, logLevel string)
+
+	// SetLogLevels assigns all subsystem loggers the same new log level.
+	SetLogLevels(logLevel string)
+}
+
+// ParseAndSetDebugLevels attempts to parse the specified debug level and set
+// the levels accordingly on the given logger. An appropriate error is returned
+// if anything is invalid.
+func ParseAndSetDebugLevels(level string, logger LeveledSubLogger) error {
+	// Split at the delimiter.
+	levels := strings.Split(level, ",")
+	if len(levels) == 0 {
+		return fmt.Errorf("invalid log level: %v", level)
+	}
+
+	// If the first entry has no =, treat is as the log level for all
+	// subsystems.
+	globalLevel := levels[0]
+	if !strings.Contains(globalLevel, "=") {
+		// Validate debug log level.
+		if !validLogLevel(globalLevel) {
+			str := "the specified debug level [%v] is invalid"
+
+			return fmt.Errorf(str, globalLevel)
+		}
+
+		// Change the logging level for all subsystems.
+		logger.SetLogLevels(globalLevel)
+
+		// The rest will target specific subsystems.
+		levels = levels[1:]
+	}
+
+	// Go through the subsystem/level pairs while detecting issues and
+	// update the log levels accordingly.
+	for _, logLevelPair := range levels {
+		if !strings.Contains(logLevelPair, "=") {
+			str := "the specified debug level contains an " +
+				"invalid subsystem/level pair [%v]"
+
+			return fmt.Errorf(str, logLevelPair)
+		}
+
+		// Extract the specified subsystem and log level.
+		fields := strings.Split(logLevelPair, "=")
+		if len(fields) != 2 {
+			str := "the specified debug level has an invalid " +
+				"format [%v] -- use format subsystem1=level1," +
+				"subsystem2=level2"
+
+			return fmt.Errorf(str, logLevelPair)
+		}
+		subsysID, logLevel := fields[0], fields[1]
+		subLoggers := logger.SubLoggers()
+
+		// Validate subsystem.
+		if _, exists := subLoggers[subsysID]; !exists {
+			str := "the specified subsystem [%v] is invalid -- " +
+				"supported subsystems are %v"
+
+			return fmt.Errorf(
+				str, subsysID, logger.SupportedSubsystems(),
+			)
+		}
+
+		// Validate log level.
+		if !validLogLevel(logLevel) {
+			str := "the specified debug level [%v] is invalid"
+			return fmt.Errorf(str, logLevel)
+		}
+
+		logger.SetLogLevel(subsysID, logLevel)
+	}
+
+	return nil
+}
+
+// validLogLevel returns whether or not logLevel is a valid debug log level.
+func validLogLevel(logLevel string) bool {
+	switch logLevel {
+	case "trace":
+		fallthrough
+	case "debug":
+		fallthrough
+	case "info":
+		fallthrough
+	case "warn":
+		fallthrough
+	case "error":
+		fallthrough
+	case "critical":
+		fallthrough
+	case "off":
+		return true
+	}
+
+	return false
+}
diff --git a/chainntnfs/log.go b/chainntnfs/log.go
index f21246053e..cc06e3bc63 100644
--- a/chainntnfs/log.go
+++ b/chainntnfs/log.go
@@ -1,7 +1,7 @@
 package chainntnfs
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/chainreg/log.go b/chainreg/log.go
index 2670794786..1a8e1148f8 100644
--- a/chainreg/log.go
+++ b/chainreg/log.go
@@ -1,7 +1,7 @@
 package chainreg
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/chanacceptor/log.go b/chanacceptor/log.go
index 26e131c438..3f9c925d30 100644
--- a/chanacceptor/log.go
+++ b/chanacceptor/log.go
@@ -1,7 +1,7 @@
 package chanacceptor
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/chanbackup/log.go b/chanbackup/log.go
index 34eb9e93ff..6f7147caa4 100644
--- a/chanbackup/log.go
+++ b/chanbackup/log.go
@@ -1,7 +1,7 @@
 package chanbackup
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/chanfitness/log.go b/chanfitness/log.go
index 626f7538dc..b729c51f7b 100644
--- a/chanfitness/log.go
+++ b/chanfitness/log.go
@@ -1,7 +1,7 @@
 package chanfitness
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/channeldb/log.go b/channeldb/log.go
index 10b1b54d3c..b423154d3e 100644
--- a/channeldb/log.go
+++ b/channeldb/log.go
@@ -1,7 +1,7 @@
 package channeldb
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	mig "github.com/lightningnetwork/lnd/channeldb/migration"
 	"github.com/lightningnetwork/lnd/channeldb/migration12"
diff --git a/channeldb/migration/log.go b/channeldb/migration/log.go
index 5085596dac..b60e34978f 100644
--- a/channeldb/migration/log.go
+++ b/channeldb/migration/log.go
@@ -1,6 +1,6 @@
 package migration
 
-import "github.com/btcsuite/btclog"
+import "github.com/btcsuite/btclog/v2"
 
 // log is a logger that is initialized as disabled.  This means the package will
 // not perform any logging by default until a logger is set.
diff --git a/channeldb/migration12/log.go b/channeldb/migration12/log.go
index 1352e52aea..9581808421 100644
--- a/channeldb/migration12/log.go
+++ b/channeldb/migration12/log.go
@@ -1,7 +1,7 @@
 package migration12
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration13/log.go b/channeldb/migration13/log.go
index 33ec1812af..ff8169f30e 100644
--- a/channeldb/migration13/log.go
+++ b/channeldb/migration13/log.go
@@ -1,7 +1,7 @@
 package migration13
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration16/log.go b/channeldb/migration16/log.go
index cb946854cf..ec134bc186 100644
--- a/channeldb/migration16/log.go
+++ b/channeldb/migration16/log.go
@@ -1,7 +1,7 @@
 package migration16
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration20/log.go b/channeldb/migration20/log.go
index da75760d08..23a97de7d0 100644
--- a/channeldb/migration20/log.go
+++ b/channeldb/migration20/log.go
@@ -1,7 +1,7 @@
 package migration20
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled. This means the package
diff --git a/channeldb/migration24/log.go b/channeldb/migration24/log.go
index a74cb039d4..0424df79e4 100644
--- a/channeldb/migration24/log.go
+++ b/channeldb/migration24/log.go
@@ -1,7 +1,7 @@
 package migration24
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration25/log.go b/channeldb/migration25/log.go
index e308d2755a..07b9f60ad7 100644
--- a/channeldb/migration25/log.go
+++ b/channeldb/migration25/log.go
@@ -1,7 +1,7 @@
 package migration25
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration26/log.go b/channeldb/migration26/log.go
index b7326a3130..3bcd276ad3 100644
--- a/channeldb/migration26/log.go
+++ b/channeldb/migration26/log.go
@@ -1,7 +1,7 @@
 package migration26
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration27/log.go b/channeldb/migration27/log.go
index 70cdb41ec8..c4dd1a1284 100644
--- a/channeldb/migration27/log.go
+++ b/channeldb/migration27/log.go
@@ -1,7 +1,7 @@
 package migration27
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration29/log.go b/channeldb/migration29/log.go
index b9ae19a5f5..3563c074f3 100644
--- a/channeldb/migration29/log.go
+++ b/channeldb/migration29/log.go
@@ -1,6 +1,6 @@
 package migration29
 
-import "github.com/btcsuite/btclog"
+import "github.com/btcsuite/btclog/v2"
 
 // log is a logger that is initialized as disabled. This means the package will
 // not perform any logging by default until a logger is set.
diff --git a/channeldb/migration30/log.go b/channeldb/migration30/log.go
index 1a1672fe6f..8b58b99a62 100644
--- a/channeldb/migration30/log.go
+++ b/channeldb/migration30/log.go
@@ -1,7 +1,7 @@
 package migration30
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration31/log.go b/channeldb/migration31/log.go
index 2a863b7c1c..42b6cadfb9 100644
--- a/channeldb/migration31/log.go
+++ b/channeldb/migration31/log.go
@@ -1,7 +1,7 @@
 package migration31
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration32/log.go b/channeldb/migration32/log.go
index 98709c28ea..2a921a600d 100644
--- a/channeldb/migration32/log.go
+++ b/channeldb/migration32/log.go
@@ -1,7 +1,7 @@
 package migration32
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration33/log.go b/channeldb/migration33/log.go
index e9b271f5df..62b286b28a 100644
--- a/channeldb/migration33/log.go
+++ b/channeldb/migration33/log.go
@@ -1,7 +1,7 @@
 package migration33
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channeldb/migration_01_to_11/log.go b/channeldb/migration_01_to_11/log.go
index b169b5afb1..22c3932618 100644
--- a/channeldb/migration_01_to_11/log.go
+++ b/channeldb/migration_01_to_11/log.go
@@ -1,7 +1,7 @@
 package migration_01_to_11
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/channelnotifier/log.go b/channelnotifier/log.go
index 5756f69976..6044e84548 100644
--- a/channelnotifier/log.go
+++ b/channelnotifier/log.go
@@ -1,7 +1,7 @@
 package channelnotifier
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/cluster/log.go b/cluster/log.go
index 11ced0871c..5551809c12 100644
--- a/cluster/log.go
+++ b/cluster/log.go
@@ -1,7 +1,7 @@
 package cluster
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/config.go b/config.go
index 2ad83c1265..a427e4c7d4 100644
--- a/config.go
+++ b/config.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/btcsuite/btcd/btcutil"
 	"github.com/btcsuite/btcd/chaincfg"
+	"github.com/btcsuite/btclog/v2"
 	flags "github.com/jessevdk/go-flags"
 	"github.com/lightninglabs/neutrino"
 	"github.com/lightningnetwork/lnd/autopilot"
@@ -494,9 +495,11 @@ type Config struct {
 
 	GRPC *GRPCConfig `group:"grpc" namespace:"grpc"`
 
-	// LogWriter is the root logger that all of the daemon's subloggers are
+	// SubLogMgr is the root logger that all the daemon's subloggers are
 	// hooked up to.
-	LogWriter *build.RotatingLogWriter
+	SubLogMgr  *build.SubLoggerManager
+	LogRotator *build.RotatingLogWriter
+	LogConfig  *build.LogConfig `group:"logging" namespace:"logging"`
 
 	// networkDir is the path to the directory of the currently active
 	// network. This path will hold the files related to each different
@@ -714,7 +717,7 @@ func DefaultConfig() Config {
 		MaxChannelFeeAllocation:   htlcswitch.DefaultMaxLinkFeeAllocation,
 		MaxCommitFeeRateAnchors:   lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
 		MaxFeeExposure:            uint64(htlcswitch.DefaultMaxFeeExposure.ToSatoshis()),
-		LogWriter:                 build.NewRotatingLogWriter(),
+		LogRotator:                build.NewRotatingLogWriter(),
 		DB:                        lncfg.DefaultDB(),
 		Cluster:                   lncfg.DefaultCluster(),
 		RPCMiddleware:             lncfg.DefaultRPCMiddleware(),
@@ -736,6 +739,7 @@ func DefaultConfig() Config {
 			ServerPingTimeout: defaultGrpcServerPingTimeout,
 			ClientPingMinWait: defaultGrpcClientPingMinWait,
 		},
+		LogConfig:         build.DefaultLogConfig(),
 		WtClient:          lncfg.DefaultWtClientCfg(),
 		HTTPHeaderTimeout: DefaultHTTPHeaderTimeout,
 	}
@@ -1400,10 +1404,24 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
 		lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
 	)
 
-	// A log writer must be passed in, otherwise we can't function and would
-	// run into a panic later on.
-	if cfg.LogWriter == nil {
-		return nil, mkErr("log writer missing in config")
+	var (
+		logCfg                            = cfg.LogConfig
+		logHandlers                       []btclog.Handler
+		consoleLogHandler, logFileHandler = build.NewDefaultLogHandlers(
+			logCfg, cfg.LogRotator,
+		)
+	)
+	maybeAddLogger := func(cmdOptionDisable bool, handler btclog.Handler) {
+		if !cmdOptionDisable {
+			logHandlers = append(logHandlers, handler)
+		}
+	}
+	switch build.LoggingType {
+	case build.LogTypeStdOut:
+		maybeAddLogger(logCfg.Console.Disable, consoleLogHandler)
+	case build.LogTypeDefault:
+		maybeAddLogger(logCfg.Console.Disable, consoleLogHandler)
+		maybeAddLogger(logCfg.File.Disable, logFileHandler)
 	}
 
 	if !build.SuportedLogCompressor(cfg.LogCompressor) {
@@ -1411,16 +1429,18 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
 			cfg.LogCompressor)
 	}
 
+	cfg.SubLogMgr = build.NewSubLoggerManager(logHandlers...)
+
 	// Initialize logging at the default logging level.
-	SetupLoggers(cfg.LogWriter, interceptor)
+	SetupLoggers(cfg.SubLogMgr, interceptor)
 
 	// Special show command to list supported subsystems and exit.
 	if cfg.DebugLevel == "show" {
 		fmt.Println("Supported subsystems",
-			cfg.LogWriter.SupportedSubsystems())
+			cfg.SubLogMgr.SupportedSubsystems())
 		os.Exit(0)
 	}
-	err = cfg.LogWriter.InitLogRotator(
+	err = cfg.LogRotator.InitLogRotator(
 		filepath.Join(cfg.LogDir, defaultLogFilename),
 		cfg.LogCompressor, cfg.MaxLogFileSize, cfg.MaxLogFiles,
 	)
@@ -1430,7 +1450,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
 	}
 
 	// Parse, validate, and set debug log level(s).
-	err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.LogWriter)
+	err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.SubLogMgr)
 	if err != nil {
 		str := "error parsing debug level: %v"
 		return nil, &lncfg.UsageError{Err: mkErr(str, err)}
diff --git a/config_builder.go b/config_builder.go
index 1c3a842ef1..7cc1a112d2 100644
--- a/config_builder.go
+++ b/config_builder.go
@@ -18,7 +18,7 @@ import (
 	"github.com/btcsuite/btcd/chaincfg"
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/wire"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/btcsuite/btcwallet/chain"
 	"github.com/btcsuite/btcwallet/waddrmgr"
 	"github.com/btcsuite/btcwallet/wallet"
diff --git a/contractcourt/contract_resolver.go b/contractcourt/contract_resolver.go
index 5acf800649..691822610a 100644
--- a/contractcourt/contract_resolver.go
+++ b/contractcourt/contract_resolver.go
@@ -7,7 +7,7 @@ import (
 	"io"
 
 	"github.com/btcsuite/btcd/wire"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/fn"
diff --git a/contractcourt/log.go b/contractcourt/log.go
index 2e52cc97dd..21844cc3d6 100644
--- a/contractcourt/log.go
+++ b/contractcourt/log.go
@@ -1,7 +1,7 @@
 package contractcourt
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/discovery/log.go b/discovery/log.go
index f09e7fb71b..0f885e9067 100644
--- a/discovery/log.go
+++ b/discovery/log.go
@@ -1,7 +1,7 @@
 package discovery
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md
index 9ff363c03c..3b20a054e6 100644
--- a/docs/release-notes/release-notes-0.19.0.md
+++ b/docs/release-notes/release-notes-0.19.0.md
@@ -75,6 +75,19 @@
   config settings to its own dedicated group. The old ones still work but will
   be removed in a future release.
  
+* [Update to use structured 
+  logging](https://github.com/lightningnetwork/lnd/pull/9083). This also 
+  introduces a new `--logging.console.disable` option to disable logs being 
+  written to stdout and a new `--logging.file.disable` option to disable writing 
+  logs to the standard log file. It also adds `--logging.console.no-timestamps`
+  and `--logging.file.no-timestamps` which can be used to omit timestamps in
+  log messages for the respective loggers. The new `--logging.console.call-site`
+  and `--logging.file.call-site` options can be used to include the call-site of
+  a log line. The options for this include "off" (default), "short" (source file
+  name and line number) and "long" (full path to source file and line number). 
+  Finally, the new `--logging.console.style` option can be used under the `dev` 
+  build tag to add styling to console logging.
+ 
 ## Breaking Changes
 ## Performance Improvements
 
diff --git a/funding/log.go b/funding/log.go
index 887d4f7192..357483db40 100644
--- a/funding/log.go
+++ b/funding/log.go
@@ -1,7 +1,7 @@
 package funding
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/go.mod b/go.mod
index b1e63f8cff..692e5ad75f 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,8 @@ require (
 	github.com/btcsuite/btcd/btcutil v1.1.5
 	github.com/btcsuite/btcd/btcutil/psbt v1.1.8
 	github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
-	github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
+	github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
+	github.com/btcsuite/btclog/v2 v2.0.0-20241017175713-3428138b75c7
 	github.com/btcsuite/btcwallet v0.16.10-0.20240912233857-ffb143c77cc5
 	github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
 	github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
diff --git a/go.sum b/go.sum
index 5c3b97e88a..d0f1882f32 100644
--- a/go.sum
+++ b/go.sum
@@ -89,8 +89,11 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtyd
 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ=
 github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
-github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
+github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c h1:4HxD1lBUGUddhzgaNgrCPsFWd7cGYNpeFUgd9ZIgyM0=
+github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhwT3lmrS4H3b/D1XAXxvh+tbhUm8xeHN2y3TQ=
+github.com/btcsuite/btclog/v2 v2.0.0-20241017175713-3428138b75c7 h1:3Ct3zN3VCEKVm5nceWBBEKczc+jvTfVyOEG71ob2Yuc=
+github.com/btcsuite/btclog/v2 v2.0.0-20241017175713-3428138b75c7/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
 github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
 github.com/btcsuite/btcwallet v0.16.10-0.20240912233857-ffb143c77cc5 h1:zYy233eUBvkF3lq2MUkybEhxhDsrRDSgiToIKN57mtk=
 github.com/btcsuite/btcwallet v0.16.10-0.20240912233857-ffb143c77cc5/go.mod h1:1HJXYbjJzgumlnxOC2+ViR1U+gnHWoOn7WeK5OfY1eU=
diff --git a/graph/log.go b/graph/log.go
index 7d2f741c66..4e03156688 100644
--- a/graph/log.go
+++ b/graph/log.go
@@ -1,7 +1,7 @@
 package graph
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/htlcswitch/hop/log.go b/htlcswitch/hop/log.go
index 4134d1662f..7b412b95d2 100644
--- a/htlcswitch/hop/log.go
+++ b/htlcswitch/hop/log.go
@@ -1,7 +1,7 @@
 package hop
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized with no output filters.  This
diff --git a/htlcswitch/link.go b/htlcswitch/link.go
index 7ce618574a..3532a23181 100644
--- a/htlcswitch/link.go
+++ b/htlcswitch/link.go
@@ -13,7 +13,7 @@ import (
 
 	"github.com/btcsuite/btcd/btcutil"
 	"github.com/btcsuite/btcd/wire"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/channeldb/models"
diff --git a/htlcswitch/log.go b/htlcswitch/log.go
index 79259be91c..f20310f8dd 100644
--- a/htlcswitch/log.go
+++ b/htlcswitch/log.go
@@ -1,7 +1,7 @@
 package htlcswitch
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/htlcswitch/hop"
 )
diff --git a/invoices/log.go b/invoices/log.go
index 5e3a0b17be..291a6aa68e 100644
--- a/invoices/log.go
+++ b/invoices/log.go
@@ -1,7 +1,7 @@
 package invoices
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lncfg/db.go b/lncfg/db.go
index 4072a51fea..1f7e0ae35d 100644
--- a/lncfg/db.go
+++ b/lncfg/db.go
@@ -7,7 +7,7 @@ import (
 	"path/filepath"
 	"time"
 
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/kvdb"
 	"github.com/lightningnetwork/lnd/kvdb/etcd"
 	"github.com/lightningnetwork/lnd/kvdb/postgres"
diff --git a/lncfg/log.go b/lncfg/log.go
index a22a215daf..f48516eb1f 100644
--- a/lncfg/log.go
+++ b/lncfg/log.go
@@ -1,7 +1,7 @@
 package lncfg
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnd.go b/lnd.go
index 8fb38b8f38..f511811950 100644
--- a/lnd.go
+++ b/lnd.go
@@ -149,7 +149,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
 
 	defer func() {
 		ltndLog.Info("Shutdown complete\n")
-		err := cfg.LogWriter.Close()
+		err := cfg.LogRotator.Close()
 		if err != nil {
 			ltndLog.Errorf("Could not close log rotator: %v", err)
 		}
diff --git a/lnrpc/autopilotrpc/log.go b/lnrpc/autopilotrpc/log.go
index 113486d0d9..6eace32443 100644
--- a/lnrpc/autopilotrpc/log.go
+++ b/lnrpc/autopilotrpc/log.go
@@ -1,7 +1,7 @@
 package autopilotrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/chainrpc/log.go b/lnrpc/chainrpc/log.go
index 6b68e324aa..d592e55b06 100644
--- a/lnrpc/chainrpc/log.go
+++ b/lnrpc/chainrpc/log.go
@@ -1,7 +1,7 @@
 package chainrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/devrpc/log.go b/lnrpc/devrpc/log.go
index 9cae145e52..8e13c1001f 100644
--- a/lnrpc/devrpc/log.go
+++ b/lnrpc/devrpc/log.go
@@ -1,7 +1,7 @@
 package devrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/invoicesrpc/log.go b/lnrpc/invoicesrpc/log.go
index f3644fdb51..7f284ad2ea 100644
--- a/lnrpc/invoicesrpc/log.go
+++ b/lnrpc/invoicesrpc/log.go
@@ -1,7 +1,7 @@
 package invoicesrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/neutrinorpc/log.go b/lnrpc/neutrinorpc/log.go
index fb2cd79ecf..be5c9ff1b1 100644
--- a/lnrpc/neutrinorpc/log.go
+++ b/lnrpc/neutrinorpc/log.go
@@ -1,7 +1,7 @@
 package neutrinorpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/peersrpc/log.go b/lnrpc/peersrpc/log.go
index 3fd59789cb..2196fdfc91 100644
--- a/lnrpc/peersrpc/log.go
+++ b/lnrpc/peersrpc/log.go
@@ -1,7 +1,7 @@
 package peersrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/routerrpc/log.go b/lnrpc/routerrpc/log.go
index 5c4ad01d83..a705e2b615 100644
--- a/lnrpc/routerrpc/log.go
+++ b/lnrpc/routerrpc/log.go
@@ -1,7 +1,7 @@
 package routerrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/signrpc/log.go b/lnrpc/signrpc/log.go
index 2dc6533435..cce9f7c436 100644
--- a/lnrpc/signrpc/log.go
+++ b/lnrpc/signrpc/log.go
@@ -1,7 +1,7 @@
 package signrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/verrpc/log.go b/lnrpc/verrpc/log.go
index 3553b56806..a6f3546dd9 100644
--- a/lnrpc/verrpc/log.go
+++ b/lnrpc/verrpc/log.go
@@ -1,7 +1,7 @@
 package verrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/walletrpc/log.go b/lnrpc/walletrpc/log.go
index 1916c549f2..eef49e7d0c 100644
--- a/lnrpc/walletrpc/log.go
+++ b/lnrpc/walletrpc/log.go
@@ -1,7 +1,7 @@
 package walletrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/watchtowerrpc/log.go b/lnrpc/watchtowerrpc/log.go
index a71b7827fb..2563affe1c 100644
--- a/lnrpc/watchtowerrpc/log.go
+++ b/lnrpc/watchtowerrpc/log.go
@@ -1,7 +1,7 @@
 package watchtowerrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnrpc/websocket_proxy.go b/lnrpc/websocket_proxy.go
index 4a46929657..b333ba1c8e 100644
--- a/lnrpc/websocket_proxy.go
+++ b/lnrpc/websocket_proxy.go
@@ -12,7 +12,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/gorilla/websocket"
 	"golang.org/x/net/context"
 )
diff --git a/lnrpc/wtclientrpc/config.go b/lnrpc/wtclientrpc/config.go
index b95fb1197c..81fb5f18f6 100644
--- a/lnrpc/wtclientrpc/config.go
+++ b/lnrpc/wtclientrpc/config.go
@@ -1,7 +1,7 @@
 package wtclientrpc
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/lncfg"
 	"github.com/lightningnetwork/lnd/watchtower/wtclient"
 )
diff --git a/lnwallet/btcwallet/log.go b/lnwallet/btcwallet/log.go
index 878db08e64..d0a589fed2 100644
--- a/lnwallet/btcwallet/log.go
+++ b/lnwallet/btcwallet/log.go
@@ -1,7 +1,7 @@
 package btcwallet
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnwallet/chainfee/log.go b/lnwallet/chainfee/log.go
index d5d0405782..536f73eff6 100644
--- a/lnwallet/chainfee/log.go
+++ b/lnwallet/chainfee/log.go
@@ -1,7 +1,7 @@
 package chainfee
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnwallet/chancloser/log.go b/lnwallet/chancloser/log.go
index 7189ed2a35..46c777aae8 100644
--- a/lnwallet/chancloser/log.go
+++ b/lnwallet/chancloser/log.go
@@ -1,7 +1,7 @@
 package chancloser
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnwallet/chanfunding/log.go b/lnwallet/chanfunding/log.go
index 159a96ca1d..4ef34f9852 100644
--- a/lnwallet/chanfunding/log.go
+++ b/lnwallet/chanfunding/log.go
@@ -1,7 +1,7 @@
 package chanfunding
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/lnwallet/channel.go b/lnwallet/channel.go
index 3a980df1a3..33360228d5 100644
--- a/lnwallet/channel.go
+++ b/lnwallet/channel.go
@@ -21,7 +21,7 @@ import (
 	"github.com/btcsuite/btcd/mempool"
 	"github.com/btcsuite/btcd/txscript"
 	"github.com/btcsuite/btcd/wire"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/davecgh/go-spew/spew"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/chainntnfs"
diff --git a/lnwallet/log.go b/lnwallet/log.go
index 7c8ce51984..5fde019d76 100644
--- a/lnwallet/log.go
+++ b/lnwallet/log.go
@@ -1,7 +1,7 @@
 package lnwallet
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/btcsuite/btcwallet/chain"
 	btcwallet "github.com/btcsuite/btcwallet/wallet"
 	"github.com/btcsuite/btcwallet/wtxmgr"
diff --git a/lnwallet/rpcwallet/log.go b/lnwallet/rpcwallet/log.go
index 190aa623ad..21b8a73d68 100644
--- a/lnwallet/rpcwallet/log.go
+++ b/lnwallet/rpcwallet/log.go
@@ -1,7 +1,7 @@
 package rpcwallet
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/log.go b/log.go
index a24c39fd40..faa4f15f4d 100644
--- a/log.go
+++ b/log.go
@@ -3,7 +3,8 @@ package lnd
 import (
 	"github.com/btcsuite/btcd/connmgr"
 	"github.com/btcsuite/btcd/rpcclient"
-	"github.com/btcsuite/btclog"
+	btclogv1 "github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightninglabs/neutrino"
 	sphinx "github.com/lightningnetwork/lightning-onion"
 	"github.com/lightningnetwork/lnd/autopilot"
@@ -95,7 +96,7 @@ var (
 
 // genSubLogger creates a logger for a subsystem. We provide an instance of
 // a signal.Interceptor to be able to shutdown in the case of a critical error.
-func genSubLogger(root *build.RotatingLogWriter,
+func genSubLogger(root *build.SubLoggerManager,
 	interceptor signal.Interceptor) func(string) btclog.Logger {
 
 	// Create a shutdown function which will request shutdown from our
@@ -116,7 +117,9 @@ func genSubLogger(root *build.RotatingLogWriter,
 }
 
 // SetupLoggers initializes all package-global logger variables.
-func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor) {
+//
+//nolint:lll
+func SetupLoggers(root *build.SubLoggerManager, interceptor signal.Interceptor) {
 	genLogger := genSubLogger(root, interceptor)
 
 	// Now that we have the proper root logger, we can replace the
@@ -132,9 +135,9 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor)
 	// `btcwallet.chain`, which is overwritten by `lnwallet`. To ensure the
 	// overwriting works, we need to initialize the loggers here so they
 	// can be overwritten later.
-	AddSubLogger(root, "BTCN", interceptor, neutrino.UseLogger)
-	AddSubLogger(root, "CMGR", interceptor, connmgr.UseLogger)
-	AddSubLogger(root, "RPCC", interceptor, rpcclient.UseLogger)
+	AddV1SubLogger(root, "BTCN", interceptor, neutrino.UseLogger)
+	AddV1SubLogger(root, "CMGR", interceptor, connmgr.UseLogger)
+	AddV1SubLogger(root, "RPCC", interceptor, rpcclient.UseLogger)
 
 	// Some of the loggers declared in the main lnd package are also used
 	// in sub packages.
@@ -149,7 +152,7 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor)
 	AddSubLogger(root, "CNCT", interceptor, contractcourt.UseLogger)
 	AddSubLogger(root, "UTXN", interceptor, contractcourt.UseNurseryLogger)
 	AddSubLogger(root, "BRAR", interceptor, contractcourt.UseBreachLogger)
-	AddSubLogger(root, "SPHX", interceptor, sphinx.UseLogger)
+	AddV1SubLogger(root, "SPHX", interceptor, sphinx.UseLogger)
 	AddSubLogger(root, "SWPR", interceptor, sweep.UseLogger)
 	AddSubLogger(root, "SGNR", interceptor, signrpc.UseLogger)
 	AddSubLogger(root, "WLKT", interceptor, walletrpc.UseLogger)
@@ -174,13 +177,13 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor)
 	AddSubLogger(root, routerrpc.Subsystem, interceptor, routerrpc.UseLogger)
 	AddSubLogger(root, chanfitness.Subsystem, interceptor, chanfitness.UseLogger)
 	AddSubLogger(root, verrpc.Subsystem, interceptor, verrpc.UseLogger)
-	AddSubLogger(root, healthcheck.Subsystem, interceptor, healthcheck.UseLogger)
+	AddV1SubLogger(root, healthcheck.Subsystem, interceptor, healthcheck.UseLogger)
 	AddSubLogger(root, chainreg.Subsystem, interceptor, chainreg.UseLogger)
 	AddSubLogger(root, chanacceptor.Subsystem, interceptor, chanacceptor.UseLogger)
 	AddSubLogger(root, funding.Subsystem, interceptor, funding.UseLogger)
 	AddSubLogger(root, cluster.Subsystem, interceptor, cluster.UseLogger)
 	AddSubLogger(root, rpcperms.Subsystem, interceptor, rpcperms.UseLogger)
-	AddSubLogger(root, tor.Subsystem, interceptor, tor.UseLogger)
+	AddV1SubLogger(root, tor.Subsystem, interceptor, tor.UseLogger)
 	AddSubLogger(root, btcwallet.Subsystem, interceptor, btcwallet.UseLogger)
 	AddSubLogger(root, rpcwallet.Subsystem, interceptor, rpcwallet.UseLogger)
 	AddSubLogger(root, peersrpc.Subsystem, interceptor, peersrpc.UseLogger)
@@ -193,7 +196,7 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor)
 
 // AddSubLogger is a helper method to conveniently create and register the
 // logger of one or more sub systems.
-func AddSubLogger(root *build.RotatingLogWriter, subsystem string,
+func AddSubLogger(root *build.SubLoggerManager, subsystem string,
 	interceptor signal.Interceptor, useLoggers ...func(btclog.Logger)) {
 
 	// genSubLogger will return a callback for creating a logger instance,
@@ -206,9 +209,9 @@ func AddSubLogger(root *build.RotatingLogWriter, subsystem string,
 	SetSubLogger(root, subsystem, logger, useLoggers...)
 }
 
-// SetSubLogger is a helper method to conveniently register the logger of a sub
-// system.
-func SetSubLogger(root *build.RotatingLogWriter, subsystem string,
+// SetSubLogger is a helper method to conveniently register the logger of a
+// sub system.
+func SetSubLogger(root *build.SubLoggerManager, subsystem string,
 	logger btclog.Logger, useLoggers ...func(btclog.Logger)) {
 
 	root.RegisterSubLogger(subsystem, logger)
@@ -216,3 +219,31 @@ func SetSubLogger(root *build.RotatingLogWriter, subsystem string,
 		useLogger(logger)
 	}
 }
+
+// AddV1SubLogger is a helper method to conveniently create and register the
+// logger of one or more sub systems.
+func AddV1SubLogger(root *build.SubLoggerManager, subsystem string,
+	interceptor signal.Interceptor, useLoggers ...func(btclogv1.Logger)) {
+
+	// genSubLogger will return a callback for creating a logger instance,
+	// which we will give to the root logger.
+	genLogger := genSubLogger(root, interceptor)
+
+	// Create and register just a single logger to prevent them from
+	// overwriting each other internally.
+	logger := build.NewSubLogger(subsystem, genLogger)
+	SetV1SubLogger(root, subsystem, logger, useLoggers...)
+}
+
+// SetV1SubLogger is a helper method to conveniently register the logger of a
+// sub system. Note that the btclog v2 logger implements the btclog v1 logger
+// which is why we can pass the v2 logger to the UseLogger call-backs that
+// expect the v1 logger.
+func SetV1SubLogger(root *build.SubLoggerManager, subsystem string,
+	logger btclog.Logger, useLoggers ...func(btclogv1.Logger)) {
+
+	root.RegisterSubLogger(subsystem, logger)
+	for _, useLogger := range useLoggers {
+		useLogger(logger)
+	}
+}
diff --git a/monitoring/log.go b/monitoring/log.go
index a35af85230..95f0efb232 100644
--- a/monitoring/log.go
+++ b/monitoring/log.go
@@ -1,7 +1,7 @@
 package monitoring
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/msgmux/log.go b/msgmux/log.go
index 63da4791f9..27abbf271c 100644
--- a/msgmux/log.go
+++ b/msgmux/log.go
@@ -1,7 +1,7 @@
 package msgmux
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/netann/log.go b/netann/log.go
index 85d8d68c34..4aaa15bc7a 100644
--- a/netann/log.go
+++ b/netann/log.go
@@ -1,7 +1,7 @@
 package netann
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/peer/brontide.go b/peer/brontide.go
index a66618f11c..ae4d373cac 100644
--- a/peer/brontide.go
+++ b/peer/brontide.go
@@ -17,7 +17,7 @@ import (
 	"github.com/btcsuite/btcd/connmgr"
 	"github.com/btcsuite/btcd/txscript"
 	"github.com/btcsuite/btcd/wire"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/davecgh/go-spew/spew"
 	"github.com/lightningnetwork/lnd/buffer"
 	"github.com/lightningnetwork/lnd/build"
diff --git a/peer/log.go b/peer/log.go
index f0d1a6672f..e4b075d19d 100644
--- a/peer/log.go
+++ b/peer/log.go
@@ -1,7 +1,7 @@
 package peer
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/peernotifier/log.go b/peernotifier/log.go
index 1178c0f3df..a06051b263 100644
--- a/peernotifier/log.go
+++ b/peernotifier/log.go
@@ -1,7 +1,7 @@
 package peernotifier
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/routing/blindedpath/log.go b/routing/blindedpath/log.go
index 979884fbd4..d6b39d7ef4 100644
--- a/routing/blindedpath/log.go
+++ b/routing/blindedpath/log.go
@@ -1,7 +1,7 @@
 package blindedpath
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/routing/chainview/log.go b/routing/chainview/log.go
index b411819dd2..5a2bad27fe 100644
--- a/routing/chainview/log.go
+++ b/routing/chainview/log.go
@@ -1,7 +1,7 @@
 package chainview
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/routing/log.go b/routing/log.go
index 741ec084ef..ad248bba98 100644
--- a/routing/log.go
+++ b/routing/log.go
@@ -1,7 +1,7 @@
 package routing
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/routing/chainview"
 )
diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go
index d848235d88..be8ce7c9f6 100644
--- a/routing/missioncontrol.go
+++ b/routing/missioncontrol.go
@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/btcsuite/btcd/btcutil"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/btcsuite/btcwallet/walletdb"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/channeldb"
diff --git a/routing/payment_session.go b/routing/payment_session.go
index 89fb2fd137..2ab9e4fd41 100644
--- a/routing/payment_session.go
+++ b/routing/payment_session.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 
 	"github.com/btcsuite/btcd/btcec/v2"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/channeldb/models"
diff --git a/rpcperms/interceptor.go b/rpcperms/interceptor.go
index cea9670c48..9bbef0414d 100644
--- a/rpcperms/interceptor.go
+++ b/rpcperms/interceptor.go
@@ -7,7 +7,7 @@ import (
 	"sync"
 	"sync/atomic"
 
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	"github.com/lightningnetwork/lnd/lnrpc"
 	"github.com/lightningnetwork/lnd/macaroons"
diff --git a/rpcperms/log.go b/rpcperms/log.go
index 209aacce58..76e4c8d409 100644
--- a/rpcperms/log.go
+++ b/rpcperms/log.go
@@ -1,7 +1,7 @@
 package rpcperms
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/rpcserver.go b/rpcserver.go
index 6f43adf7fb..76aa057bf1 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -7341,7 +7341,7 @@ func (r *rpcServer) DebugLevel(ctx context.Context,
 	if req.Show {
 		return &lnrpc.DebugLevelResponse{
 			SubSystems: strings.Join(
-				r.cfg.LogWriter.SupportedSubsystems(), " ",
+				r.cfg.SubLogMgr.SupportedSubsystems(), " ",
 			),
 		}, nil
 	}
@@ -7350,12 +7350,12 @@ func (r *rpcServer) DebugLevel(ctx context.Context,
 
 	// Otherwise, we'll attempt to set the logging level using the
 	// specified level spec.
-	err := build.ParseAndSetDebugLevels(req.LevelSpec, r.cfg.LogWriter)
+	err := build.ParseAndSetDebugLevels(req.LevelSpec, r.cfg.SubLogMgr)
 	if err != nil {
 		return nil, err
 	}
 
-	subLoggers := r.cfg.LogWriter.SubLoggers()
+	subLoggers := r.cfg.SubLogMgr.SubLoggers()
 	// Sort alphabetically by subsystem name.
 	var tags []string
 	for t := range subLoggers {
diff --git a/sample-lnd.conf b/sample-lnd.conf
index b863e60bf6..eeb6a9d311 100644
--- a/sample-lnd.conf
+++ b/sample-lnd.conf
@@ -970,6 +970,33 @@
 ; Instructs lnd to encrypt the private key using the wallet's seed.
 ; tor.encryptkey=false
 
+[logging]
+
+; Disable logging to stdout and stderror.
+; logging.console.disable=false
+
+; Don't add timestamps to logs written to stdout and stderr.
+; logging.console.no-timestamps=false
+
+; Include the log call-site in the log line written to stdout
+; and stderr. Options include 'off', 'short' and 'long'.
+; Default:
+;   logging.console.call-site=off
+; Example:
+;   logging.console.call-site=short
+
+; Disable logging to the standard LND log file.
+; logging.file.disable=false
+
+; Don't add timestamps to logs written to the standard LND log file.
+; logging.file.no-timestamps=false
+
+; Include the log call-site in the log line written the standard LND
+; log file. Options include 'off', 'short' and 'long'.
+; Default:
+;   logging.file.call-site=off
+; Example:
+;   logging.file.call-site=short
 
 [watchtower]
 
diff --git a/signal/log.go b/signal/log.go
index 6bde284b76..4fcbd6d7aa 100644
--- a/signal/log.go
+++ b/signal/log.go
@@ -1,6 +1,6 @@
 package signal
 
-import "github.com/btcsuite/btclog"
+import "github.com/btcsuite/btclog/v2"
 
 // log is a logger that is initialized with no output filters.  This
 // means the package will not perform any logging by default until the caller
diff --git a/subrpcserver_config.go b/subrpcserver_config.go
index 8ea5b0ab39..a4ee6d1a16 100644
--- a/subrpcserver_config.go
+++ b/subrpcserver_config.go
@@ -6,7 +6,7 @@ import (
 	"reflect"
 
 	"github.com/btcsuite/btcd/chaincfg"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/aliasmgr"
 	"github.com/lightningnetwork/lnd/autopilot"
 	"github.com/lightningnetwork/lnd/chainreg"
diff --git a/sweep/log.go b/sweep/log.go
index 1725023758..864c9e37a2 100644
--- a/sweep/log.go
+++ b/sweep/log.go
@@ -1,7 +1,7 @@
 package sweep
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/watchtower/log.go b/watchtower/log.go
index 8e9062d958..bfdbe5fcab 100644
--- a/watchtower/log.go
+++ b/watchtower/log.go
@@ -1,7 +1,7 @@
 package watchtower
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/watchtower/lookout"
 	"github.com/lightningnetwork/lnd/watchtower/wtclient"
diff --git a/watchtower/lookout/log.go b/watchtower/lookout/log.go
index b569b4ae0a..286478614e 100644
--- a/watchtower/lookout/log.go
+++ b/watchtower/lookout/log.go
@@ -1,7 +1,7 @@
 package lookout
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/watchtower/wtclient/client.go b/watchtower/wtclient/client.go
index aef0fe505e..6cf5d9114a 100644
--- a/watchtower/wtclient/client.go
+++ b/watchtower/wtclient/client.go
@@ -11,7 +11,7 @@ import (
 	"time"
 
 	"github.com/btcsuite/btcd/btcec/v2"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/keychain"
diff --git a/watchtower/wtclient/log.go b/watchtower/wtclient/log.go
index 8d2e37dda3..dbda57bada 100644
--- a/watchtower/wtclient/log.go
+++ b/watchtower/wtclient/log.go
@@ -1,7 +1,7 @@
 package wtclient
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 
diff --git a/watchtower/wtclient/queue.go b/watchtower/wtclient/queue.go
index ee03d288ad..68e4b6c180 100644
--- a/watchtower/wtclient/queue.go
+++ b/watchtower/wtclient/queue.go
@@ -7,7 +7,7 @@ import (
 	"sync/atomic"
 	"time"
 
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/watchtower/wtdb"
 )
 
diff --git a/watchtower/wtclient/session_negotiator.go b/watchtower/wtclient/session_negotiator.go
index ea17c5b47a..4cba7de62c 100644
--- a/watchtower/wtclient/session_negotiator.go
+++ b/watchtower/wtclient/session_negotiator.go
@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnwire"
 	"github.com/lightningnetwork/lnd/watchtower/blob"
diff --git a/watchtower/wtclient/session_queue.go b/watchtower/wtclient/session_queue.go
index 41ae28f2fa..4ee1597637 100644
--- a/watchtower/wtclient/session_queue.go
+++ b/watchtower/wtclient/session_queue.go
@@ -7,7 +7,7 @@ import (
 	"time"
 
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnwire"
diff --git a/watchtower/wtdb/log.go b/watchtower/wtdb/log.go
index fa0a64f6ac..474ff08b9b 100644
--- a/watchtower/wtdb/log.go
+++ b/watchtower/wtdb/log.go
@@ -1,7 +1,7 @@
 package wtdb
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/lightningnetwork/lnd/watchtower/wtdb/migration1"
 	"github.com/lightningnetwork/lnd/watchtower/wtdb/migration2"
diff --git a/watchtower/wtdb/migration1/log.go b/watchtower/wtdb/migration1/log.go
index 1dc1052809..f741ceaace 100644
--- a/watchtower/wtdb/migration1/log.go
+++ b/watchtower/wtdb/migration1/log.go
@@ -1,7 +1,7 @@
 package migration1
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtdb/migration2/log.go b/watchtower/wtdb/migration2/log.go
index fd64d2fcbe..ae1ca59a18 100644
--- a/watchtower/wtdb/migration2/log.go
+++ b/watchtower/wtdb/migration2/log.go
@@ -1,7 +1,7 @@
 package migration2
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtdb/migration3/log.go b/watchtower/wtdb/migration3/log.go
index 2f14ff5e75..07cc2730ea 100644
--- a/watchtower/wtdb/migration3/log.go
+++ b/watchtower/wtdb/migration3/log.go
@@ -1,7 +1,7 @@
 package migration3
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtdb/migration4/log.go b/watchtower/wtdb/migration4/log.go
index 3a609d76d6..eb85aa5c3a 100644
--- a/watchtower/wtdb/migration4/log.go
+++ b/watchtower/wtdb/migration4/log.go
@@ -1,7 +1,7 @@
 package migration4
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtdb/migration5/log.go b/watchtower/wtdb/migration5/log.go
index f0b725a2ef..d92b108ad6 100644
--- a/watchtower/wtdb/migration5/log.go
+++ b/watchtower/wtdb/migration5/log.go
@@ -1,7 +1,7 @@
 package migration5
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtdb/migration6/log.go b/watchtower/wtdb/migration6/log.go
index e43e7d27ec..b15662bba6 100644
--- a/watchtower/wtdb/migration6/log.go
+++ b/watchtower/wtdb/migration6/log.go
@@ -1,7 +1,7 @@
 package migration6
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtdb/migration7/log.go b/watchtower/wtdb/migration7/log.go
index 39f28b6c00..baf5a0990c 100644
--- a/watchtower/wtdb/migration7/log.go
+++ b/watchtower/wtdb/migration7/log.go
@@ -1,7 +1,7 @@
 package migration7
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtdb/migration8/log.go b/watchtower/wtdb/migration8/log.go
index ab35682c5a..412b95f8b8 100644
--- a/watchtower/wtdb/migration8/log.go
+++ b/watchtower/wtdb/migration8/log.go
@@ -1,7 +1,7 @@
 package migration8
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 )
 
 // log is a logger that is initialized as disabled.  This means the package will
diff --git a/watchtower/wtserver/log.go b/watchtower/wtserver/log.go
index 7939bd97f8..3c901b4861 100644
--- a/watchtower/wtserver/log.go
+++ b/watchtower/wtserver/log.go
@@ -1,7 +1,7 @@
 package wtserver
 
 import (
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 )
 


Test patch:
diff --git a/build/log_test.go b/build/log_test.go
index 001b8e08b3..dbce428e40 100644
--- a/build/log_test.go
+++ b/build/log_test.go
@@ -3,7 +3,7 @@ package build_test
 import (
 	"testing"
 
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/build"
 	"github.com/stretchr/testify/require"
 )
diff --git a/watchtower/wtclient/queue_test.go b/watchtower/wtclient/queue_test.go
index 529acb49a4..a8494c2166 100644
--- a/watchtower/wtclient/queue_test.go
+++ b/watchtower/wtclient/queue_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/btcsuite/btclog"
+	"github.com/btcsuite/btclog/v2"
 	"github.com/lightningnetwork/lnd/kvdb"
 	"github.com/lightningnetwork/lnd/lntest/wait"
 	"github.com/lightningnetwork/lnd/watchtower/wtdb"