strimertul/logging.go

119 lines
2.5 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"math/rand"
"os"
"time"
"git.sr.ht/~ashkeel/containers/sync"
slogmulti "github.com/samber/slog-multi"
"gopkg.in/natefinch/lumberjack.v2"
)
const LogHistory = 50
var (
lastLogs *sync.Slice[LogEntry]
incomingLogs chan LogEntry
)
func initLogger(level slog.Level) {
lastLogs = sync.NewSlice[LogEntry]()
incomingLogs = make(chan LogEntry, 100)
logStorage := NewLogStorage(level)
fileLogger := &lumberjack.Logger{
Filename: logFilename,
MaxSize: 20,
MaxBackups: 3,
MaxAge: 28,
}
consoleHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: level,
})
fileHandler := slog.NewJSONHandler(fileLogger, &slog.HandlerOptions{
AddSource: true,
Level: level,
})
logger := slog.New(slogmulti.Fanout(consoleHandler, fileHandler, logStorage))
slog.SetDefault(logger)
}
type LogEntry struct {
ID string `json:"id"`
Time string `json:"time"`
Level string `json:"level"`
Message string `json:"message"`
Data string `json:"data"`
}
type LogStorage struct {
minLevel slog.Level
attrs []slog.Attr
group string
}
func (core *LogStorage) Enabled(_ context.Context, level slog.Level) bool {
return level >= core.minLevel
}
func (core *LogStorage) Handle(_ context.Context, record slog.Record) error {
attributes := map[string]any{}
record.Attrs(func(attrs slog.Attr) bool {
attributes[attrs.Key] = attrs.Value.Any()
return true
})
attrJSON, _ := json.Marshal(attributes)
// Generate unique log ID
id := fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Int31())
logEntry := LogEntry{
ID: id,
Time: record.Time.Format(time.RFC3339),
Level: record.Level.String(),
Message: record.Message,
Data: string(attrJSON),
}
lastLogs.Push(logEntry)
if lastLogs.Size() > LogHistory {
lastLogs.Splice(0, 1)
}
incomingLogs <- logEntry
return nil
}
func (core *LogStorage) WithAttrs(attrs []slog.Attr) slog.Handler {
return &LogStorage{
minLevel: core.minLevel,
attrs: append(core.attrs, attrs...),
group: core.group,
}
}
func (core *LogStorage) WithGroup(name string) slog.Handler {
return &LogStorage{
minLevel: core.minLevel,
attrs: core.attrs,
group: name,
}
}
func NewLogStorage(level slog.Level) *LogStorage {
return &LogStorage{
minLevel: level,
}
}
func parseLogFields(data map[string]any) []any {
fields := []any{}
for k, v := range data {
fields = append(fields, k, v)
}
return fields
}