package main import ( "context" "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.MarshalToString(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: 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 }