diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-10-09 05:04:08 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-10-09 05:18:19 +0900 |
| commit | 87b5c30ef6d5b7b8a10d6a5a8610054c403923e6 (patch) | |
| tree | 805116c39a623bb30fc532af47ea3ed78b7304c0 /message/message.go | |
| parent | df9b77b077d967d35082adb8ab3b00dd67d7bc97 (diff) | |
message: relocate from container
This package is quite useful. This change allows it to be imported without importing container.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'message/message.go')
| -rw-r--r-- | message/message.go | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/message/message.go b/message/message.go new file mode 100644 index 00000000..36f8ae4b --- /dev/null +++ b/message/message.go @@ -0,0 +1,111 @@ +// Package message provides interfaces and a base implementation for extended reporting on top of [log.Logger] +package message + +import ( + "errors" + "log" + "sync/atomic" +) + +// Error is an error with a user-facing message. +type Error interface { + // Message returns a user-facing error message. + Message() string + + error +} + +// GetMessage returns whether an error implements [Error], and the message if it does. +func GetMessage(err error) (string, bool) { + var e Error + if !errors.As(err, &e) || e == nil { + return "", false + } + 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 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() +} + +// 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 + + logger *log.Logger + Suspendable +} + +// 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) GetLogger() *log.Logger { return msg.logger } + +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...) + } +} + +// 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 +} + +// 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") + } +} |
