157 lines
3.4 KiB
Go
157 lines
3.4 KiB
Go
package logx
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"time"
|
|
)
|
|
|
|
type multiHandler struct {
|
|
stdout slog.Handler
|
|
file slog.Handler
|
|
}
|
|
|
|
func newMultiHandler(stdout slog.Handler, storage *Storage, level *slog.LevelVar) *multiHandler {
|
|
return &multiHandler{
|
|
stdout: stdout,
|
|
file: &fileHandler{storage: storage, level: level},
|
|
}
|
|
}
|
|
|
|
func (h *multiHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|
return h.stdout.Enabled(context.Background(), level)
|
|
}
|
|
|
|
func (h *multiHandler) Handle(ctx context.Context, r slog.Record) error {
|
|
if h.stdout.Enabled(ctx, r.Level) {
|
|
_ = h.stdout.Handle(ctx, r)
|
|
}
|
|
return h.file.Handle(ctx, r)
|
|
}
|
|
|
|
func (h *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
return &multiHandler{
|
|
stdout: h.stdout.WithAttrs(attrs),
|
|
file: h.file.WithAttrs(attrs),
|
|
}
|
|
}
|
|
|
|
func (h *multiHandler) WithGroup(name string) slog.Handler {
|
|
return &multiHandler{
|
|
stdout: h.stdout.WithGroup(name),
|
|
file: h.file.WithGroup(name),
|
|
}
|
|
}
|
|
|
|
type fileHandler struct {
|
|
storage *Storage
|
|
level *slog.LevelVar
|
|
attrs []slog.Attr
|
|
groups []string
|
|
}
|
|
|
|
func (h *fileHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|
return level >= h.level.Level()
|
|
}
|
|
|
|
func (h *fileHandler) Handle(_ context.Context, r slog.Record) error {
|
|
if !h.Enabled(context.Background(), r.Level) {
|
|
return nil
|
|
}
|
|
e := recordToEntry(r, h.attrs, h.groups)
|
|
h.storage.Enqueue(e)
|
|
return nil
|
|
}
|
|
|
|
func (h *fileHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
dup := *h
|
|
dup.attrs = append(append([]slog.Attr{}, h.attrs...), attrs...)
|
|
return &dup
|
|
}
|
|
|
|
func (h *fileHandler) WithGroup(name string) slog.Handler {
|
|
dup := *h
|
|
dup.groups = append(append([]string{}, h.groups...), name)
|
|
return &dup
|
|
}
|
|
|
|
func recordToEntry(r slog.Record, baseAttrs []slog.Attr, groups []string) Entry {
|
|
attrs := append([]slog.Attr{}, baseAttrs...)
|
|
r.Attrs(func(a slog.Attr) bool {
|
|
attrs = append(attrs, a)
|
|
return true
|
|
})
|
|
|
|
e := Entry{
|
|
Timestamp: r.Time.Format(time.RFC3339),
|
|
Level: LevelToInt(r.Level),
|
|
Module: string(ModuleSystem),
|
|
Message: r.Message,
|
|
Details: map[string]any{},
|
|
}
|
|
|
|
for _, a := range attrs {
|
|
key := a.Key
|
|
if len(groups) > 0 {
|
|
key = groups[0] + "." + key
|
|
}
|
|
switch key {
|
|
case "module":
|
|
e.Module = attrString(a)
|
|
case "account_id":
|
|
e.AccountID = attrAny(a)
|
|
case "driver_name", "driver":
|
|
e.DriverName = attrAny(a)
|
|
case "account", "account_name":
|
|
if e.Details == nil {
|
|
e.Details = map[string]any{}
|
|
}
|
|
e.Details[key] = attrAny(a)
|
|
default:
|
|
e.Details[key] = attrAny(a)
|
|
}
|
|
}
|
|
if len(e.Details) == 0 {
|
|
e.Details = nil
|
|
}
|
|
return e
|
|
}
|
|
|
|
func attrString(a slog.Attr) string {
|
|
return a.Value.String()
|
|
}
|
|
|
|
func attrAny(a slog.Attr) any {
|
|
switch a.Value.Kind() {
|
|
case slog.KindString:
|
|
return a.Value.String()
|
|
case slog.KindInt64:
|
|
return a.Value.Int64()
|
|
case slog.KindUint64:
|
|
return a.Value.Uint64()
|
|
case slog.KindFloat64:
|
|
return a.Value.Float64()
|
|
case slog.KindBool:
|
|
return a.Value.Bool()
|
|
case slog.KindDuration:
|
|
return a.Value.Duration().String()
|
|
case slog.KindTime:
|
|
return a.Value.Time().Format(time.RFC3339)
|
|
default:
|
|
return a.Value.Any()
|
|
}
|
|
}
|
|
|
|
func newStdoutHandler(w io.Writer, lv *slog.LevelVar) slog.Handler {
|
|
return slog.NewTextHandler(w, &slog.HandlerOptions{
|
|
Level: lv,
|
|
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
|
|
if a.Key == slog.TimeKey {
|
|
return slog.String("time", a.Value.Time().Format("15:04:05"))
|
|
}
|
|
return a
|
|
},
|
|
})
|
|
}
|