diff --git a/ext/botmapping.go b/ext/botmapping.go index ca80dafa..835f9c3b 100644 --- a/ext/botmapping.go +++ b/ext/botmapping.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "io" - "log" + "log/slog" "net/http" "strings" "sync" @@ -46,8 +46,8 @@ type botMapping struct { // errFunc fills the same purpose as Updater.UnhandledErrFunc. errFunc ErrorFunc - // errorLog fills the same purpose as Updater.ErrorLog. - errorLog *log.Logger + // logger fills the same purpose as Updater.Logger. + logger *slog.Logger } var ( @@ -198,7 +198,7 @@ func (m *botMapping) getHandlerFunc(prefix string) func(writer http.ResponseWrit if m.errFunc != nil { m.errFunc(err) } else { - m.logf("Failed to read incoming update contents: %s", err.Error()) + logError(m.logger, "failed to read incoming update contents", err) } w.WriteHeader(http.StatusInternalServerError) return @@ -208,14 +208,6 @@ func (m *botMapping) getHandlerFunc(prefix string) func(writer http.ResponseWrit } } -func (m *botMapping) logf(format string, args ...interface{}) { - if m.errorLog != nil { - m.errorLog.Printf(format, args...) - } else { - log.Printf(format, args...) - } -} - func (b *botData) stop() { // Close stopUpdates loops first, to ensure any updates currently being polled have the time to be sent to the updateChan. if b.stopUpdates != nil { diff --git a/ext/common.go b/ext/common.go new file mode 100644 index 00000000..4ff62522 --- /dev/null +++ b/ext/common.go @@ -0,0 +1,30 @@ +package ext + +import ( + "log" + "log/slog" +) + +func logError(l *slog.Logger, text string, err error) { + if l == nil { + log.Printf("ERROR: %s: %s", text, err.Error()) + return + } + l.Error(text, "error", err.Error()) +} + +func logDebug(l *slog.Logger, text string, args ...any) { + if l == nil { + // No logger? No debug. + return + } + l.Error(text, args...) +} + +// ternary operator approximation. +func iftrue[T any](b bool, t T, f T) T { + if b { + return t + } + return f +} diff --git a/ext/dispatcher.go b/ext/dispatcher.go index 5074ad38..25202fac 100644 --- a/ext/dispatcher.go +++ b/ext/dispatcher.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "log" + "log/slog" "runtime/debug" "strings" "sync" @@ -74,11 +74,10 @@ type Dispatcher struct { // UnhandledErrFunc provides more flexibility for dealing with unhandled update processing errors. // This includes errors when unmarshalling updates, unhandled panics during handler executions, or unknown // dispatcher actions. - // If nil, the error goes to ErrorLog. + // If nil, the error goes to the logger. UnhandledErrFunc ErrorFunc - // ErrorLog specifies an optional logger for unexpected behavior from handlers. - // If nil, logging is done via the log package's standard logger. - ErrorLog *log.Logger + // Logger specifies an optional logger for unexpected behavior from handlers. + Logger *slog.Logger // handlers represents all available handlers. handlers handlerMapping @@ -101,18 +100,17 @@ type DispatcherOpts struct { // More info at Dispatcher.Error. Error DispatcherErrorHandler // Panic handles any panics that occur during handler execution. - // If no panic handlers are defined, the stack is logged to ErrorLog. + // If no panic handlers are defined, the stack is logged to the logger. // More info at Dispatcher.Panic. Panic DispatcherPanicHandler // UnhandledErrFunc provides more flexibility for dealing with unhandled update processing errors. // This includes errors when unmarshalling updates, unhandled panics during handler executions, or unknown // dispatcher actions. - // If nil, the error goes to ErrorLog. + // If nil, the error goes to the logger. UnhandledErrFunc ErrorFunc - // ErrorLog specifies an optional logger for unexpected behavior from handlers. - // If nil, logging is done via the log package's standard logger. - ErrorLog *log.Logger + // Logger specifies an optional logger for unexpected behavior from handlers. + Logger *slog.Logger // MaxRoutines is used to decide how to limit the number of goroutines spawned by the dispatcher. // This defines how many updates can be processed at the same time. @@ -127,23 +125,19 @@ func NewDispatcher(opts *DispatcherOpts) *Dispatcher { var errHandler DispatcherErrorHandler var panicHandler DispatcherPanicHandler var unhandledErrFunc ErrorFunc - var errLog *log.Logger + logger := slog.Default() maxRoutines := DefaultMaxRoutines processor := Processor(BaseProcessor{}) if opts != nil { - if opts.MaxRoutines != 0 { - maxRoutines = opts.MaxRoutines - } - if opts.Processor != nil { - processor = opts.Processor - } + maxRoutines = iftrue(opts.MaxRoutines != 0, opts.MaxRoutines, maxRoutines) + processor = iftrue(opts.Processor != nil, opts.Processor, processor) + logger = iftrue(opts.Logger != nil, opts.Logger, logger) errHandler = opts.Error panicHandler = opts.Panic unhandledErrFunc = opts.UnhandledErrFunc - errLog = opts.ErrorLog } var limiter chan struct{} @@ -157,25 +151,17 @@ func NewDispatcher(opts *DispatcherOpts) *Dispatcher { } return &Dispatcher{ + Logger: logger.With("component", "dispatcher"), Processor: processor, Error: errHandler, Panic: panicHandler, UnhandledErrFunc: unhandledErrFunc, - ErrorLog: errLog, handlers: handlerMapping{}, limiter: limiter, waitGroup: sync.WaitGroup{}, } } -func (d *Dispatcher) logf(format string, args ...interface{}) { - if d.ErrorLog != nil { - d.ErrorLog.Printf(format, args...) - } else { - log.Printf(format, args...) - } -} - // CurrentUsage returns the current number of concurrently processing updates. func (d *Dispatcher) CurrentUsage() int { return len(d.limiter) @@ -215,7 +201,7 @@ func (d *Dispatcher) Start(b *gotgbot.Bot, updates <-chan json.RawMessage) { if d.UnhandledErrFunc != nil { d.UnhandledErrFunc(err) } else { - d.logf("Failed to process update: %s", err.Error()) + logError(d.Logger, "failed to process update", err) } } @@ -299,8 +285,12 @@ func (d *Dispatcher) iterateOverHandlerGroups(b *gotgbot.Bot, ctx *Context) erro continue } + logDebug(d.Logger, "Matched handler on update", "handler", handler.Name()) + err := handler.HandleUpdate(b, ctx) if err != nil { + logDebug(d.Logger, "Update handling returned error", "handler", handler.Name(), "error", err.Error()) + if errors.Is(err, ContinueGroups) { // Continue handling current group. continue diff --git a/ext/updater.go b/ext/updater.go index c67de096..fbb7c5bc 100644 --- a/ext/updater.go +++ b/ext/updater.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "log" + "log/slog" "net" "net/http" "strconv" @@ -32,11 +32,10 @@ type Updater struct { // UnhandledErrFunc provides more flexibility for dealing with previously unhandled errors, such as failures to get // updates (when long-polling), or failures to unmarshal. - // If nil, the error goes to ErrorLog. + // If nil, the error goes to the logger. UnhandledErrFunc ErrorFunc - // ErrorLog specifies an optional logger for unexpected behavior from handlers. - // If nil, logging is done via the log package's standard logger. - ErrorLog *log.Logger + // Logger specifies an optional logger for unexpected behavior from handlers. + Logger *slog.Logger // stopIdling is the channel that blocks the main thread from exiting, to keep the bots running. stopIdling chan struct{} @@ -51,42 +50,33 @@ type Updater struct { type UpdaterOpts struct { // UnhandledErrFunc provides more flexibility for dealing with previously unhandled errors, such as failures to get // updates (when long-polling), or failures to unmarshal. - // If nil, the error goes to ErrorLog. + // If nil, the error goes to the logger. UnhandledErrFunc ErrorFunc - // ErrorLog specifies an optional logger for unexpected behavior from handlers. - // If nil, logging is done via the log package's standard logger. - ErrorLog *log.Logger + // Logger specifies an optional logger for unexpected behavior from handlers. + Logger *slog.Logger } // NewUpdater Creates a new Updater, as well as a Dispatcher and any optional updater configurations (via UpdaterOpts). func NewUpdater(dispatcher UpdateDispatcher, opts *UpdaterOpts) *Updater { var unhandledErrFunc ErrorFunc - var errLog *log.Logger + logger := slog.Default() if opts != nil { + logger = iftrue(opts.Logger == nil, logger, opts.Logger) unhandledErrFunc = opts.UnhandledErrFunc - errLog = opts.ErrorLog } return &Updater{ Dispatcher: dispatcher, UnhandledErrFunc: unhandledErrFunc, - ErrorLog: errLog, + Logger: logger.With("component", "updater"), botMapping: botMapping{ - errFunc: unhandledErrFunc, - errorLog: errLog, + errFunc: unhandledErrFunc, + logger: logger.With("component", "botmapping"), }, } } -func (u *Updater) logf(format string, args ...interface{}) { - if u.ErrorLog != nil { - u.ErrorLog.Printf(format, args...) - } else { - log.Printf(format, args...) - } -} - // PollingOpts represents the optional values to start long polling. type PollingOpts struct { // DropPendingUpdates toggles whether to drop updates which were sent before the bot was started. @@ -196,7 +186,7 @@ func (u *Updater) pollingLoop(ctx context.Context, bData *botData, opts *gotgbot if u.UnhandledErrFunc != nil { u.UnhandledErrFunc(err) } else { - u.logf("Failed to get updates; sleeping 1s: %s", err.Error()) + logError(u.Logger, "failed to get updates; sleeping 1s", err) time.Sleep(time.Second) } continue @@ -210,7 +200,7 @@ func (u *Updater) pollingLoop(ctx context.Context, bData *botData, opts *gotgbot if u.UnhandledErrFunc != nil { u.UnhandledErrFunc(err) } else { - u.logf("Failed to unmarshal updates: %s", err.Error()) + logError(u.Logger, "failed to unmarshal updates", err) } continue } @@ -228,7 +218,7 @@ func (u *Updater) pollingLoop(ctx context.Context, bData *botData, opts *gotgbot if u.UnhandledErrFunc != nil { u.UnhandledErrFunc(err) } else { - u.logf("Failed to unmarshal last update: %s", err.Error()) + logError(u.Logger, "failed to unmarshal last update", err) } continue } diff --git a/go.mod b/go.mod index 52478bc3..aaf7bf17 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/PaulSonOfLars/gotgbot/v2 -go 1.19 +go 1.21 diff --git a/samples/callbackqueryBot/go.mod b/samples/callbackqueryBot/go.mod index 9194fac1..3c4afe25 100644 --- a/samples/callbackqueryBot/go.mod +++ b/samples/callbackqueryBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/callbackqueryBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/commandBot/go.mod b/samples/commandBot/go.mod index fa05ced1..0936caca 100644 --- a/samples/commandBot/go.mod +++ b/samples/commandBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/commandBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/conversationBot/go.mod b/samples/conversationBot/go.mod index 64e8d1c2..4552f3b6 100644 --- a/samples/conversationBot/go.mod +++ b/samples/conversationBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/conversationBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/echoBot/go.mod b/samples/echoBot/go.mod index 9a4170c2..08afba25 100644 --- a/samples/echoBot/go.mod +++ b/samples/echoBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/echoBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/echoMultiBot/go.mod b/samples/echoMultiBot/go.mod index 68a691b8..0fee33cb 100644 --- a/samples/echoMultiBot/go.mod +++ b/samples/echoMultiBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/echoMultiBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/echoWebhookBot/go.mod b/samples/echoWebhookBot/go.mod index 93f3c4db..85dee761 100644 --- a/samples/echoWebhookBot/go.mod +++ b/samples/echoWebhookBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/echoWebhookBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/inlinequeryBot/go.mod b/samples/inlinequeryBot/go.mod index cd2b88da..3a1edf95 100644 --- a/samples/inlinequeryBot/go.mod +++ b/samples/inlinequeryBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/inlinequeryBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/metricsBot/go.mod b/samples/metricsBot/go.mod index 2a9ed775..b5510c52 100644 --- a/samples/metricsBot/go.mod +++ b/samples/metricsBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/metricsBot -go 1.19 +go 1.21 require ( github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/metricsBot/go.sum b/samples/metricsBot/go.sum index 5c4add19..61b79d2f 100644 --- a/samples/metricsBot/go.sum +++ b/samples/metricsBot/go.sum @@ -8,6 +8,7 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= diff --git a/samples/middlewareBot/go.mod b/samples/middlewareBot/go.mod index bc9d8b63..81a84002 100644 --- a/samples/middlewareBot/go.mod +++ b/samples/middlewareBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/middlewareBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/paymentsBot/go.mod b/samples/paymentsBot/go.mod index 55d489d7..a1067982 100644 --- a/samples/paymentsBot/go.mod +++ b/samples/paymentsBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/paymentsBot -go 1.19 +go 1.21 require ( github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/statefulClientBot/go.mod b/samples/statefulClientBot/go.mod index 1b3dcae0..65dcb7a1 100644 --- a/samples/statefulClientBot/go.mod +++ b/samples/statefulClientBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/statefulClientBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/samples/webappBot/go.mod b/samples/webappBot/go.mod index 5c5c26f0..1f7401eb 100644 --- a/samples/webappBot/go.mod +++ b/samples/webappBot/go.mod @@ -1,6 +1,6 @@ module github.com/PaulSonOfLars/gotgbot/samples/webappBot -go 1.19 +go 1.21 require github.com/PaulSonOfLars/gotgbot/v2 v2.99.99 diff --git a/scripts/ci/ensure-consistent-sample-mod-files.sh b/scripts/ci/ensure-consistent-sample-mod-files.sh index 6d528f26..992f6d2e 100755 --- a/scripts/ci/ensure-consistent-sample-mod-files.sh +++ b/scripts/ci/ensure-consistent-sample-mod-files.sh @@ -4,7 +4,7 @@ set -euo pipefail SAMPLES_DIR="samples" REPO="github.com/PaulSonOfLars/gotgbot" # Current library import path -GO_VERSION="1.19" # Go version we expect our samples to be using +GO_VERSION="1.21" # Go version we expect our samples to be using V_MAJOR="v2" # Current major version for the library V_DUMMY="v2.99.99" # dummy version for the library