REPOSITORY / ScuroNeko/Laniakea
Wiki
(doc): Sentry Observer integration wiki page (EN/RU)
Cross-linked from Error-Handling and Recipes; not bundled as a package since Sentry tagging/noise filtering is opinionated enough that users will want to adjust it rather than depend on it.
@@ -115,4 +115,5 @@ Internal-only ошибки идут по тому же logger path, но не с
|
||||
|
||||
- [[Logging-RU]]
|
||||
- [[Middleware-RU]]
|
||||
- [[Sentry-Integration-RU]]
|
||||
- [[Error-Handling]]
|
||||
|
||||
@@ -230,3 +230,4 @@ plugin.NewPayload(func(ctx *laniakea.MessageContext, db *App) error {
|
||||
- [[Middleware]]
|
||||
- [[Logging]]
|
||||
- [[Commands-and-Plugins]]
|
||||
- [[Sentry-Integration]]
|
||||
|
||||
@@ -59,4 +59,5 @@ Recipes хороши как стартовые шаблоны, но не зам
|
||||
- [[Getting-Started-RU]]
|
||||
- [[Commands-and-Plugins-RU]]
|
||||
- [[Localization-RU]]
|
||||
- [[Sentry-Integration-RU]]
|
||||
- [[Recipes]]
|
||||
|
||||
+1
@@ -157,3 +157,4 @@ This is most useful when you want payload-type mismatches to fail loudly instead
|
||||
- [[Inline-Keyboards-and-Payloads]]
|
||||
- [[Drafts]]
|
||||
- [[Localization]]
|
||||
- [[Sentry-Integration]]
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Sentry Integration RU
|
||||
|
||||
English version: [[Sentry-Integration]]
|
||||
|
||||
Это краткая русскоязычная версия страницы про интеграцию с Sentry. Полная и наиболее актуальная страница: [[Sentry-Integration]].
|
||||
|
||||
## Идея
|
||||
|
||||
Laniakea намеренно не поставляет готовую интеграцию с Sentry. Точка расширения уже есть — интерфейс `Observer` (см. [[Error-Handling]]): в него прилетают все классифицированные ошибки из handler'ов, сцен, runner'ов и policy-проверок вместе с контекстом (plugin, handler kind, chat/user ID, update ID). Вызов observer'а panic-safe, так что упавший экспортёр не уронит бота.
|
||||
|
||||
Тегирование и фильтрация шума — вещи специфичные под конкретный бот, поэтому такой код лучше держать у себя, а не тянуть `sentry-go` в зависимости библиотеки.
|
||||
|
||||
## Что отправлять
|
||||
|
||||
- `OnError` — срабатывает на каждую ошибку из центрального потока handler/scene. Пропускай `IsUserError(err)` — это ожидаемые ошибки, уже показанные пользователю, а не баги.
|
||||
- `OnPolicyChecked` — срабатывает на каждую policy-проверку, включая обычные отказы "не разрешено". В Sentry стоит слать только `Internal`-ошибки (неправильная конфигурация, упавший запрос к API).
|
||||
- `OnPollingRetry` — сам по себе не инцидент, но полезен как breadcrumb перед реальной ошибкой.
|
||||
|
||||
## Пример
|
||||
|
||||
Полный листинг — на странице [[Sentry-Integration]]. Кратко: реализуешь `laniakea.Observer`, у большинства методов пустое тело, а `OnError`/`OnPolicyChecked`/`OnPollingRetry` вызывают `sentry.CaptureException(...)` через `sentry.GetHubFromContext(ctx)`, добавляя теги (`plugin`, `handler_kind`, `handler_name`, `update_type`) и `chat_id`/`from_id`/`update_id` в `scope.SetContext(...)`.
|
||||
|
||||
## Подключение
|
||||
|
||||
```go
|
||||
sentry.Init(sentry.ClientOptions{Dsn: dsn})
|
||||
defer sentry.Flush(2 * time.Second)
|
||||
|
||||
bot.SetObserver(observability.SentryObserver{})
|
||||
```
|
||||
|
||||
`SetObserver(...)` полностью заменяет observer (см. [[Bot-Options-and-Configuration]]). Если нужно несколько получателей (Sentry + метрики + логирование), пиши небольшой fan-out `Observer`, оборачивая вызов каждого дочернего observer'а своим `recover()`, чтобы паника в одном не блокировала остальных для того же события.
|
||||
|
||||
## Что читать дальше
|
||||
|
||||
- [[Error-Handling-RU]]
|
||||
- [[Recipes-RU]]
|
||||
- [[Sentry-Integration]]
|
||||
@@ -0,0 +1,117 @@
|
||||
# Sentry Integration
|
||||
|
||||
Russian version: [[Sentry-Integration-RU]]
|
||||
|
||||
Laniakea does not ship a Sentry integration, and intentionally so — see [[Error-Handling]] for the classified-error model and [[Runners]] / [[Policies]] for the other event sources. The `Observer` interface is already the extension point for this: it receives every classified handler, scene, runner, and policy error with rich context (plugin, handler kind, chat/user IDs, update ID), and dispatch is panic-safe, so a failing exporter cannot crash the bot.
|
||||
|
||||
This page is a copy-paste starting point, not a bundled package. Sentry tagging conventions and noise filtering are opinionated enough that you'll likely want to adjust them for your bot, so keeping this out of the module avoids forcing `sentry-go` into every consumer's dependency graph for a shape that rarely fits as-is.
|
||||
|
||||
## What to report
|
||||
|
||||
- `OnError` fires for every error routed through the centralized handler/scene error flow. Skip `IsUserError(err)` results — those are expected, already answered in chat, and not bugs.
|
||||
- `OnPolicyChecked` fires for every policy evaluation, including ordinary "not allowed" denials. Only the `Internal` ones (misconfiguration, failed API lookups) are worth reporting.
|
||||
- `OnPollingRetry` is not an incident by itself, but makes a good breadcrumb leading up to a later error.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package observability
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
|
||||
laniakea "git.scuroneko.dev/scuroneko/laniakea"
|
||||
)
|
||||
|
||||
// SentryObserver forwards unexpected framework errors to Sentry. All hooks
|
||||
// besides OnError, OnPolicyChecked and OnPollingRetry are no-ops — they
|
||||
// exist only to satisfy laniakea.Observer.
|
||||
type SentryObserver struct{}
|
||||
|
||||
var _ laniakea.Observer = SentryObserver{}
|
||||
|
||||
func (SentryObserver) OnUpdateReceived(context.Context, laniakea.UpdateReceivedEvent) {}
|
||||
func (SentryObserver) OnUpdateHandled(context.Context, laniakea.UpdateHandledEvent) {}
|
||||
func (SentryObserver) OnHandlerStarted(context.Context, laniakea.HandlerStartedEvent) {}
|
||||
func (SentryObserver) OnHandlerFinished(context.Context, laniakea.HandlerFinishedEvent) {}
|
||||
func (SentryObserver) OnSceneTransition(context.Context, laniakea.SceneTransitionEvent) {}
|
||||
func (SentryObserver) OnRunnerFinished(context.Context, laniakea.RunnerFinishedEvent) {}
|
||||
|
||||
// OnPollingRetry leaves a breadcrumb instead of an event — a single retry
|
||||
// isn't an incident, but it's useful context if a real error follows.
|
||||
func (SentryObserver) OnPollingRetry(ctx context.Context, e laniakea.PollingRetryEvent) {
|
||||
sentry.GetHubFromContext(ctx).AddBreadcrumb(&sentry.Breadcrumb{
|
||||
Category: "polling",
|
||||
Message: e.Err.Error(),
|
||||
Level: sentry.LevelWarning,
|
||||
Data: map[string]any{
|
||||
"attempt": e.Attempt,
|
||||
"delay": e.Delay.String(),
|
||||
},
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// OnPolicyChecked reports only internal policy failures (misconfiguration,
|
||||
// failed API lookups) — a plain "not allowed" denial is expected behavior.
|
||||
func (SentryObserver) OnPolicyChecked(ctx context.Context, e laniakea.PolicyCheckedEvent) {
|
||||
if e.Passed || e.Err == nil || !e.Internal {
|
||||
return
|
||||
}
|
||||
captureLaniakeaError(ctx, e.Err, map[string]string{
|
||||
"source": "policy",
|
||||
"policy": e.Name,
|
||||
"plugin": e.Plugin,
|
||||
}, e.FromID, e.ChatID, 0)
|
||||
}
|
||||
|
||||
// OnError is the main entry point: every handler/scene/runner error routed
|
||||
// through the framework's classified-error flow lands here.
|
||||
func (SentryObserver) OnError(ctx context.Context, e laniakea.ErrorEvent) {
|
||||
if e.Err == nil || laniakea.IsUserError(e.Err) {
|
||||
return // already surfaced to the user via ctx.Error, not a bug report
|
||||
}
|
||||
captureLaniakeaError(ctx, e.Err, map[string]string{
|
||||
"source": "handler",
|
||||
"plugin": e.Plugin,
|
||||
"handler_kind": string(e.HandlerKind),
|
||||
"handler_name": e.HandlerName,
|
||||
"update_type": string(e.UpdateType),
|
||||
}, e.FromID, e.ChatID, e.UpdateID)
|
||||
}
|
||||
|
||||
func captureLaniakeaError(ctx context.Context, err error, tags map[string]string, fromID, chatID int64, updateID int) {
|
||||
hub := sentry.GetHubFromContext(ctx)
|
||||
if hub == nil {
|
||||
hub = sentry.CurrentHub().Clone()
|
||||
}
|
||||
hub.WithScope(func(scope *sentry.Scope) {
|
||||
scope.SetTags(tags)
|
||||
scope.SetContext("telegram", map[string]any{
|
||||
"chat_id": chatID,
|
||||
"from_id": fromID,
|
||||
"update_id": updateID,
|
||||
})
|
||||
hub.CaptureException(err)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Wiring it up
|
||||
|
||||
```go
|
||||
sentry.Init(sentry.ClientOptions{Dsn: dsn})
|
||||
defer sentry.Flush(2 * time.Second)
|
||||
|
||||
bot.SetObserver(observability.SentryObserver{})
|
||||
```
|
||||
|
||||
`bot.SetObserver(...)` replaces the observer wholesale — see [[Bot-Options-and-Configuration]]. If you need Sentry alongside metrics or logging instrumentation, write a small fan-out `Observer` that dispatches to several sub-observers, each wrapped in its own `recover()` so a panic in one exporter does not skip the others for the same event.
|
||||
|
||||
## Related pages
|
||||
|
||||
- [[Error-Handling]]
|
||||
- [[Recipes]]
|
||||
- [[Runners]]
|
||||
- [[Policies]]
|
||||
+1
@@ -30,6 +30,7 @@
|
||||
|
||||
## Practical Guides
|
||||
- [[Recipes]]
|
||||
- [[Sentry-Integration]]
|
||||
- [[Testing-Bots-with-Laniakea]]
|
||||
|
||||
## Maintenance
|
||||
|
||||
Reference in New Issue
Block a user