FILE / ScuroNeko/Laniakea
observer.go
Исходный файл и его история в репозитории.
(fix): harden concurrent lifecycle (tests): add regression coverage (doc): update v1.2 guidance
289 lines
9.5 KiB
Go
289 lines
9.5 KiB
Go
package laniakea
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
|
|
)
|
|
|
|
// HandlerEventKind identifies the kind of handler observed by runtime events.
|
|
type HandlerEventKind string
|
|
|
|
const (
|
|
// HandlerCommandKind identifies a command handler.
|
|
HandlerCommandKind HandlerEventKind = "command"
|
|
// HandlerMessageKind identifies a message fallback handler.
|
|
HandlerMessageKind HandlerEventKind = "message"
|
|
// HandlerMiddlewareKind identifies middleware execution.
|
|
HandlerMiddlewareKind HandlerEventKind = "middleware"
|
|
// HandlerPayloadKind identifies a callback payload handler.
|
|
HandlerPayloadKind HandlerEventKind = "payload"
|
|
// HandlerUpdateKind identifies a generic update handler.
|
|
HandlerUpdateKind HandlerEventKind = "update"
|
|
// HandlerRunnerKind identifies a background runner execution.
|
|
HandlerRunnerKind HandlerEventKind = "runner"
|
|
// HandlerPollingKind identifies polling and getUpdates runtime work.
|
|
HandlerPollingKind HandlerEventKind = "polling"
|
|
// HandlerSceneKind identifies a scene runtime handler wrapper.
|
|
HandlerSceneKind HandlerEventKind = "scene"
|
|
// HandlerSceneStepKind identifies a scene step handler.
|
|
HandlerSceneStepKind HandlerEventKind = "scene_step"
|
|
// HandlerSceneCommandKind identifies a scene-local command handler.
|
|
HandlerSceneCommandKind HandlerEventKind = "scene_command"
|
|
// HandlerScenePayloadKind identifies a scene-local callback payload handler.
|
|
HandlerScenePayloadKind HandlerEventKind = "scene_payload"
|
|
// HandlerSceneMessageKind identifies a scene message fallback handler.
|
|
HandlerSceneMessageKind HandlerEventKind = "scene_message"
|
|
)
|
|
|
|
// Event is the marker interface implemented by all observer runtime events.
|
|
type Event interface {
|
|
isEvent()
|
|
}
|
|
|
|
func emitContextError(ctx *MessageContext, event ErrorEvent) {
|
|
if ctx == nil {
|
|
return
|
|
}
|
|
if ctx.Logger != nil {
|
|
ctx.Logger.Errorln(event.Err)
|
|
}
|
|
if ctx.eventEmitter != nil {
|
|
ctx.eventEmitter(ctx.Context(), event)
|
|
return
|
|
}
|
|
if ctx.observer == nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil && ctx.Logger != nil {
|
|
ctx.Logger.Errorln(fmt.Sprintf("panic in observer: %v", recovered))
|
|
}
|
|
}()
|
|
ctx.observer.OnError(ctx.Context(), event)
|
|
}
|
|
|
|
// UpdateReceivedEvent describes an update entering the bot runtime.
|
|
type UpdateReceivedEvent struct {
|
|
// UpdateID identifies the Telegram update.
|
|
UpdateID int
|
|
// UpdateType identifies the normalized update kind.
|
|
UpdateType tgapi.UpdateType
|
|
// FromID identifies the originating user when available.
|
|
FromID int64
|
|
// ChatID identifies the originating chat when available.
|
|
ChatID int64
|
|
}
|
|
|
|
// UpdateHandledEvent describes a completed update execution path.
|
|
type UpdateHandledEvent struct {
|
|
// UpdateID identifies the Telegram update.
|
|
UpdateID int
|
|
// UpdateType identifies the normalized update kind.
|
|
UpdateType tgapi.UpdateType
|
|
// FromID identifies the originating user when available.
|
|
FromID int64
|
|
// ChatID identifies the originating chat when available.
|
|
ChatID int64
|
|
// Duration is the total framework handling time.
|
|
Duration time.Duration
|
|
// Handled reports whether a registered path handled the update.
|
|
Handled bool
|
|
}
|
|
|
|
// HandlerStartedEvent describes a handler about to execute.
|
|
type HandlerStartedEvent struct {
|
|
// UpdateID identifies the Telegram update.
|
|
UpdateID int
|
|
// UpdateType identifies the normalized update kind.
|
|
UpdateType tgapi.UpdateType
|
|
// Plugin names the plugin that owns the handler.
|
|
Plugin string
|
|
// HandlerKind classifies the handler.
|
|
HandlerKind HandlerEventKind
|
|
// HandlerName identifies the handler within its plugin and kind.
|
|
HandlerName string
|
|
// FromID identifies the originating user when available.
|
|
FromID int64
|
|
// ChatID identifies the originating chat when available.
|
|
ChatID int64
|
|
}
|
|
|
|
// HandlerFinishedEvent describes a handler that has completed.
|
|
type HandlerFinishedEvent struct {
|
|
// UpdateID identifies the Telegram update.
|
|
UpdateID int
|
|
// UpdateType identifies the normalized update kind.
|
|
UpdateType tgapi.UpdateType
|
|
// Plugin names the plugin that owns the handler.
|
|
Plugin string
|
|
// HandlerKind classifies the handler.
|
|
HandlerKind HandlerEventKind
|
|
// HandlerName identifies the handler within its plugin and kind.
|
|
HandlerName string
|
|
// FromID identifies the originating user when available.
|
|
FromID int64
|
|
// ChatID identifies the originating chat when available.
|
|
ChatID int64
|
|
// Duration is the handler execution time.
|
|
Duration time.Duration
|
|
// Err is the error returned or recovered from the handler.
|
|
Err error
|
|
// UserFacing reports whether Err is safe to show to the user.
|
|
UserFacing bool
|
|
}
|
|
|
|
// SceneTransitionEvent describes a scene state transition.
|
|
type SceneTransitionEvent struct {
|
|
// Plugin names the plugin that owns the scene.
|
|
Plugin string
|
|
// Scene names the transitioning scene.
|
|
Scene string
|
|
// From is the previous scene step.
|
|
From string
|
|
// To is the resulting scene step.
|
|
To string
|
|
// Action identifies the requested state transition.
|
|
Action SceneAction
|
|
// FromID identifies the session user when available.
|
|
FromID int64
|
|
// ChatID identifies the session chat when available.
|
|
ChatID int64
|
|
}
|
|
|
|
// PolicyCheckedEvent describes the result of a policy evaluation.
|
|
type PolicyCheckedEvent struct {
|
|
// Name identifies the evaluated policy.
|
|
Name string
|
|
// Plugin names the plugin that requested the policy check.
|
|
Plugin string
|
|
// FromID identifies the evaluated user when available.
|
|
FromID int64
|
|
// ChatID identifies the evaluated chat when available.
|
|
ChatID int64
|
|
// Passed reports whether the policy accepted the context.
|
|
Passed bool
|
|
// Err is the policy evaluation error, if any.
|
|
Err error
|
|
// Internal reports whether evaluation failed internally rather than denying access.
|
|
Internal bool
|
|
}
|
|
|
|
// RunnerFinishedEvent describes a completed background runner execution.
|
|
type RunnerFinishedEvent struct {
|
|
// Name identifies the runner.
|
|
Name string
|
|
// Duration is the callback execution time.
|
|
Duration time.Duration
|
|
// Err is the callback error or recovered panic.
|
|
Err error
|
|
}
|
|
|
|
// PollingRetryEvent describes a polling retry after a failed getUpdates call.
|
|
type PollingRetryEvent struct {
|
|
// Attempt is the one-based retry number.
|
|
Attempt int
|
|
// Delay is the time before the next polling attempt.
|
|
Delay time.Duration
|
|
// Err is the polling error that triggered the retry.
|
|
Err error
|
|
}
|
|
|
|
// ErrorEvent describes an error routed through framework error handling.
|
|
type ErrorEvent struct {
|
|
// UpdateID identifies the Telegram update when available.
|
|
UpdateID int
|
|
// UpdateType identifies the normalized update kind when available.
|
|
UpdateType tgapi.UpdateType
|
|
// Plugin names the component that reported the error.
|
|
Plugin string
|
|
// HandlerKind classifies the failing handler or runtime component.
|
|
HandlerKind HandlerEventKind
|
|
// HandlerName identifies the failing handler within its kind.
|
|
HandlerName string
|
|
// FromID identifies the originating user when available.
|
|
FromID int64
|
|
// ChatID identifies the originating chat when available.
|
|
ChatID int64
|
|
// Err is the reported error.
|
|
Err error
|
|
// UserFacing reports whether Err is safe to show to the user.
|
|
UserFacing bool
|
|
}
|
|
|
|
func (UpdateReceivedEvent) isEvent() {}
|
|
func (UpdateHandledEvent) isEvent() {}
|
|
func (HandlerStartedEvent) isEvent() {}
|
|
func (HandlerFinishedEvent) isEvent() {}
|
|
func (SceneTransitionEvent) isEvent() {}
|
|
func (PolicyCheckedEvent) isEvent() {}
|
|
func (RunnerFinishedEvent) isEvent() {}
|
|
func (PollingRetryEvent) isEvent() {}
|
|
func (ErrorEvent) isEvent() {}
|
|
|
|
// Observer receives best-effort runtime instrumentation events.
|
|
//
|
|
// During RunWithContext and RunWebhookWithContext, callbacks execute on a
|
|
// dedicated dispatcher goroutine in enqueue order and never block update
|
|
// handlers. The queue is bounded; overload drops events and emits sampled
|
|
// warnings. Runtime shutdown cancels callback contexts and drains queued events;
|
|
// Bot.Close returns ErrObserverShutdownTimeout if a callback ignores cancellation.
|
|
type Observer interface {
|
|
OnUpdateReceived(ctx context.Context, event UpdateReceivedEvent)
|
|
OnUpdateHandled(ctx context.Context, event UpdateHandledEvent)
|
|
OnHandlerStarted(ctx context.Context, event HandlerStartedEvent)
|
|
OnHandlerFinished(ctx context.Context, event HandlerFinishedEvent)
|
|
OnSceneTransition(ctx context.Context, event SceneTransitionEvent)
|
|
OnPolicyChecked(ctx context.Context, event PolicyCheckedEvent)
|
|
OnRunnerFinished(ctx context.Context, event RunnerFinishedEvent)
|
|
OnPollingRetry(ctx context.Context, event PollingRetryEvent)
|
|
OnError(ctx context.Context, event ErrorEvent)
|
|
}
|
|
|
|
func (bot *Bot[T]) safeEmitEvent(ctx context.Context, event Event) {
|
|
if bot.observer == nil {
|
|
return
|
|
}
|
|
if bot.observerAsync != nil {
|
|
bot.observerAsync.enqueue(ctx, event)
|
|
return
|
|
}
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if bot.logger != nil {
|
|
bot.logger.Errorln(fmt.Sprintf("panic in observer: %v", r))
|
|
}
|
|
}
|
|
}()
|
|
bot.emitEvent(ctx, event)
|
|
}
|
|
|
|
func (bot *Bot[T]) emitEvent(ctx context.Context, event Event) {
|
|
emitObserverEvent(bot.observer, ctx, event)
|
|
}
|
|
|
|
func emitObserverEvent(observer Observer, ctx context.Context, event Event) {
|
|
switch e := event.(type) {
|
|
case UpdateReceivedEvent:
|
|
observer.OnUpdateReceived(ctx, e)
|
|
case UpdateHandledEvent:
|
|
observer.OnUpdateHandled(ctx, e)
|
|
case HandlerStartedEvent:
|
|
observer.OnHandlerStarted(ctx, e)
|
|
case HandlerFinishedEvent:
|
|
observer.OnHandlerFinished(ctx, e)
|
|
case SceneTransitionEvent:
|
|
observer.OnSceneTransition(ctx, e)
|
|
case PolicyCheckedEvent:
|
|
observer.OnPolicyChecked(ctx, e)
|
|
case RunnerFinishedEvent:
|
|
observer.OnRunnerFinished(ctx, e)
|
|
case PollingRetryEvent:
|
|
observer.OnPollingRetry(ctx, e)
|
|
case ErrorEvent:
|
|
observer.OnError(ctx, e)
|
|
}
|
|
}
|