aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/hlog
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-06-25 03:59:52 +0900
committerOphestra <cat@gensokyo.uk>2025-06-25 04:57:41 +0900
commit87e008d56de974947ebb99c2cc40b25d3c2cf43e (patch)
tree31791911e5226d6ec04e3fac7d91b0bf53e63aa5 /internal/hlog
parent399207321265307bb15f37d867f9370cd51c82a8 (diff)
treewide: rename to hakurei
Fortify makes little sense for a container tool. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/hlog')
-rw-r--r--internal/hlog/errors.go81
-rw-r--r--internal/hlog/hlog.go86
-rw-r--r--internal/hlog/msg.go12
-rw-r--r--internal/hlog/verbose.go23
4 files changed, 202 insertions, 0 deletions
diff --git a/internal/hlog/errors.go b/internal/hlog/errors.go
new file mode 100644
index 00000000..d731f57f
--- /dev/null
+++ b/internal/hlog/errors.go
@@ -0,0 +1,81 @@
+package hlog
+
+import (
+ "fmt"
+ "log"
+ "reflect"
+ "strings"
+)
+
+// baseError implements a basic error container
+type baseError struct {
+ Err error
+}
+
+func (e *baseError) Error() string { return e.Err.Error() }
+func (e *baseError) Unwrap() error { return e.Err }
+
+// BaseError implements an error container with a user-facing message
+type BaseError struct {
+ message string
+ baseError
+}
+
+// Message returns a user-facing error message
+func (e *BaseError) Message() string { return e.message }
+
+// WrapErr wraps an error with a corresponding message.
+func WrapErr(err error, a ...any) error {
+ if err == nil {
+ return nil
+ }
+ return wrapErr(err, fmt.Sprintln(a...))
+}
+
+// WrapErrSuffix wraps an error with a corresponding message with err at the end of the message.
+func WrapErrSuffix(err error, a ...any) error {
+ if err == nil {
+ return nil
+ }
+ return wrapErr(err, fmt.Sprintln(append(a, err)...))
+}
+
+// WrapErrFunc wraps an error with a corresponding message returned by f.
+func WrapErrFunc(err error, f func(err error) string) error {
+ if err == nil {
+ return nil
+ }
+ return wrapErr(err, f(err))
+}
+
+func wrapErr(err error, message string) *BaseError {
+ return &BaseError{message, baseError{err}}
+}
+
+var (
+ baseErrorType = reflect.TypeFor[*BaseError]()
+)
+
+func AsBaseError(err error, target **BaseError) bool {
+ v := reflect.ValueOf(err)
+ if !v.CanConvert(baseErrorType) {
+ return false
+ }
+
+ *target = v.Convert(baseErrorType).Interface().(*BaseError)
+ return true
+}
+
+func PrintBaseError(err error, fallback string) {
+ var e *BaseError
+
+ if AsBaseError(err, &e) {
+ if msg := e.Message(); strings.TrimSpace(msg) != "" {
+ log.Print(msg)
+ return
+ }
+ Verbose("*"+fallback, err)
+ return
+ }
+ log.Println(fallback, err)
+}
diff --git a/internal/hlog/hlog.go b/internal/hlog/hlog.go
new file mode 100644
index 00000000..415d16bf
--- /dev/null
+++ b/internal/hlog/hlog.go
@@ -0,0 +1,86 @@
+// Package hlog provides various functions for output messages.
+package hlog
+
+import (
+ "bytes"
+ "io"
+ "log"
+ "os"
+ "sync"
+ "sync/atomic"
+ "syscall"
+)
+
+const (
+ bufSize = 4 * 1024
+ bufSizeMax = 16 * 1024 * 1024
+)
+
+var o = &suspendable{w: os.Stderr}
+
+// Prepare configures the system logger for [Suspend] and [Resume] to take effect.
+func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) }
+
+type suspendable struct {
+ w io.Writer
+ s atomic.Bool
+
+ buf bytes.Buffer
+ bufOnce sync.Once
+ bufMu sync.Mutex
+ dropped int
+}
+
+func (s *suspendable) Write(p []byte) (n int, err error) {
+ if !s.s.Load() {
+ return s.w.Write(p)
+ }
+ s.bufOnce.Do(func() { s.prepareBuf() })
+
+ s.bufMu.Lock()
+ defer s.bufMu.Unlock()
+
+ if l := len(p); s.buf.Len()+l > bufSizeMax {
+ s.dropped += l
+ return 0, syscall.ENOMEM
+ }
+ return s.buf.Write(p)
+}
+
+func (s *suspendable) prepareBuf() { s.buf.Grow(bufSize) }
+func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) }
+func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) {
+ if o.s.CompareAndSwap(true, false) {
+ o.bufMu.Lock()
+ defer o.bufMu.Unlock()
+
+ resumed = true
+ dropped = uintptr(o.dropped)
+
+ o.dropped = 0
+ n, err = io.Copy(s.w, &s.buf)
+ s.buf = bytes.Buffer{}
+ s.prepareBuf()
+ }
+ return
+}
+
+func Suspend() bool { return o.Suspend() }
+func Resume() bool {
+ resumed, dropped, _, err := o.Resume()
+ if err != nil {
+ // probably going to result in an error as well,
+ // so this call is as good as unreachable
+ log.Printf("cannot dump buffer on resume: %v", err)
+ }
+ if resumed && dropped > 0 {
+ log.Fatalf("dropped %d bytes while output is suspended", dropped)
+ }
+ return resumed
+}
+
+func BeforeExit() {
+ if Resume() {
+ log.Printf("beforeExit reached on suspended output")
+ }
+}
diff --git a/internal/hlog/msg.go b/internal/hlog/msg.go
new file mode 100644
index 00000000..dd37fdbf
--- /dev/null
+++ b/internal/hlog/msg.go
@@ -0,0 +1,12 @@
+package hlog
+
+type Output struct{}
+
+func (Output) IsVerbose() bool { return Load() }
+func (Output) Verbose(v ...any) { Verbose(v...) }
+func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) }
+func (Output) WrapErr(err error, a ...any) error { return WrapErr(err, a...) }
+func (Output) PrintBaseErr(err error, fallback string) { PrintBaseError(err, fallback) }
+func (Output) Suspend() { Suspend() }
+func (Output) Resume() bool { return Resume() }
+func (Output) BeforeExit() { BeforeExit() }
diff --git a/internal/hlog/verbose.go b/internal/hlog/verbose.go
new file mode 100644
index 00000000..d6fcd94d
--- /dev/null
+++ b/internal/hlog/verbose.go
@@ -0,0 +1,23 @@
+package hlog
+
+import (
+ "log"
+ "sync/atomic"
+)
+
+var verbose = new(atomic.Bool)
+
+func Load() bool { return verbose.Load() }
+func Store(v bool) { verbose.Store(v) }
+
+func Verbosef(format string, v ...any) {
+ if verbose.Load() {
+ log.Printf(format, v...)
+ }
+}
+
+func Verbose(v ...any) {
+ if verbose.Load() {
+ log.Println(v...)
+ }
+}