fix: sane errors

This commit is contained in:
Ash Keel 2024-03-16 01:20:15 +01:00
parent ce2ce81768
commit bc83a743f3
No known key found for this signature in database
GPG Key ID: 53A9E9A6035DD109
25 changed files with 386 additions and 257 deletions

50
app.go
View File

@ -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))
}
}

View File

@ -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
}

View File

@ -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',

View File

@ -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<void>;
@ -14,7 +15,7 @@ export function GetDocumentation():Promise<{[key: string]: docs.KeyObject}>;
export function GetKilovoltBind():Promise<string>;
export function GetLastLogs():Promise<Array<main.LogEntry>>;
export function GetLastLogs():Promise<Array<log.Entry>>;
export function GetProblems():Promise<Array<main.Problem>>;

View File

@ -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;

17
log/attr.go Normal file
View File

@ -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()),
)
}

24
log/context.go Normal file
View File

@ -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
}

146
log/setup.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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))
}

18
main.go
View File

@ -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...)
}
}

View File

@ -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{

View File

@ -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

View File

@ -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")

View File

@ -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{

View File

@ -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
}

View File

@ -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 {

View File

@ -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))
}
}

View File

@ -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))
}
}()

View File

@ -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
},

View File

@ -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, &notificationData)
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))
}
}

View File

@ -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

View File

@ -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))
}
}

View File

@ -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
}
}