diff --git a/app.go b/app.go index 32553c9..2d57ea8 100644 --- a/app.go +++ b/app.go @@ -14,6 +14,8 @@ import ( "runtime/debug" "strconv" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/containers/sync" kv "git.sr.ht/~ashkeel/kilovolt/v12" "github.com/nicklaw5/helix/v2" @@ -39,6 +41,7 @@ type App struct { isFatalError *sync.RWSync[bool] backupOptions database.BackupOptions cancelLogs database.CancelFunc + logger *slog.Logger db *database.LocalDBClient twitchManager *client.Manager @@ -52,6 +55,7 @@ func NewApp(cliParams *cli.Context) *App { cliParams: cliParams, ready: sync.NewRWSync(false), isFatalError: sync.NewRWSync(false), + logger: slog.Default(), } } @@ -145,19 +149,31 @@ func (a *App) initializeComponents() error { var err error // Create logger and endpoints - a.httpServer, err = webserver.NewServer(a.db, webserver.DefaultServerFactory) + a.httpServer, err = webserver.NewServer( + log.WithLogger(a.ctx, a.logger.With(slog.String("strimertul.module", "webserver"))), + a.db, + webserver.DefaultServerFactory, + ) if err != nil { return fmt.Errorf("could not initialize http server: %w", err) } // Create twitch client - a.twitchManager, err = client.NewManager(a.ctx, a.db, a.httpServer) + a.twitchManager, err = client.NewManager( + log.WithLogger(a.ctx, a.logger.With(slog.String("strimertul.module", "twitch"))), + a.db, + a.httpServer, + ) if err != nil { return fmt.Errorf("could not initialize twitch client: %w", err) } // Initialize loyalty system - a.loyaltyManager, err = loyalty.NewManager(a.db, a.twitchManager) + a.loyaltyManager, err = loyalty.NewManager( + log.WithLogger(a.ctx, a.logger.With(slog.String("strimertul.module", "logger"))), + a.db, + a.twitchManager, + ) if err != nil { return fmt.Errorf("could not initialize loyalty manager: %w", err) } @@ -177,12 +193,12 @@ func (a *App) listenForLogs() (database.CancelFunc, error) { level = slog.LevelInfo } - slog.Log(a.ctx, level, entry.Message, parseLogFields(entry.Data)...) + slog.Log(a.ctx, level, entry.Message, log.ParseLogFields(entry.Data)...) }) } func (a *App) forwardLogs() { - for entry := range incomingLogs { + for entry := range log.IncomingLogs { runtime.EventsEmit(a.ctx, "log-event", entry) } } @@ -233,8 +249,8 @@ func (a *App) GetTwitchLoggedUser() (helix.User, error) { return a.twitchManager.Client().GetLoggedUser() } -func (a *App) GetLastLogs() []LogEntry { - return lastLogs.Get() +func (a *App) GetLastLogs() []log.Entry { + return log.LastLogs.Get() } func (a *App) GetDocumentation() map[string]docs.KeyObject { @@ -247,26 +263,26 @@ func (a *App) SendCrashReport(errorData string, info string) (string, error) { // Add text fields if err := w.WriteField("error", errorData); err != nil { - slog.Error("Could not encode field error for crash report", "error", err) + slog.Error("Could not encode field error for crash report", log.Error(err)) } if len(info) > 0 { if err := w.WriteField("info", info); err != nil { - slog.Error("Could not encode field info for crash report", "error", err) + slog.Error("Could not encode field info for crash report", log.Error(err)) } } // Add log files - addFile(w, "log", logFilename) - addFile(w, "paniclog", panicFilename) + addFile(w, "log", log.Filename) + addFile(w, "paniclog", log.PanicFilename) if err := w.Close(); err != nil { - slog.Error("Could not prepare request for crash report", "error", err) + slog.Error("Could not prepare request for crash report", log.Error(err)) return "", err } resp, err := http.Post(crashReportURL, w.FormDataContentType(), &b) if err != nil { - slog.Error("Could not send crash report", "error", err) + slog.Error("Could not send crash report", log.Error(err)) return "", err } defer resp.Body.Close() @@ -325,7 +341,7 @@ func (a *App) interactiveAuth(client kv.Client, message map[string]any) bool { func (a *App) showFatalError(err error, text string, fields ...any) { if err != nil { - fields = append(fields, "error", err, slog.String("Z", string(debug.Stack()))) + fields = append(fields, log.Error(err), slog.String("Z", string(debug.Stack()))) slog.Error(text, fields...) runtime.EventsEmit(a.ctx, "fatalError") a.isFatalError.Set(true) @@ -343,17 +359,17 @@ func (a *App) onSecondInstanceLaunch(_ options.SecondInstanceData) { func addFile(m *multipart.Writer, field string, filename string) { logfile, err := m.CreateFormFile(field, filename) if err != nil { - slog.Error("Could not encode field log for crash report", "error", err) + slog.Error("Could not encode field log for crash report", log.Error(err)) return } file, err := os.Open(filename) if err != nil { - slog.Error("Could not open file for including in crash report", slog.String("file", filename), "error", err) + slog.Error("Could not open file for including in crash report", slog.String("file", filename), log.Error(err)) return } if _, err = io.Copy(logfile, file); err != nil { - slog.Error("Could not read from file for including in crash report", slog.String("file", filename), "error", err) + slog.Error("Could not read from file for including in crash report", slog.String("file", filename), log.Error(err)) } } diff --git a/backup.go b/backup.go index 44ff05d..ba0b464 100644 --- a/backup.go +++ b/backup.go @@ -8,6 +8,8 @@ import ( "sort" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/strimertul/database" "git.sr.ht/~ashkeel/strimertul/utils" ) @@ -20,7 +22,7 @@ func BackupTask(driver database.Driver, options database.BackupOptions) { err := os.MkdirAll(options.BackupDir, 0o755) if err != nil { - slog.Error("Could not create backup directory, moving to a temporary folder", "error", err) + slog.Error("Could not create backup directory, moving to a temporary folder", log.Error(err)) options.BackupDir = os.TempDir() slog.Info("Using temporary directory", slog.String("backup-dir", options.BackupDir)) return @@ -37,13 +39,13 @@ func performBackup(driver database.Driver, options database.BackupOptions) { // Run backup procedure file, err := os.Create(fmt.Sprintf("%s/%s.db", options.BackupDir, time.Now().Format("20060102-150405"))) if err != nil { - slog.Error("Could not create backup file", "error", err) + slog.Error("Could not create backup file", log.Error(err)) return } err = driver.Backup(file) if err != nil { - slog.Error("Could not backup database", "error", err) + slog.Error("Could not backup database", log.Error(err)) } _ = file.Close() slog.Info("Database backup created", slog.String("backup-file", file.Name())) @@ -51,7 +53,7 @@ func performBackup(driver database.Driver, options database.BackupOptions) { // Remove old backups files, err := os.ReadDir(options.BackupDir) if err != nil { - slog.Error("Could not read backup directory", "error", err) + slog.Error("Could not read backup directory", log.Error(err)) return } @@ -64,7 +66,7 @@ func performBackup(driver database.Driver, options database.BackupOptions) { for _, file := range toRemove { err = os.Remove(fmt.Sprintf("%s/%s", options.BackupDir, file.Name())) if err != nil { - slog.Error("Could not remove backup file", "error", err) + slog.Error("Could not remove backup file", log.Error(err)) } } } @@ -79,7 +81,7 @@ type BackupInfo struct { func (a *App) GetBackups() (list []BackupInfo) { files, err := os.ReadDir(a.backupOptions.BackupDir) if err != nil { - slog.Error("Could not read backup directory", "error", err) + slog.Error("Could not read backup directory", log.Error(err)) return nil } @@ -90,7 +92,7 @@ func (a *App) GetBackups() (list []BackupInfo) { info, err := file.Info() if err != nil { - slog.Error("Could not get info for backup file", "error", err) + slog.Error("Could not get info for backup file", log.Error(err)) continue } diff --git a/frontend/src/ui/components/LogViewer.tsx b/frontend/src/ui/components/LogViewer.tsx index 3d512c6..b49f595 100644 --- a/frontend/src/ui/components/LogViewer.tsx +++ b/frontend/src/ui/components/LogViewer.tsx @@ -249,7 +249,7 @@ const LogDetails = styled('div', { gridColumn: '2/4', display: 'flex', flexWrap: 'wrap', - gap: '1rem', + gap: '0.5rem 1rem', fontSize: '0.8em', color: '$gray11', backgroundColor: '$gray3', diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 076a83a..181c086 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -2,6 +2,7 @@ // This file is automatically generated. DO NOT EDIT import {main} from '../models'; import {docs} from '../models'; +import {log} from '../models'; import {helix} from '../models'; export function AuthenticateKVClient(arg1:string):Promise; @@ -14,7 +15,7 @@ export function GetDocumentation():Promise<{[key: string]: docs.KeyObject}>; export function GetKilovoltBind():Promise; -export function GetLastLogs():Promise>; +export function GetLastLogs():Promise>; export function GetProblems():Promise>; diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 45cfb6d..c2e022d 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -54,6 +54,31 @@ export namespace helix { } +export namespace log { + + export class Entry { + id: string; + time: string; + level: string; + message: string; + data: string; + + static createFrom(source: any = {}) { + return new Entry(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.id = source["id"]; + this.time = source["time"]; + this.level = source["level"]; + this.message = source["message"]; + this.data = source["data"]; + } + } + +} + export namespace main { export class BackupInfo { @@ -72,26 +97,6 @@ export namespace main { this.size = source["size"]; } } - export class LogEntry { - id: string; - time: string; - level: string; - message: string; - data: string; - - static createFrom(source: any = {}) { - return new LogEntry(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.time = source["time"]; - this.level = source["level"]; - this.message = source["message"]; - this.data = source["data"]; - } - } export class Problem { id: string; details: any; diff --git a/log/attr.go b/log/attr.go new file mode 100644 index 0000000..2107e9e --- /dev/null +++ b/log/attr.go @@ -0,0 +1,17 @@ +package log + +import ( + "fmt" + "log/slog" + "runtime" +) + +func Error(err error) slog.Attr { + pc, filename, line, _ := runtime.Caller(1) + + return slog.Group("error", + slog.String("message", err.Error()), + slog.String("file", fmt.Sprintf("%s@%d", filename, line)), + slog.String("func", runtime.FuncForPC(pc).Name()), + ) +} diff --git a/log/context.go b/log/context.go new file mode 100644 index 0000000..57e1673 --- /dev/null +++ b/log/context.go @@ -0,0 +1,24 @@ +package log + +import ( + "context" + "log/slog" +) + +type ContextKey string + +const ( + ContextLogger ContextKey = "logger" +) + +func WithLogger(ctx context.Context, logger *slog.Logger) context.Context { + return context.WithValue(ctx, ContextLogger, logger) +} + +func GetLogger(ctx context.Context) *slog.Logger { + logger, ok := ctx.Value(ContextLogger).(*slog.Logger) + if !ok { + return slog.Default() + } + return logger +} diff --git a/log/setup.go b/log/setup.go new file mode 100644 index 0000000..45ea49f --- /dev/null +++ b/log/setup.go @@ -0,0 +1,146 @@ +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 +} diff --git a/logging.go b/logging.go deleted file mode 100644 index e7e56aa..0000000 --- a/logging.go +++ /dev/null @@ -1,118 +0,0 @@ -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 -} diff --git a/loyalty/chat.go b/loyalty/chat.go index 15427f4..5b7c063 100644 --- a/loyalty/chat.go +++ b/loyalty/chat.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "github.com/nicklaw5/helix/v2" "git.sr.ht/~ashkeel/containers/sync" @@ -27,6 +29,7 @@ type twitchIntegration struct { ctx context.Context manager *Manager module *chat.Module + logger *slog.Logger activeUsers *sync.Map[string, bool] } @@ -36,6 +39,7 @@ func setupTwitchIntegration(ctx context.Context, m *Manager, mod *chat.Module) * ctx: ctx, manager: m, module: mod, + logger: log.GetLogger(ctx), activeUsers: sync.NewMap[string, bool](), } @@ -89,7 +93,7 @@ func setupTwitchIntegration(ctx context.Context, m *Manager, mod *chat.Module) * var streamInfos []helix.Stream err := m.db.GetJSON(twitch.StreamInfoKey, &streamInfos) if err != nil { - slog.Error("Error retrieving stream info", "error", err) + li.logger.Error("Error retrieving stream info", log.Error(err)) continue } if len(streamInfos) < 1 { @@ -123,13 +127,13 @@ func setupTwitchIntegration(ctx context.Context, m *Manager, mod *chat.Module) * if len(users) > 0 { err := li.manager.GivePoints(pointsToGive) if err != nil { - slog.Error("Error awarding loyalty points to user", "error", err) + li.logger.Error("Error awarding loyalty points to user", log.Error(err)) } } } }() - slog.Info("Loyalty system integration with Twitch is ready") + li.logger.Info("Loyalty system integration with Twitch is ready") return li } @@ -217,7 +221,7 @@ func (li *twitchIntegration) cmdRedeemReward(message helix.EventSubChannelChatMe }) return } - slog.Error("Error while performing redeem", "error", err) + li.logger.Error("Error while performing redeem", log.Error(err)) return } @@ -339,7 +343,7 @@ func (li *twitchIntegration) cmdContributeGoal(message helix.EventSubChannelChat // Add points to goal points, err := li.manager.PerformContribution(selectedGoal, message.ChatterUserLogin, points) if err != nil { - slog.Error("Error while contributing to goal", "error", err) + li.logger.Error("Error while contributing to goal", log.Error(err)) return } if points == 0 { diff --git a/loyalty/manager.go b/loyalty/manager.go index 63c1eae..8eac79c 100644 --- a/loyalty/manager.go +++ b/loyalty/manager.go @@ -9,6 +9,8 @@ import ( "strings" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/containers/sync" "git.sr.ht/~ashkeel/strimertul/database" twitchclient "git.sr.ht/~ashkeel/strimertul/twitch/client" @@ -29,6 +31,7 @@ type Manager struct { Goals *sync.Slice[Goal] Queue *sync.Slice[Redeem] db database.Database + logger *slog.Logger cooldowns map[string]time.Time banlist map[string]bool ctx context.Context @@ -39,19 +42,19 @@ type Manager struct { twitchIntegration *twitchIntegration } -func NewManager(db database.Database, twitchManager *twitchclient.Manager) (*Manager, error) { - ctx, cancelFn := context.WithCancel(context.Background()) +func NewManager(ctx context.Context, db database.Database, twitchManager *twitchclient.Manager) (*Manager, error) { + loyaltyContext, cancelFn := context.WithCancel(ctx) loyalty := &Manager{ - Config: sync.NewRWSync(Config{Enabled: false}), - Rewards: sync.NewSlice[Reward](), - Goals: sync.NewSlice[Goal](), - Queue: sync.NewSlice[Redeem](), - + Config: sync.NewRWSync(Config{Enabled: false}), + Rewards: sync.NewSlice[Reward](), + Goals: sync.NewSlice[Goal](), + Queue: sync.NewSlice[Redeem](), + logger: log.GetLogger(ctx), db: db, points: sync.NewMap[string, PointsEntry](), cooldowns: make(map[string]time.Time), banlist: make(map[string]bool), - ctx: ctx, + ctx: loyaltyContext, cancelFn: cancelFn, restartTwitchHandler: make(chan struct{}), twitchManager: twitchManager, @@ -116,14 +119,14 @@ func NewManager(db database.Database, twitchManager *twitchclient.Manager) (*Man // SubscribePrefix for changes loyalty.cancelSub, err = db.SubscribePrefix(loyalty.update, "loyalty/") if err != nil { - slog.Error("Could not setup loyalty reload subscription", "error", err) + loyalty.logger.Error("Could not setup loyalty reload subscription", log.Error(err)) } loyalty.SetBanList(config.BanList) // Start twitch handler if twitchManager.Client() != nil { - loyalty.twitchIntegration = setupTwitchIntegration(ctx, loyalty, twitchManager.Client().Chat) + loyalty.twitchIntegration = setupTwitchIntegration(loyaltyContext, loyalty, twitchManager.Client().Chat) } return loyalty, nil @@ -186,7 +189,7 @@ func (m *Manager) update(key, value string) { } } if err != nil { - slog.Error("Subscribe error: invalid JSON received on key", slog.String("key", key), "error", err) + slog.Error("Subscribe error: invalid JSON received on key", slog.String("key", key), log.Error(err)) } else { slog.Debug("Updated key", slog.String("key", key)) } diff --git a/main.go b/main.go index 277f60f..ae2a9fb 100644 --- a/main.go +++ b/main.go @@ -4,12 +4,14 @@ import ( "context" "embed" "fmt" - "log" + corelog "log" "log/slog" _ "net/http/pprof" "os" "runtime/debug" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/strimertul/utils" "github.com/apenwarr/fixconsole" "github.com/urfave/cli/v2" @@ -25,8 +27,6 @@ var appVersion = devVersionMarker const ( crashReportURL = "https://crash.strimertul.stream/upload" - logFilename = "strimertul.log" - panicFilename = "strimertul-panic.log" ) //go:embed frontend/dist @@ -34,7 +34,7 @@ var frontend embed.FS func main() { if err := fixconsole.FixConsoleIfNeeded(); err != nil { - log.Fatal(err) + corelog.Fatal(err) } var panicLog *os.File @@ -88,13 +88,13 @@ func main() { if err := level.UnmarshalText([]byte(ctx.String("log-level"))); err != nil { return cli.Exit(fmt.Sprintf("Invalid log level: %s", err), 1) } - initLogger(level) + log.Init(level) // Create file for panics var err error - panicLog, err = os.OpenFile(panicFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o666) + panicLog, err = os.OpenFile(log.PanicFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o666) if err != nil { - slog.Warn("Could not create panic log", "error", err) + slog.Warn("Could not create panic log", log.Error(err)) } else { utils.RedirectStderr(panicLog) } @@ -116,7 +116,7 @@ func main() { } if err := app.Run(os.Args); err != nil { - log.Fatal(err) + corelog.Fatal(err) } } @@ -165,7 +165,7 @@ func cliMain(ctx *cli.Context) error { func warnOnError(err error, text string, fields ...any) { if err != nil { - fields = append(fields, "error", err) + fields = append(fields, log.Error(err)) slog.Warn(text, fields...) } } diff --git a/problem.go b/problem.go index 8ea866d..be0d512 100644 --- a/problem.go +++ b/problem.go @@ -3,6 +3,7 @@ package main import ( "log/slog" + "git.sr.ht/~ashkeel/strimertul/log" "git.sr.ht/~ashkeel/strimertul/twitch" ) @@ -27,7 +28,7 @@ func (a *App) GetProblems() (problems []Problem) { // Check if the app needs to be authorized again scopesMatch, err := twitch.CheckScopes(client.DB) if err != nil { - slog.Warn("Could not check scopes for problems", "error", err) + slog.Warn("Could not check scopes for problems", log.Error(err)) } else { if !scopesMatch { problems = append(problems, Problem{ diff --git a/twitch/alerts/events.go b/twitch/alerts/events.go index 4640947..db0f7b8 100644 --- a/twitch/alerts/events.go +++ b/twitch/alerts/events.go @@ -5,6 +5,8 @@ import ( "math/rand" "text/template" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/strimertul/twitch/chat" "github.com/nicklaw5/helix/v2" ) @@ -12,7 +14,7 @@ import ( func (m *Module) onEventSubEvent(_ string, value string) { var ev eventSubNotification if err := json.Unmarshal([]byte(value), &ev); err != nil { - m.logger.Warn("Error parsing webhook payload", "error", err) + m.logger.Warn("Error parsing webhook payload", log.Error(err)) return } switch ev.Subscription.Type { @@ -24,7 +26,7 @@ func (m *Module) onEventSubEvent(_ string, value string) { // Parse as a follow event var followEv helix.EventSubChannelFollowEvent if err := json.Unmarshal(ev.Event, &followEv); err != nil { - m.logger.Warn("Error parsing follow event", "error", err) + m.logger.Warn("Error parsing follow event", log.Error(err)) return } // Pick a random message @@ -49,7 +51,7 @@ func (m *Module) onEventSubEvent(_ string, value string) { var raidEv helix.EventSubChannelRaidEvent if err := json.Unmarshal(ev.Event, &raidEv); err != nil { - m.logger.Warn("Error parsing raid event", "error", err) + m.logger.Warn("Error parsing raid event", log.Error(err)) return } // Pick a random message from base set @@ -83,7 +85,7 @@ func (m *Module) onEventSubEvent(_ string, value string) { // Parse as cheer event var cheerEv helix.EventSubChannelCheerEvent if err := json.Unmarshal(ev.Event, &cheerEv); err != nil { - m.logger.Warn("Error parsing cheer event", "error", err) + m.logger.Warn("Error parsing cheer event", log.Error(err)) return } // Pick a random message from base set @@ -117,7 +119,7 @@ func (m *Module) onEventSubEvent(_ string, value string) { // Parse as subscription event var subEv helix.EventSubChannelSubscribeEvent if err := json.Unmarshal(ev.Event, &subEv); err != nil { - m.logger.Warn("Error parsing new subscription event", "error", err) + m.logger.Warn("Error parsing new subscription event", log.Error(err)) return } m.addMixedEvent(subEv) @@ -130,7 +132,7 @@ func (m *Module) onEventSubEvent(_ string, value string) { var subEv helix.EventSubChannelSubscriptionMessageEvent err := json.Unmarshal(ev.Event, &subEv) if err != nil { - m.logger.Warn("Error parsing returning subscription event", "error", err) + m.logger.Warn("Error parsing returning subscription event", log.Error(err)) return } m.addMixedEvent(subEv) @@ -142,7 +144,7 @@ func (m *Module) onEventSubEvent(_ string, value string) { // Parse as gift event var giftEv helix.EventSubChannelSubscriptionGiftEvent if err := json.Unmarshal(ev.Event, &giftEv); err != nil { - m.logger.Warn("Error parsing subscription gifted event", "error", err) + m.logger.Warn("Error parsing subscription gifted event", log.Error(err)) return } // Pick a random message from base set diff --git a/twitch/alerts/module.go b/twitch/alerts/module.go index c8f3708..554ea77 100644 --- a/twitch/alerts/module.go +++ b/twitch/alerts/module.go @@ -8,8 +8,9 @@ import ( "text/template" "git.sr.ht/~ashkeel/strimertul/database" + "git.sr.ht/~ashkeel/strimertul/log" "git.sr.ht/~ashkeel/strimertul/twitch/eventsub" - template2 "git.sr.ht/~ashkeel/strimertul/twitch/template" + templater "git.sr.ht/~ashkeel/strimertul/twitch/template" ) type ( @@ -33,14 +34,14 @@ type Module struct { ctx context.Context db database.Database logger *slog.Logger - templater template2.Engine + templater templater.Engine templates templateCacheMap pendingMux sync.Mutex pendingSubs map[string]subMixedEvent } -func Setup(ctx context.Context, db database.Database, logger *slog.Logger, templater template2.Engine) *Module { +func Setup(ctx context.Context, db database.Database, logger *slog.Logger, templater templater.Engine) *Module { mod := &Module{ ctx: ctx, db: db, @@ -52,12 +53,12 @@ func Setup(ctx context.Context, db database.Database, logger *slog.Logger, templ // Load config from database if err := db.GetJSON(ConfigKey, &mod.Config); err != nil { - logger.Debug("Config load error", "error", err) + logger.Debug("Config load error", log.Error(err)) mod.Config = Config{} // Save empty config err = db.PutJSON(ConfigKey, mod.Config) if err != nil { - logger.Warn("Could not save default config for bot alerts", "error", err) + logger.Warn("Could not save default config for bot alerts", log.Error(err)) } } @@ -66,17 +67,17 @@ func Setup(ctx context.Context, db database.Database, logger *slog.Logger, templ if err := db.SubscribeKeyContext(ctx, ConfigKey, func(value string) { err := json.Unmarshal([]byte(value), &mod.Config) if err != nil { - logger.Warn("Error loading alert config", "error", err) + logger.Warn("Error loading alert config", log.Error(err)) } else { logger.Info("Reloaded alert config") } mod.compileTemplates() }); err != nil { - logger.Error("Could not set-up bot alert reload subscription", "error", err) + logger.Error("Could not set-up bot alert reload subscription", log.Error(err)) } if err := db.SubscribePrefixContext(ctx, mod.onEventSubEvent, eventsub.EventKeyPrefix); err != nil { - logger.Error("Could not setup twitch alert subscription", "error", err) + logger.Error("Could not setup twitch alert subscription", log.Error(err)) } logger.Debug("Loaded bot alerts") diff --git a/twitch/alerts/templates.go b/twitch/alerts/templates.go index 07801c9..788cde3 100644 --- a/twitch/alerts/templates.go +++ b/twitch/alerts/templates.go @@ -4,6 +4,8 @@ import ( "bytes" "text/template" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/strimertul/twitch/chat" ) @@ -42,7 +44,7 @@ func (m *Module) compileTemplates() { func (m *Module) addTemplate(templateList templateCache, message string) { tpl, err := m.templater.MakeTemplate(message) if err != nil { - m.logger.Error("Error compiling alert template", "error", err) + m.logger.Error("Error compiling alert template", log.Error(err)) return } templateList[message] = tpl @@ -58,7 +60,7 @@ func (m *Module) addTemplatesForType(templateList templateType, messages []strin func (m *Module) writeTemplate(tpl *template.Template, data interface{}, announce bool) { var buf bytes.Buffer if err := tpl.Execute(&buf, data); err != nil { - m.logger.Error("Error executing template for bot alert", "error", err) + m.logger.Error("Error executing template for bot alert", log.Error(err)) return } chat.WriteMessage(m.db, m.logger, chat.WriteMessageRequest{ diff --git a/twitch/chat/commands.go b/twitch/chat/commands.go index 27641b1..6aa49c6 100644 --- a/twitch/chat/commands.go +++ b/twitch/chat/commands.go @@ -5,6 +5,8 @@ import ( "log/slog" "github.com/nicklaw5/helix/v2" + + "git.sr.ht/~ashkeel/strimertul/log" ) var accessLevels = map[AccessLevelType]int{ @@ -70,7 +72,7 @@ func cmdCustom(mod *Module, cmd string, data CustomCommand, message helix.EventS return } if err := tpl.Execute(&buf, message); err != nil { - mod.logger.Error("Failed to execute custom command template", "error", err) + mod.logger.Error("Failed to execute custom command template", log.Error(err)) return } diff --git a/twitch/chat/module.go b/twitch/chat/module.go index c6787de..8abfe4d 100644 --- a/twitch/chat/module.go +++ b/twitch/chat/module.go @@ -9,6 +9,8 @@ import ( textTemplate "text/template" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "github.com/nicklaw5/helix/v2" "git.sr.ht/~ashkeel/containers/sync" @@ -59,12 +61,12 @@ func Setup(ctx context.Context, db database.Database, api *helix.Client, user he CommandCooldown: 2, } } else { - logger.Error("Failed to load chat module config", "error", err) + logger.Error("Failed to load chat module config", log.Error(err)) } } if err := db.SubscribeKeyContext(ctx, eventsub.EventKeyPrefix+helix.EventSubTypeChannelChatMessage, mod.onChatMessage); err != nil { - logger.Error("Could not subscribe to chat messages", "error", err) + logger.Error("Could not subscribe to chat messages", log.Error(err)) } // Load custom commands @@ -73,21 +75,21 @@ func Setup(ctx context.Context, db database.Database, api *helix.Client, user he if errors.Is(err, database.ErrEmptyKey) { customCommands = make(map[string]CustomCommand) } else { - logger.Error("Failed to load custom commands", "error", err) + logger.Error("Failed to load custom commands", log.Error(err)) } } mod.customCommands.Set(customCommands) if err := mod.updateTemplates(); err != nil { - logger.Error("Failed to parse custom commands", "error", err) + logger.Error("Failed to parse custom commands", log.Error(err)) } if err := db.SubscribeKeyContext(ctx, CustomCommandsKey, mod.updateCommands); err != nil { - logger.Error("Could not set-up chat command reload subscription", "error", err) + logger.Error("Could not set-up chat command reload subscription", log.Error(err)) } if err := db.SubscribeKeyContext(ctx, WriteMessageRPC, mod.handleWriteMessageRPC); err != nil { - logger.Error("Could not set-up chat command reload subscription", "error", err) + logger.Error("Could not set-up chat command reload subscription", log.Error(err)) } return mod @@ -98,7 +100,7 @@ func (mod *Module) onChatMessage(newValue string) { Event helix.EventSubChannelChatMessageEvent `json:"event"` } if err := json.Unmarshal([]byte(newValue), &chatMessage); err != nil { - mod.logger.Error("Failed to decode incoming chat message", slog.String("error", err.Error())) + mod.logger.Error("Failed to decode incoming chat message", log.Error(err)) return } @@ -146,7 +148,7 @@ func (mod *Module) onChatMessage(newValue string) { func (mod *Module) handleWriteMessageRPC(value string) { var request WriteMessageRequest if err := json.Unmarshal([]byte(value), &request); err != nil { - mod.logger.Warn("Failed to decode write message request", "error", err) + mod.logger.Warn("Failed to decode write message request", log.Error(err)) return } @@ -157,7 +159,7 @@ func (mod *Module) handleWriteMessageRPC(value string) { Message: request.Message, }) if err != nil { - mod.logger.Error("Failed to send announcement", "error", err) + mod.logger.Error("Failed to send announcement", log.Error(err)) } if resp.Error != "" { mod.logger.Error("Failed to send announcement", slog.String("code", resp.Error), slog.String("message", resp.ErrorMessage)) @@ -172,7 +174,7 @@ func (mod *Module) handleWriteMessageRPC(value string) { Message: request.Message, }) if err != nil { - mod.logger.Error("Failed to send whisper", "error", err) + mod.logger.Error("Failed to send whisper", log.Error(err)) } if resp.Error != "" { mod.logger.Error("Failed to send whisper", slog.String("code", resp.Error), slog.String("message", resp.ErrorMessage)) @@ -187,7 +189,7 @@ func (mod *Module) handleWriteMessageRPC(value string) { ReplyParentMessageID: request.ReplyTo, }) if err != nil { - mod.logger.Error("Failed to send chat message", "error", err) + mod.logger.Error("Failed to send chat message", log.Error(err)) } if resp.Error != "" { mod.logger.Error("Failed to send chat message", slog.String("code", resp.Error), slog.String("message", resp.ErrorMessage)) @@ -197,12 +199,12 @@ func (mod *Module) handleWriteMessageRPC(value string) { func (mod *Module) updateCommands(value string) { err := utils.LoadJSONToWrapped[map[string]CustomCommand](value, mod.customCommands) if err != nil { - mod.logger.Error("Failed to decode new custom commands", "error", err) + mod.logger.Error("Failed to decode new custom commands", log.Error(err)) return } // Recreate templates if err := mod.updateTemplates(); err != nil { - mod.logger.Error("Failed to update custom commands templates", "error", err) + mod.logger.Error("Failed to update custom commands templates", log.Error(err)) return } } @@ -236,7 +238,7 @@ func (mod *Module) GetChatters() (users []string) { for { userClient, err := twitch.GetUserClient(mod.db, false) if err != nil { - slog.Error("Could not get user api client for list of chatters", "error", err) + slog.Error("Could not get user api client for list of chatters", log.Error(err)) return } res, err := userClient.GetChannelChatChatters(&helix.GetChatChattersParams{ @@ -246,7 +248,7 @@ func (mod *Module) GetChatters() (users []string) { After: cursor, }) if err != nil { - mod.logger.Error("Could not retrieve list of chatters", "error", err) + mod.logger.Error("Could not retrieve list of chatters", log.Error(err)) return } for _, user := range res.Data.Chatters { diff --git a/twitch/chat/write.go b/twitch/chat/write.go index 772a689..064443a 100644 --- a/twitch/chat/write.go +++ b/twitch/chat/write.go @@ -4,6 +4,7 @@ import ( "log/slog" "git.sr.ht/~ashkeel/strimertul/database" + "git.sr.ht/~ashkeel/strimertul/log" ) // WriteMessageRequest is an RPC to send a chat message with extra options @@ -17,6 +18,6 @@ type WriteMessageRequest struct { func WriteMessage(db database.Database, logger *slog.Logger, m WriteMessageRequest) { err := db.PutJSON(WriteMessageRPC, m) if err != nil { - logger.Error("Failed to write chat message", "error", err) + logger.Error("Failed to write chat message", log.Error(err)) } } diff --git a/twitch/client/client.go b/twitch/client/client.go index 9397bd3..fcb67c6 100644 --- a/twitch/client/client.go +++ b/twitch/client/client.go @@ -8,6 +8,8 @@ import ( "log/slog" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/containers/sync" "github.com/nicklaw5/helix/v2" @@ -25,6 +27,8 @@ type Manager struct { } func NewManager(ctx context.Context, db database.Database, server *webserver.WebServer) (*Manager, error) { + logger := log.GetLogger(ctx) + // Get Twitch config var config twitch.Config if err := db.GetJSON(twitch.ConfigKey, &config); err != nil { @@ -50,7 +54,7 @@ func NewManager(ctx context.Context, db database.Database, server *webserver.Web if err = db.SubscribeKeyContext(ctx, twitch.ConfigKey, func(value string) { var newConfig twitch.Config if err := json.Unmarshal([]byte(value), &newConfig); err != nil { - slog.Error("Failed to decode Twitch integration config", "error", err) + logger.Error("Failed to decode Twitch integration config", log.Error(err)) return } @@ -60,7 +64,7 @@ func NewManager(ctx context.Context, db database.Database, server *webserver.Web clientContext, cancel = context.WithCancel(ctx) updatedClient, err = newClient(clientContext, newConfig, db, server) if err != nil { - slog.Error("Could not create twitch client with new config, keeping old", "error", err) + logger.Error("Could not create twitch client with new config, keeping old", log.Error(err)) return } @@ -68,9 +72,9 @@ func NewManager(ctx context.Context, db database.Database, server *webserver.Web updatedClient.Merge(manager.client) manager.client = updatedClient - slog.Info("Reloaded/updated Twitch integration") + logger.Info("Reloaded/updated Twitch integration") }); err != nil { - slog.Error("Could not setup twitch config reload subscription", "error", err) + logger.Error("Could not setup twitch config reload subscription", log.Error(err)) } return manager, nil @@ -140,7 +144,7 @@ func newClient(ctx context.Context, config twitch.Config, db database.Database, if userClient, err := twitch.GetUserClient(db, true); err == nil { users, err := userClient.GetUsers(&helix.UsersParams{}) if err != nil { - client.Logger.Error("Failed looking up user", "error", err) + client.Logger.Error("Failed looking up user", log.Error(err)) } else if len(users.Data.Users) < 1 { client.Logger.Error("No users found, please authenticate in Twitch configuration -> Events") } else { @@ -148,7 +152,7 @@ func newClient(ctx context.Context, config twitch.Config, db database.Database, client.User = users.Data.Users[0] client.eventSub, err = eventsub.Setup(ctx, userClient, client.User, db, client.Logger) if err != nil { - client.Logger.Error("Failed to setup EventSub", "error", err) + client.Logger.Error("Failed to setup EventSub", log.Error(err)) } tpl := client.GetTemplateEngine() @@ -184,14 +188,14 @@ func (c *Client) runStatusPoll() { UserIDs: []string{c.User.ID}, }) if err != nil { - c.Logger.Error("Error checking stream status", "error", err) + c.Logger.Error("Error checking stream status", log.Error(err)) return } c.streamOnline.Set(len(status.Data.Streams) > 0) err = c.DB.PutJSON(twitch.StreamInfoKey, status.Data.Streams) if err != nil { - c.Logger.Warn("Error saving stream info", "error", err) + c.Logger.Warn("Error saving stream info", log.Error(err)) } }() diff --git a/twitch/client/template.go b/twitch/client/template.go index 4ba4644..aa95563 100644 --- a/twitch/client/template.go +++ b/twitch/client/template.go @@ -7,6 +7,8 @@ import ( "strings" textTemplate "text/template" + "git.sr.ht/~ashkeel/strimertul/log" + "github.com/Masterminds/sprig/v3" "git.sr.ht/~ashkeel/strimertul/twitch/template" @@ -57,7 +59,7 @@ func (c *Client) GetTemplateEngine() template.Engine { counter++ err := c.DB.PutKey(counterKey, strconv.Itoa(counter)) if err != nil { - c.Logger.Error("Error saving key", "error", err, slog.String("key", counterKey)) + c.Logger.Error("Error saving key", slog.String("key", counterKey), log.Error(err)) } return counter }, diff --git a/twitch/eventsub/client.go b/twitch/eventsub/client.go index a2f4578..b6120b2 100644 --- a/twitch/eventsub/client.go +++ b/twitch/eventsub/client.go @@ -7,6 +7,8 @@ import ( "log/slog" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/strimertul/database" "git.sr.ht/~ashkeel/strimertul/utils" "github.com/gorilla/websocket" @@ -55,7 +57,7 @@ func (c *Client) eventSubLoop() { for endpoint != "" { endpoint, connection, err = c.connectWebsocket(endpoint, connection) if err != nil { - c.logger.Error("EventSub websocket read error", "error", err) + c.logger.Error("EventSub websocket read error", log.Error(err)) } } if connection != nil { @@ -83,7 +85,7 @@ func readLoop(connection *websocket.Conn, recv chan<- []byte, wsErr chan<- error func (c *Client) connectWebsocket(url string, oldConnection *websocket.Conn) (string, *websocket.Conn, error) { connection, _, err := websocket.DefaultDialer.Dial(url, nil) if err != nil { - c.logger.Error("Could not establish a connection to the EventSub websocket", "error", err) + c.logger.Error("Could not establish a connection to the EventSub websocket", log.Error(err)) return "", nil, err } @@ -106,7 +108,7 @@ func (c *Client) connectWebsocket(url string, oldConnection *websocket.Conn) (st var wsMessage WebsocketMessage err = json.Unmarshal(messageData, &wsMessage) if err != nil { - c.logger.Error("Error decoding EventSub message", "error", err) + c.logger.Error("Error decoding EventSub message", log.Error(err)) continue } @@ -125,7 +127,7 @@ func (c *Client) processMessage(wsMessage WebsocketMessage, oldConnection *webso var welcomeData WelcomeMessagePayload err := json.Unmarshal(wsMessage.Payload, &welcomeData) if err != nil { - c.logger.Error("Error decoding EventSub welcome message", slog.String("message-type", wsMessage.Metadata.MessageType), "error", err) + c.logger.Error("Error decoding EventSub welcome message", slog.String("message-type", wsMessage.Metadata.MessageType), log.Error(err)) break } c.logger.Info("Connection to EventSub websocket established", slog.String("session-id", welcomeData.Session.ID)) @@ -138,14 +140,14 @@ func (c *Client) processMessage(wsMessage WebsocketMessage, oldConnection *webso // Add subscription to websocket session err = c.addSubscriptionsForSession(welcomeData.Session.ID) if err != nil { - c.logger.Error("Could not add subscriptions", "error", err) + c.logger.Error("Could not add subscriptions", log.Error(err)) break } case "session_reconnect": var reconnectData WelcomeMessagePayload err := json.Unmarshal(wsMessage.Payload, &reconnectData) if err != nil { - c.logger.Error("Error decoding EventSub session reconnect parameters", slog.String("message-type", wsMessage.Metadata.MessageType), "error", err) + c.logger.Error("Error decoding EventSub session reconnect parameters", slog.String("message-type", wsMessage.Metadata.MessageType), log.Error(err)) break } c.logger.Info("EventSub websocket requested a reconnection", slog.String("session-id", reconnectData.Session.ID), slog.String("reconnect-url", reconnectData.Session.ReconnectURL)) @@ -173,7 +175,7 @@ func (c *Client) processEvent(message WebsocketMessage) { var notificationData NotificationMessagePayload err := json.Unmarshal(message.Payload, ¬ificationData) if err != nil { - c.logger.Error("Error decoding EventSub notification payload", slog.String("message-type", message.Metadata.MessageType), "error", err) + c.logger.Error("Error decoding EventSub notification payload", slog.String("message-type", message.Metadata.MessageType), log.Error(err)) } notificationData.Date = time.Now() @@ -181,7 +183,7 @@ func (c *Client) processEvent(message WebsocketMessage) { historyKey := fmt.Sprintf("%s%s", HistoryKeyPrefix, notificationData.Subscription.Type) err = c.db.PutJSON(eventKey, notificationData) if err != nil { - c.logger.Error("Error storing event to database", slog.String("key", eventKey), "error", err) + c.logger.Error("Error storing event to database", slog.String("key", eventKey), log.Error(err)) } var archive []NotificationMessagePayload @@ -195,7 +197,7 @@ func (c *Client) processEvent(message WebsocketMessage) { } err = c.db.PutJSON(historyKey, archive) if err != nil { - c.logger.Error("Error storing event to database", slog.String("key", historyKey), "error", err) + c.logger.Error("Error storing event to database", slog.String("key", historyKey), log.Error(err)) } } diff --git a/twitch/timers/module.go b/twitch/timers/module.go index 18bcfd8..c927727 100644 --- a/twitch/timers/module.go +++ b/twitch/timers/module.go @@ -7,6 +7,8 @@ import ( "math/rand" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/containers/sync" "git.sr.ht/~ashkeel/strimertul/database" "git.sr.ht/~ashkeel/strimertul/twitch/chat" @@ -42,25 +44,25 @@ func Setup(ctx context.Context, db database.Database, logger *slog.Logger) *Modu // Load config from database if err := db.GetJSON(ConfigKey, &mod.Config); err != nil { - logger.Debug("Config load error", "error", err) + logger.Debug("Config load error", log.Error(err)) mod.Config = Config{ Timers: make(map[string]ChatTimer), } // Save empty config err = db.PutJSON(ConfigKey, mod.Config) if err != nil { - logger.Warn("Could not save default config for bot timers", "error", err) + logger.Warn("Could not save default config for bot timers", log.Error(err)) } } if err := db.SubscribeKeyContext(ctx, ConfigKey, func(value string) { if err := json.Unmarshal([]byte(value), &mod.Config); err != nil { - logger.Warn("Error reloading timer config", "error", err) + logger.Warn("Error reloading timer config", log.Error(err)) return } logger.Info("Reloaded timer config") }); err != nil { - logger.Error("Could not set-up timer reload subscription", "error", err) + logger.Error("Could not set-up timer reload subscription", log.Error(err)) } logger.Debug("Loaded timers", slog.Int("timers", len(mod.Config.Timers))) @@ -81,7 +83,7 @@ func (m *Module) runTimers() { err := m.db.PutJSON(chat.ActivityKey, m.messages.Get()) if err != nil { - m.logger.Warn("Error saving chat activity", "error", err) + m.logger.Warn("Error saving chat activity", log.Error(err)) } // Calculate activity diff --git a/utils/deferlogger.go b/utils/deferlogger.go index 481bbe4..fc0485c 100644 --- a/utils/deferlogger.go +++ b/utils/deferlogger.go @@ -5,11 +5,13 @@ import ( "log/slog" "reflect" "runtime/debug" + + "git.sr.ht/~ashkeel/strimertul/log" ) func Close(res io.Closer) { err := res.Close() if err != nil { - slog.Error("Could not close resource", slog.String("name", reflect.TypeOf(res).String()), slog.String("stack", string(debug.Stack())), "error", err) + slog.Error("Could not close resource", slog.String("name", reflect.TypeOf(res).String()), slog.String("stack", string(debug.Stack())), log.Error(err)) } } diff --git a/webserver/server.go b/webserver/server.go index 35fb021..b23c4da 100644 --- a/webserver/server.go +++ b/webserver/server.go @@ -14,6 +14,8 @@ import ( "net/http/pprof" "time" + "git.sr.ht/~ashkeel/strimertul/log" + "git.sr.ht/~ashkeel/containers/sync" kv "git.sr.ht/~ashkeel/kilovolt/v12" "git.sr.ht/~ashkeel/strimertul/database" @@ -21,7 +23,9 @@ import ( type WebServer struct { Config *sync.RWSync[ServerConfig] + ctx context.Context db database.Database + logger *slog.Logger server Server frontend fs.FS hub *kv.Hub @@ -32,9 +36,11 @@ type WebServer struct { factory ServerFactory } -func NewServer(db database.Database, serverFactory ServerFactory) (*WebServer, error) { +func NewServer(ctx context.Context, db database.Database, serverFactory ServerFactory) (*WebServer, error) { server := &WebServer{ db: db, + ctx: ctx, + logger: log.GetLogger(ctx), server: nil, requestedRoutes: sync.NewMap[string, http.Handler](), restart: sync.NewRWSync(false), @@ -46,7 +52,7 @@ func NewServer(db database.Database, serverFactory ServerFactory) (*WebServer, e err := db.GetJSON(ServerConfigKey, &config) if err != nil { if !errors.Is(err, database.ErrEmptyKey) { - slog.Warn("HTTP config is corrupted or could not be read", "error", err) + server.logger.Warn("HTTP config is corrupted or could not be read", log.Error(err)) } // Initialize with default config server.Config.Set(ServerConfig{ @@ -155,7 +161,7 @@ func (s *WebServer) Listen() error { for { // Read config and make http request mux config := s.Config.Get() - slog.Info("Starting HTTP server", slog.String("bind", config.Bind)) + s.logger.Info("Starting HTTP server", slog.String("bind", config.Bind)) s.mux = s.makeMux() // Make HTTP server instance @@ -167,25 +173,25 @@ func (s *WebServer) Listen() error { } // Start HTTP server - slog.Info("HTTP server started", slog.String("bind", config.Bind)) + s.logger.Info("HTTP server started", slog.String("bind", config.Bind)) err = s.server.Start() // If the server died, we need to see what to do - slog.Debug("HTTP server died", "error", err) + s.logger.Debug("HTTP server died", log.Error(err)) if err != nil && !errors.Is(err, http.ErrServerClosed) { exit <- err return } // Are we trying to close or restart? - slog.Debug("HTTP server stopped", slog.Bool("restart", s.restart.Get())) + s.logger.Debug("HTTP server stopped", slog.Bool("restart", s.restart.Get())) if s.restart.Get() { s.restart.Set(false) continue } break } - slog.Debug("HTTP server stalled") + s.logger.Debug("HTTP server stalled") exit <- nil }() @@ -198,7 +204,7 @@ func (s *WebServer) onConfigUpdate(value string) { var config ServerConfig err := json.Unmarshal([]byte(value), &config) if err != nil { - slog.Error("Failed to unmarshal config", "error", err) + s.logger.Error("Failed to unmarshal config", log.Error(err)) return } @@ -215,7 +221,7 @@ func (s *WebServer) onConfigUpdate(value string) { s.restart.Set(true) err = s.server.Shutdown(context.Background()) if err != nil { - slog.Error("Failed to shutdown server", "error", err) + s.logger.Error("Failed to shutdown server", log.Error(err)) return } }