From f5fb1b1188089837687e0c8d19e11c4df168a694 Mon Sep 17 00:00:00 2001 From: Paul Larsen Date: Sun, 13 Dec 2020 18:00:11 +0000 Subject: [PATCH] Improve error logging; can now pass a standard logger struct to the updater and dispatcher to change log settings. Defaults to stderr. --- ext/dispatcher.go | 49 +++++++++++++++++++++++++++++++++++------ ext/updater.go | 40 ++++++++++++++++++++++++++++----- samples/echoBot/main.go | 2 +- 3 files changed, 77 insertions(+), 14 deletions(-) diff --git a/ext/dispatcher.go b/ext/dispatcher.go index c2156989..934ea4fb 100644 --- a/ext/dispatcher.go +++ b/ext/dispatcher.go @@ -17,8 +17,10 @@ type Dispatcher struct { // Error handles any errors that occur during handler execution. Error func(ctx *Context, err error) // Panic handles any panics that occur during handler execution. - // If no panic is defined, the stack is logged to stderr. + // If this field is nil, the stack is logged to stderr. Panic func(ctx *Context, stack []byte) + // ErrorLog is the output to log to in the case of a library error. + ErrorLog *log.Logger // handlerGroups represents the list of available handler groups, numerically sorted. handlerGroups []int @@ -33,13 +35,43 @@ type Dispatcher struct { waitGroup sync.WaitGroup } +type DispatcherOpts struct { + // Error handles any errors that occur during handler execution. + Error func(ctx *Context, err error) + // Panic handles any panics that occur during handler execution. + // If no panic is defined, the stack is logged to + Panic func(ctx *Context, stack []byte) + + // MaxRoutines is used to decide how to limit the number of goroutines spawned by the dispatcher. + // If MaxRoutines == 0, DefaultMaxRoutines is used instead. + // If MaxRoutines < 0, no limits are imposed. + MaxRoutines int + ErrorLog *log.Logger +} + // NewDispatcher creates a new dispatcher, which process and handles incoming updates from the updates channel. -// The maxRoutines argument is used to decide how to limit the number of goroutines spawned by the dispatcher. -// If maxRoutines == 0, DefaultMaxRoutines is used instead. -// If maxRoutines < 0, no limits are imposed. -func NewDispatcher(updates chan json.RawMessage, maxRoutines int) *Dispatcher { +func NewDispatcher(updates chan json.RawMessage, opts *DispatcherOpts) *Dispatcher { var limiter chan struct{} + var errFunc func(ctx *Context, err error) + var panicFunc func(ctx *Context, stack []byte) + + maxRoutines := DefaultMaxRoutines + errLog := errorLog + + if opts != nil { + if opts.MaxRoutines != 0 { + maxRoutines = opts.MaxRoutines + } + + if opts.ErrorLog != nil { + errLog = opts.ErrorLog + } + + errFunc = opts.Error + panicFunc = opts.Panic + } + if maxRoutines >= 0 { if maxRoutines == 0 { maxRoutines = DefaultMaxRoutines @@ -49,6 +81,9 @@ func NewDispatcher(updates chan json.RawMessage, maxRoutines int) *Dispatcher { } return &Dispatcher{ + Error: errFunc, + Panic: panicFunc, + ErrorLog: errLog, updatesChan: updates, handlers: make(map[int][]Handler), limiter: limiter, @@ -120,7 +155,7 @@ var ContinueGroups = errors.New("group iteration continued") func (d *Dispatcher) ProcessRawUpdate(b *gotgbot.Bot, r json.RawMessage) { var upd gotgbot.Update if err := json.Unmarshal(r, &upd); err != nil { - log.Println("failed to process raw update: " + err.Error()) + d.ErrorLog.Println("failed to process raw update: " + err.Error()) return } @@ -137,7 +172,7 @@ func (d *Dispatcher) ProcessUpdate(b *gotgbot.Bot, update *gotgbot.Update) { return } - log.Println(debug.Stack()) + d.ErrorLog.Println(debug.Stack()) } }() diff --git a/ext/updater.go b/ext/updater.go index 1e71daba..4799cba3 100644 --- a/ext/updater.go +++ b/ext/updater.go @@ -8,6 +8,7 @@ import ( "log" "net/http" "net/url" + "os" "strconv" "time" @@ -18,23 +19,50 @@ type Updater struct { Bot gotgbot.Bot Dispatcher *Dispatcher UpdateChan chan json.RawMessage + ErrorLog *log.Logger idle bool running bool server *http.Server } +var errorLog = log.New(os.Stderr, "ERROR", log.LstdFlags) + +type UpdaterOpts struct { + PollingTimeout time.Duration + ErrorLog *log.Logger + + DispatcherOpts DispatcherOpts +} + // NewUpdater Creates a new Updater, as well as the necessary structures for -func NewUpdater(bot *gotgbot.Bot) Updater { +func NewUpdater(bot *gotgbot.Bot, opts *UpdaterOpts) Updater { + errLog := errorLog + pollTimeout := time.Second * 10 + var dispatcherOpts DispatcherOpts + + if opts != nil { + if opts.PollingTimeout != 0 { + pollTimeout = opts.PollingTimeout + } + + if opts.ErrorLog != nil { + errLog = opts.ErrorLog + } + + dispatcherOpts = opts.DispatcherOpts + } + updateChan := make(chan json.RawMessage) return Updater{ Bot: gotgbot.Bot{ Token: bot.Token, APIURL: bot.APIURL, Client: http.Client{}, - GetTimeout: time.Second * 10, + GetTimeout: pollTimeout, }, // create new bot client to allow for independent timeout changes - Dispatcher: NewDispatcher(updateChan, DefaultMaxRoutines), + ErrorLog: errLog, + Dispatcher: NewDispatcher(updateChan, &dispatcherOpts), UpdateChan: updateChan, } } @@ -90,7 +118,7 @@ func (u *Updater) pollingLoop(clean bool, v url.Values) { // note: this bot instance uses a custom http.Client with longer timeouts r, err := u.Bot.Get("getUpdates", v) if err != nil { - log.Println("failed to get updates; sleeping 1s: " + err.Error()) + u.ErrorLog.Println("failed to get updates; sleeping 1s: " + err.Error()) time.Sleep(time.Second) continue @@ -100,7 +128,7 @@ func (u *Updater) pollingLoop(clean bool, v url.Values) { var rawUpdates []json.RawMessage if err := json.Unmarshal(r, &rawUpdates); err != nil { - log.Println("failed to unmarshal updates: " + err.Error()) + u.ErrorLog.Println("failed to unmarshal updates: " + err.Error()) continue } @@ -113,7 +141,7 @@ func (u *Updater) pollingLoop(clean bool, v url.Values) { } if err := json.Unmarshal(rawUpdates[len(rawUpdates)-1], &lastUpdate); err != nil { - log.Println("failed to unmarshal last update: " + err.Error()) + u.ErrorLog.Println("failed to unmarshal last update: " + err.Error()) continue } diff --git a/samples/echoBot/main.go b/samples/echoBot/main.go index 7174dae7..2cc15928 100644 --- a/samples/echoBot/main.go +++ b/samples/echoBot/main.go @@ -18,7 +18,7 @@ func main() { } // Create updater and dispatcher. - updater := ext.NewUpdater(b) + updater := ext.NewUpdater(b, nil) dispatcher := updater.Dispatcher // Add echo handler to reply to all messages.