Files
hakurei/message/message.go
T
cat f869ff95a1
Test / Create distribution (push) Successful in 58s
Test / Sandbox (push) Successful in 2m48s
Test / ShareFS (push) Successful in 3m53s
Test / Hakurei (push) Successful in 4m0s
Test / Sandbox (race detector) (push) Successful in 5m37s
Test / Hakurei (race detector) (push) Successful in 6m40s
Test / Flake checks (push) Successful in 1m12s
all: apply modernisers
Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-06-08 14:58:24 +09:00

130 lines
3.7 KiB
Go

// 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) {
e, ok := errors.AsType[Error](err)
if !ok || 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 [New] function
// instead.
type defaultMsg struct {
verbose atomic.Bool
logger *log.Logger
Suspendable
}
// New initialises a downstream [log.Logger] for a new [Msg].
//
// The [log.Logger] should no longer be configured after [New] returns. If
// downstream is nil, a new logger is initialised in its place.
func New(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")
}
}