Implement Task Logger
This commit is contained in:
parent
bbe103c446
commit
bce27eddf3
1 changed files with 152 additions and 0 deletions
152
task/log/log.go
Normal file
152
task/log/log.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.lastassault.de/speatzle/morffix/types"
|
||||
slogmulti "github.com/samber/slog-multi"
|
||||
)
|
||||
|
||||
func GetTaskLogger(t *types.Task) *slog.Logger {
|
||||
return slog.New(
|
||||
slogmulti.Fanout(
|
||||
slog.Default().Handler(),
|
||||
New(t, &Options{slog.LevelDebug}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// groupOrAttrs holds either a group name or a list of slog.Attrs.
|
||||
type groupOrAttrs struct {
|
||||
group string // group name if non-empty
|
||||
attrs []slog.Attr // attrs if non-empty
|
||||
}
|
||||
|
||||
type TaskHandler struct {
|
||||
opts Options
|
||||
// TODO: state for WithGroup and WithAttrs
|
||||
mu *sync.Mutex
|
||||
t *types.Task
|
||||
goas []groupOrAttrs
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// Level reports the minimum level to log.
|
||||
// Levels with lower levels are discarded.
|
||||
// If nil, the Handler uses [slog.LevelInfo].
|
||||
Level slog.Leveler
|
||||
}
|
||||
|
||||
func New(t *types.Task, opts *Options) *TaskHandler {
|
||||
h := &TaskHandler{t: t, mu: &sync.Mutex{}}
|
||||
if opts != nil {
|
||||
h.opts = *opts
|
||||
}
|
||||
if h.opts.Level == nil {
|
||||
h.opts.Level = slog.LevelInfo
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
return level >= h.opts.Level.Level()
|
||||
}
|
||||
|
||||
func (h *TaskHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
buf := make([]byte, 0, 1024)
|
||||
if !r.Time.IsZero() {
|
||||
buf = h.appendAttr(buf, slog.Time(slog.TimeKey, r.Time))
|
||||
}
|
||||
buf = h.appendAttr(buf, slog.Any(slog.LevelKey, r.Level))
|
||||
if r.PC != 0 {
|
||||
fs := runtime.CallersFrames([]uintptr{r.PC})
|
||||
f, _ := fs.Next()
|
||||
buf = h.appendAttr(buf, slog.String(slog.SourceKey, fmt.Sprintf("%s:%d", f.File, f.Line)))
|
||||
}
|
||||
buf = h.appendAttr(buf, slog.String(slog.MessageKey, r.Message))
|
||||
// Handle state from WithGroup and WithAttrs.
|
||||
goas := h.goas
|
||||
if r.NumAttrs() == 0 {
|
||||
// If the record has no Attrs, remove groups at the end of the list; they are empty.
|
||||
for len(goas) > 0 && goas[len(goas)-1].group != "" {
|
||||
goas = goas[:len(goas)-1]
|
||||
}
|
||||
}
|
||||
for _, goa := range goas {
|
||||
if goa.group == "" {
|
||||
for _, a := range goa.attrs {
|
||||
buf = h.appendAttr(buf, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
buf = h.appendAttr(buf, a)
|
||||
return true
|
||||
})
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.t.Log = append(h.t.Log, string(buf))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TaskHandler) appendAttr(buf []byte, a slog.Attr) []byte {
|
||||
// Resolve the Attr's value before doing anything else.
|
||||
a.Value = a.Value.Resolve()
|
||||
// Ignore empty Attrs.
|
||||
if a.Equal(slog.Attr{}) {
|
||||
return buf
|
||||
}
|
||||
|
||||
switch a.Value.Kind() {
|
||||
case slog.KindString:
|
||||
// Quote string values, to make them easy to parse.
|
||||
buf = fmt.Appendf(buf, "%s: %q\n", a.Key, a.Value.String())
|
||||
case slog.KindTime:
|
||||
// Write times in a standard way, without the monotonic time.
|
||||
buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value.Time().Format(time.RFC3339Nano))
|
||||
case slog.KindGroup:
|
||||
attrs := a.Value.Group()
|
||||
// Ignore empty groups.
|
||||
if len(attrs) == 0 {
|
||||
return buf
|
||||
}
|
||||
// If the key is non-empty, write it out and indent the rest of the attrs.
|
||||
// Otherwise, inline the attrs.
|
||||
if a.Key != "" {
|
||||
buf = fmt.Appendf(buf, "%s:\n", a.Key)
|
||||
}
|
||||
for _, ga := range attrs {
|
||||
buf = h.appendAttr(buf, ga)
|
||||
}
|
||||
default:
|
||||
buf = fmt.Appendf(buf, "%s: %s\n", a.Key, a.Value)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func (h *TaskHandler) withGroupOrAttrs(goa groupOrAttrs) *TaskHandler {
|
||||
h2 := *h
|
||||
h2.goas = make([]groupOrAttrs, len(h.goas)+1)
|
||||
copy(h2.goas, h.goas)
|
||||
h2.goas[len(h2.goas)-1] = goa
|
||||
return &h2
|
||||
}
|
||||
|
||||
func (h *TaskHandler) WithGroup(name string) slog.Handler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
return h.withGroupOrAttrs(groupOrAttrs{group: name})
|
||||
}
|
||||
|
||||
func (h *TaskHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if len(attrs) == 0 {
|
||||
return h
|
||||
}
|
||||
return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs})
|
||||
}
|
Loading…
Add table
Reference in a new issue