vodular/logger/slog.go

189 lines
4.2 KiB
Go

package logger
import (
"context"
"io"
"log/slog"
"os"
"strings"
"sync"
"time"
"arimelody.space/melody-vod-manager/colour"
"github.com/mattn/go-isatty"
)
type (
Options struct {
Handlers []slog.Handler
Level slog.Leveler
IsTerminal bool
}
SlogHandler struct {
attrs []slog.Attr
group string
mutex *sync.Mutex
options Options
writer io.Writer
}
)
const groupPadding = 11
const levelPadding = 5
func NewSlogHandler(writer io.Writer, options Options) *SlogHandler {
writerFile, writerIsFile := writer.(*os.File)
if writerIsFile {
options.IsTerminal = isatty.IsTerminal(writerFile.Fd())
}
return &SlogHandler{
options: options,
attrs: []slog.Attr{},
group: "OTHER",
mutex: &sync.Mutex{},
writer: writer,
}
}
func (log *SlogHandler) appendRecord(buf *strings.Builder, record slog.Record) {
log.appendTime(buf, record.Time)
log.appendGroup(buf, log.group)
log.appendLevel(buf, record.Level)
log.appendMessage(buf, record.Message)
for _, attr := range log.attrs {
log.appendAttr(buf, attr)
}
record.Attrs(func(attr slog.Attr) bool {
log.appendAttr(buf, attr)
return true
})
}
func (log *SlogHandler) appendTime(buf *strings.Builder, t time.Time) {
if !t.IsZero() {
if log.options.IsTerminal {
buf.WriteString(colour.Dim + colour.Gray)
}
buf.WriteRune('[')
buf.WriteString(t.Format(time.UnixDate))
buf.WriteRune(']')
if log.options.IsTerminal {
buf.WriteString(colour.Reset)
}
buf.WriteRune(' ')
}
}
func (log *SlogHandler) appendGroup(buf *strings.Builder, group string) {
if log.options.IsTerminal {
buf.WriteString(colour.Dim + colour.Gray)
}
log.appendPadLeft(buf, groupPadding, group)
if log.options.IsTerminal {
buf.WriteString(colour.Reset)
}
buf.WriteRune(' ')
}
func (log *SlogHandler) appendPadLeft(buf *strings.Builder, padding int, content string) {
padding -= len(content)
for i := padding; i > 0; i-- {
buf.WriteRune(' ')
}
buf.WriteString(content)
}
func (log *SlogHandler) appendPadRight(buf *strings.Builder, padding int, content string) {
buf.WriteString(content)
padding -= len(content)
for i := padding; i > 0; i-- {
buf.WriteRune(' ')
}
}
func (log *SlogHandler) appendLevel(buf *strings.Builder, level slog.Level) {
if log.options.IsTerminal {
levelColour := colour.Reset
switch level {
case slog.LevelDebug:
levelColour = colour.Purple
case slog.LevelError:
levelColour = colour.Red
case slog.LevelWarn:
levelColour = colour.Yellow
case slog.LevelInfo:
levelColour = colour.Cyan
}
buf.WriteString(levelColour)
}
log.appendPadRight(buf, levelPadding, level.String())
if log.options.IsTerminal {
buf.WriteString(colour.Reset)
}
buf.WriteRune(' ')
}
func (log *SlogHandler) appendMessage(buf *strings.Builder, message string) {
if message != "" {
buf.WriteString(message)
buf.WriteRune(' ')
}
}
func (log *SlogHandler) appendAttr(buf *strings.Builder, attr slog.Attr) {
buf.WriteString(attr.String())
buf.WriteRune(' ')
}
func (log *SlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
return level >= log.options.Level.Level()
}
func (log *SlogHandler) Handle(ctx context.Context, record slog.Record) error {
buf := &strings.Builder{}
log.appendRecord(buf, record)
buf.WriteRune('\n')
log.mutex.Lock()
defer log.mutex.Unlock()
if _, err := log.writer.Write([]byte(buf.String())); err != nil {
return err
}
for _, handler := range log.options.Handlers {
if err := handler.Handle(ctx, record); err != nil {
return err
}
}
return nil
}
func (log *SlogHandler) WithGroup(group string) slog.Handler {
log2 := *log
log2.group = group
log2.options.Handlers = make([]slog.Handler, len(log.options.Handlers))
for i, handler := range log.options.Handlers {
log.options.Handlers[i] = handler.WithGroup(group)
}
return &log2
}
func (log *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
log2 := *log
log2.attrs = make([]slog.Attr, 0, len(log.attrs)+len(attrs))
log2.attrs = append(log2.attrs, log.attrs...)
log2.attrs = append(log2.attrs, attrs...)
log2.options.Handlers = make([]slog.Handler, len(log.options.Handlers))
for i, handler := range log.options.Handlers {
log.options.Handlers[i] = handler.WithAttrs(attrs)
}
return &log2
}