feat: add Sentry error reporting
Capture command errors and panics with Sentry SDK. DSN injected at build time via ldflags (dev builds silent, releases report). Opt-out: UNARR_NO_TELEMETRY=1.
This commit is contained in:
parent
6e07e82d51
commit
3d6142a62e
7 changed files with 120 additions and 2 deletions
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/torrentclaw/torrentclaw-cli/internal/config"
|
||||
"github.com/torrentclaw/torrentclaw-cli/internal/sentry"
|
||||
tc "github.com/torrentclaw/go-client"
|
||||
)
|
||||
|
||||
|
|
@ -144,6 +145,14 @@ Source: https://github.com/torrentclaw/torrentclaw-cli`,
|
|||
// Execute runs the root command.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
// Report to Sentry with command context
|
||||
command := ""
|
||||
if cmd, _, cerr := rootCmd.Find(os.Args[1:]); cerr == nil && cmd != nil && cmd != rootCmd {
|
||||
command = cmd.Name()
|
||||
}
|
||||
sentry.CaptureError(err, command)
|
||||
sentry.Close() // Flush before os.Exit (defers don't run after os.Exit)
|
||||
|
||||
fmt.Fprintln(os.Stderr, color.RedString("Error: %s", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
@ -164,6 +173,11 @@ func loadConfig() config.Config {
|
|||
|
||||
appCfg.ApplyEnvOverrides()
|
||||
cfgLoaded = true
|
||||
|
||||
if appCfg.Agent.ID != "" {
|
||||
sentry.SetUser(appCfg.Agent.ID)
|
||||
}
|
||||
|
||||
return appCfg
|
||||
}
|
||||
|
||||
|
|
|
|||
85
internal/sentry/sentry.go
Normal file
85
internal/sentry/sentry.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package sentry
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gosentry "github.com/getsentry/sentry-go"
|
||||
)
|
||||
|
||||
// dsn is injected at build time via ldflags. If empty, Sentry is disabled.
|
||||
// Set via: -ldflags "-X github.com/torrentclaw/torrentclaw-cli/internal/sentry.dsn=..."
|
||||
var dsn string
|
||||
|
||||
const flushTimeout = 2 * time.Second
|
||||
|
||||
// Init initializes the Sentry SDK. Call Close() on shutdown to flush events.
|
||||
// No-op if telemetry is disabled (UNARR_NO_TELEMETRY=1).
|
||||
func Init(version string) {
|
||||
if dsn == "" || os.Getenv("UNARR_NO_TELEMETRY") == "1" {
|
||||
return
|
||||
}
|
||||
|
||||
err := gosentry.Init(gosentry.ClientOptions{
|
||||
Dsn: dsn,
|
||||
Release: "unarr@" + version,
|
||||
Environment: environment(version),
|
||||
AttachStacktrace: true,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gosentry.ConfigureScope(func(scope *gosentry.Scope) {
|
||||
scope.SetTag("os", runtime.GOOS)
|
||||
scope.SetTag("arch", runtime.GOARCH)
|
||||
scope.SetTag("go_version", runtime.Version())
|
||||
})
|
||||
}
|
||||
|
||||
// Close flushes pending events with a timeout.
|
||||
func Close() {
|
||||
gosentry.Flush(flushTimeout)
|
||||
}
|
||||
|
||||
// CaptureError sends a non-fatal error to Sentry with optional command context.
|
||||
func CaptureError(err error, command string) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
gosentry.WithScope(func(scope *gosentry.Scope) {
|
||||
if command != "" {
|
||||
scope.SetTag("command", command)
|
||||
}
|
||||
gosentry.CaptureException(err)
|
||||
})
|
||||
}
|
||||
|
||||
// RecoverPanic captures a panic and re-panics after reporting.
|
||||
// Usage: defer sentry.RecoverPanic()
|
||||
func RecoverPanic() {
|
||||
if r := recover(); r != nil {
|
||||
gosentry.CurrentHub().Recover(r)
|
||||
gosentry.Flush(flushTimeout)
|
||||
|
||||
// Re-panic so the user sees the stack trace and the process exits non-zero
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
|
||||
// SetUser sets the user context (agent ID) for all subsequent events.
|
||||
func SetUser(agentID string) {
|
||||
gosentry.ConfigureScope(func(scope *gosentry.Scope) {
|
||||
scope.SetUser(gosentry.User{ID: agentID})
|
||||
})
|
||||
}
|
||||
|
||||
func environment(version string) string {
|
||||
if version == "" || version == "dev" || strings.HasSuffix(version, "-dev") {
|
||||
return "development"
|
||||
}
|
||||
return "production"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue