aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/msg.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-09-29 02:33:10 +0900
committerOphestra <cat@gensokyo.uk>2025-09-29 06:11:47 +0900
commit46cd3a28c83e93b5d66c52d06a8366c11910d5c0 (patch)
tree6e25c5078b5cd9de78b7a6c9b253f1132358812b /container/msg.go
parentad1bc6794f0053c3e63eab408a0d530d53e8b51c (diff)
container: remove global msg
This frees all container instances of side effects. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container/msg.go')
-rw-r--r--container/msg.go89
1 files changed, 66 insertions, 23 deletions
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")
}
}