From 46cd3a28c83e93b5d66c52d06a8366c11910d5c0 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Mon, 29 Sep 2025 02:33:10 +0900 Subject: container: remove global msg This frees all container instances of side effects. Signed-off-by: Ophestra --- container/msg.go | 89 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 23 deletions(-) (limited to 'container/msg.go') diff --git a/container/msg.go b/container/msg.go index 1512a583..38c38ecd 100644 --- a/container/msg.go +++ b/container/msg.go @@ -23,45 +23,88 @@ func GetErrorMessage(err error) (string, bool) { return e.Message(), true } +// Msg is used for package-wide verbose logging. type Msg interface { + // GetLogger returns the address of the underlying [log.Logger]. + GetLogger() *log.Logger + + // IsVerbose atomically loads and returns whether [Msg] has verbose logging enabled. IsVerbose() bool + // SwapVerbose atomically stores a new verbose state and returns the previous value held by [Msg]. + SwapVerbose(verbose bool) bool + // Verbose passes its argument to the Println method of the underlying [log.Logger] if IsVerbose returns true. Verbose(v ...any) + // Verbosef passes its argument to the Printf method of the underlying [log.Logger] if IsVerbose returns true. Verbosef(format string, v ...any) - Suspend() + // Suspend causes the embedded [Suspendable] to withhold writes to its downstream [io.Writer]. + // Suspend returns false and is a noop if called between calls to Suspend and Resume. + Suspend() bool + + // Resume dumps the entire buffer held by the embedded [Suspendable] and stops withholding future writes. + // Resume returns false and is a noop if a call to Suspend does not precede it. Resume() bool + + // BeforeExit runs implementation-specific cleanup code, and optionally prints warnings. + // BeforeExit is called before [os.Exit]. BeforeExit() } -type DefaultMsg struct{ inactive atomic.Bool } +// defaultMsg is the default implementation of the [Msg] interface. +// The zero value is not safe for use. Callers should use the [NewMsg] function instead. +type defaultMsg struct { + verbose atomic.Bool -func (msg *DefaultMsg) IsVerbose() bool { return true } -func (msg *DefaultMsg) Verbose(v ...any) { - if !msg.inactive.Load() { - log.Println(v...) - } + logger *log.Logger + Suspendable } -func (msg *DefaultMsg) Verbosef(format string, v ...any) { - if !msg.inactive.Load() { - log.Printf(format, v...) + +// NewMsg initialises a downstream [log.Logger] for a new [Msg]. +// The [log.Logger] should no longer be configured after NewMsg returns. +// If downstream is nil, a new logger is initialised in its place. +func NewMsg(downstream *log.Logger) Msg { + if downstream == nil { + downstream = log.New(log.Writer(), "container: ", 0) } + + m := defaultMsg{logger: downstream} + m.Suspendable.Downstream = downstream.Writer() + downstream.SetOutput(&m.Suspendable) + return &m } -func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) } -func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) } -func (msg *DefaultMsg) BeforeExit() {} +func (msg *defaultMsg) GetLogger() *log.Logger { return msg.logger } -// msg is the [Msg] implemented used by all exported [container] functions. -var msg Msg = new(DefaultMsg) +func (msg *defaultMsg) IsVerbose() bool { return msg.verbose.Load() } +func (msg *defaultMsg) SwapVerbose(verbose bool) bool { return msg.verbose.Swap(verbose) } +func (msg *defaultMsg) Verbose(v ...any) { + if msg.verbose.Load() { + msg.logger.Println(v...) + } +} +func (msg *defaultMsg) Verbosef(format string, v ...any) { + if msg.verbose.Load() { + msg.logger.Printf(format, v...) + } +} -// GetOutput returns the current active [Msg] implementation. -func GetOutput() Msg { return msg } +// Resume calls [Suspendable.Resume] and prints a message if buffer was filled +// between calls to [Suspendable.Suspend] and Resume. +func (msg *defaultMsg) Resume() bool { + resumed, dropped, _, err := msg.Suspendable.Resume() + if err != nil { + // probably going to result in an error as well, so this message is as good as unreachable + msg.logger.Printf("cannot dump buffer on resume: %v", err) + } + if resumed && dropped > 0 { + msg.logger.Printf("dropped %d bytes while output is suspended", dropped) + } + return resumed +} -// SetOutput replaces the current active [Msg] implementation. -func SetOutput(v Msg) { - if v == nil { - msg = new(DefaultMsg) - } else { - msg = v +// BeforeExit prints a message if called between calls to [Suspendable.Suspend] and Resume. +func (msg *defaultMsg) BeforeExit() { + if msg.Resume() { + msg.logger.Printf("beforeExit reached on suspended output") } } -- cgit v1.3.1