feat(log): add structured logging handler [LEVEL] [TIME] [FILE:LINE]
This commit is contained in:
125
internal/log/handler.go
Normal file
125
internal/log/handler.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
level slog.Level
|
||||
attrs []slog.Attr
|
||||
groups []string
|
||||
}
|
||||
|
||||
func New(w io.Writer, level slog.Level) *Handler {
|
||||
return &Handler{w: w, level: level}
|
||||
}
|
||||
|
||||
func (h *Handler) Enabled(_ context.Context, l slog.Level) bool {
|
||||
return l >= h.level
|
||||
}
|
||||
|
||||
func (h *Handler) Handle(_ context.Context, r slog.Record) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
level := r.Level.String()
|
||||
switch r.Level {
|
||||
case slog.LevelDebug:
|
||||
level = "DEBUG"
|
||||
case slog.LevelInfo:
|
||||
level = "INFO"
|
||||
case slog.LevelWarn:
|
||||
level = "WARN"
|
||||
case slog.LevelError:
|
||||
level = "ERROR"
|
||||
}
|
||||
|
||||
_, file, line, ok := runtime.Caller(4)
|
||||
if !ok {
|
||||
file = "???"
|
||||
}
|
||||
short := file
|
||||
for i := len(file) - 1; i > 0; i-- {
|
||||
if file[i] == '/' {
|
||||
short = file[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
t := r.Time.Format("2006-01-02 15:04:05.000")
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("[%s] [%s] [%s:%d]", level, t, short, line))
|
||||
|
||||
if len(h.groups) > 0 {
|
||||
b.WriteString(" [" + strings.Join(h.groups, ".") + "]")
|
||||
}
|
||||
if r.Message != "" {
|
||||
b.WriteString(" " + r.Message)
|
||||
}
|
||||
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
b.WriteString(fmt.Sprintf(" %s=%v", a.Key, a.Value.Any()))
|
||||
return true
|
||||
})
|
||||
for _, a := range h.attrs {
|
||||
b.WriteString(fmt.Sprintf(" %s=%v", a.Key, a.Value.Any()))
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
_, err := h.w.Write([]byte(b.String()))
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if len(attrs) == 0 {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.attrs = append(h2.attrs, attrs...)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *Handler) WithGroup(name string) slog.Handler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.groups = append(h2.groups, name)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *Handler) clone() *Handler {
|
||||
return &Handler{
|
||||
w: h.w,
|
||||
level: h.level,
|
||||
attrs: append([]slog.Attr{}, h.attrs...),
|
||||
groups: append([]string{}, h.groups...),
|
||||
}
|
||||
}
|
||||
|
||||
func Init(level string) {
|
||||
var l slog.Level
|
||||
switch strings.ToUpper(level) {
|
||||
case "DEBUG":
|
||||
l = slog.LevelDebug
|
||||
case "INFO":
|
||||
l = slog.LevelInfo
|
||||
case "WARN":
|
||||
l = slog.LevelWarn
|
||||
case "ERROR":
|
||||
l = slog.LevelError
|
||||
default:
|
||||
l = slog.LevelInfo
|
||||
}
|
||||
slog.SetDefault(slog.New(New(os.Stderr, l)))
|
||||
}
|
||||
Reference in New Issue
Block a user