mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-30 02:40:33 +00:00
146 lines
3.1 KiB
Go
146 lines
3.1 KiB
Go
package log
|
|
|
|
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 (
|
|
History = 50
|
|
Filename = "strimertul.log"
|
|
PanicFilename = "strimertul-panic.log"
|
|
)
|
|
|
|
var (
|
|
LastLogs = sync.NewSlice[Entry]()
|
|
IncomingLogs = make(chan Entry, 100)
|
|
)
|
|
|
|
func Init(level slog.Level) {
|
|
logStorage := NewLogStorage(level)
|
|
fileLogger := &lumberjack.Logger{
|
|
Filename: Filename,
|
|
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 Entry struct {
|
|
ID string `json:"id"`
|
|
Time string `json:"time"`
|
|
Level string `json:"level"`
|
|
Message string `json:"message"`
|
|
Data string `json:"data"`
|
|
}
|
|
|
|
type Storage struct {
|
|
minLevel slog.Level
|
|
attrs []slog.Attr
|
|
group string
|
|
}
|
|
|
|
func (core *Storage) Enabled(_ context.Context, level slog.Level) bool {
|
|
return level >= core.minLevel
|
|
}
|
|
|
|
func (core *Storage) Handle(_ context.Context, record slog.Record) error {
|
|
attributes := flatAttributeMap(record)
|
|
attrJSON, _ := json.Marshal(attributes)
|
|
|
|
// Generate unique log ID
|
|
id := fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Int31())
|
|
|
|
logEntry := Entry{
|
|
ID: id,
|
|
Time: record.Time.Format(time.RFC3339),
|
|
Level: record.Level.String(),
|
|
Message: record.Message,
|
|
Data: string(attrJSON),
|
|
}
|
|
LastLogs.Push(logEntry)
|
|
if LastLogs.Size() > History {
|
|
LastLogs.Splice(0, 1)
|
|
}
|
|
IncomingLogs <- logEntry
|
|
return nil
|
|
}
|
|
|
|
func flatAttributeMap(record slog.Record) map[string]any {
|
|
attributes := map[string]any{}
|
|
flatAttributes := func(attr slog.Attr) bool {
|
|
type attrToParse struct {
|
|
Prefix string
|
|
Attr slog.Attr
|
|
}
|
|
remaining := []attrToParse{{"", attr}}
|
|
for len(remaining) > 0 {
|
|
var current attrToParse
|
|
current, remaining = remaining[0], remaining[1:]
|
|
|
|
switch current.Attr.Value.Kind() {
|
|
case slog.KindGroup:
|
|
for _, subAttr := range current.Attr.Value.Group() {
|
|
remaining = append(remaining, attrToParse{
|
|
Prefix: current.Attr.Key + ".",
|
|
Attr: subAttr,
|
|
})
|
|
}
|
|
default:
|
|
attributes[current.Prefix+current.Attr.Key] = current.Attr.Value.Any()
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
record.Attrs(flatAttributes)
|
|
return attributes
|
|
}
|
|
|
|
func (core *Storage) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
return &Storage{
|
|
minLevel: core.minLevel,
|
|
attrs: append(core.attrs, attrs...),
|
|
group: core.group,
|
|
}
|
|
}
|
|
|
|
func (core *Storage) WithGroup(name string) slog.Handler {
|
|
return &Storage{
|
|
minLevel: core.minLevel,
|
|
attrs: core.attrs,
|
|
group: name,
|
|
}
|
|
}
|
|
|
|
func NewLogStorage(level slog.Level) *Storage {
|
|
return &Storage{
|
|
minLevel: level,
|
|
}
|
|
}
|
|
|
|
func ParseLogFields(data map[string]any) []any {
|
|
fields := []any{}
|
|
for k, v := range data {
|
|
fields = append(fields, k, v)
|
|
}
|
|
return fields
|
|
}
|