diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-06-25 03:59:52 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-06-25 04:57:41 +0900 |
| commit | 87e008d56de974947ebb99c2cc40b25d3c2cf43e (patch) | |
| tree | 31791911e5226d6ec04e3fac7d91b0bf53e63aa5 /internal/hlog | |
| parent | 399207321265307bb15f37d867f9370cd51c82a8 (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.go | 81 | ||||
| -rw-r--r-- | internal/hlog/hlog.go | 86 | ||||
| -rw-r--r-- | internal/hlog/msg.go | 12 | ||||
| -rw-r--r-- | internal/hlog/verbose.go | 23 |
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...) + } +} |
