Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Example of Recover followed by panic #241

Merged
merged 1 commit into from
Jun 4, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
125 changes: 125 additions & 0 deletions example/recover-repanic/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// This is an example program that demonstrates an advanced use of the Sentry
// SDK using Hub, Scope and EventProcessor to recover from runtime panics,
// report to Sentry filtering specific frames from the stack trace and then
// letting the program crash as usual.
//
// Try it by running:
//
// go run main.go
//
// To actually report events to Sentry, set the DSN either by editing the
// appropriate line below or setting the environment variable SENTRY_DSN to
// match the DSN of your Sentry project.
package main

import (
"fmt"
"log"
"math/rand"
"strings"
"sync"
"time"

"github.com/getsentry/sentry-go"
)

func main() {
err := sentry.Init(sentry.ClientOptions{
// Either set your DSN here or set the SENTRY_DSN environment variable.
Dsn: "",
// Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out.
Debug: true,
// This is an optional function with access to the event before it is
// sent to Sentry. The event can be mutated, or sending can be aborted
// by returning nil.
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { return event },
})
if err != nil {
log.Fatalf("sentry.Init: %s", err)
}
// Flush buffered events before the program terminates.
// Set the timeout to the maximum duration the program can afford to wait.
defer sentry.Flush(2 * time.Second)

rand.Seed(time.Now().UnixNano())

nWorkers := 8
var wg sync.WaitGroup

// Run some worker goroutines to simulate work.
for i := 0; i < nWorkers; i++ {
wg.Add(1)
go RecoverRepanic(func() {
defer wg.Done()
// Sleep to simulate some work.
time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
// Intentionally access an index out of bounds to trigger a runtime
// panic.
fmt.Println(make([]int, 3)[3])
})
}

wg.Wait()
}

// RecoverRepanic calls f and, in case of a runtime panic, reports the panic to
// Sentry and repanics.
func RecoverRepanic(f func()) {
// Clone the current hub so that modifications of the scope are visible only
// within this function.
hub := sentry.CurrentHub().Clone()

// filterFrames removes frames from outgoing events that reference the
// RecoverRepanic function and its subfunctions.
filterFrames := func(event *sentry.Event) {
for _, e := range event.Exception {
if e.Stacktrace == nil {
continue
}
frames := e.Stacktrace.Frames[:0]
for _, frame := range e.Stacktrace.Frames {
if frame.Module == "main" && strings.HasPrefix(frame.Function, "RecoverRepanic") {
continue
}
frames = append(frames, frame)
}
e.Stacktrace.Frames = frames
}
}

// Add an EventProcessor to the scope. The event processor is a function
// that can change events before they are sent to Sentry.
// Alternatively, see also ClientOptions.BeforeSend, which is a special
// event processor applied to all events.
hub.ConfigureScope(func(scope *sentry.Scope) {
scope.AddEventProcessor(func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
filterFrames(event)
return event
})
})

// See https://golang.org/ref/spec#Handling_panics.
// This will recover from runtime panics and then panic again after
// reporting to Sentry.
defer func() {
if x := recover(); x != nil {
hub.Recover(x)
// Because the goroutine running this code is going to crash the
// program, call Flush to send the event to Sentry before it is too
// late. Set the timeout to an appropriate value depending on your
// program, what is the maximum time to wait before giving up and
// dropping the event.
hub.Flush(2 * time.Second)
// Note that if multiple goroutines panic, possibly only the first
// one to call Flush will succeed in sending the event. If you want
// to capture multiple panics and still crash the program
// afterwards, you need to coordinate error reporting and
// termination differently.
panic(x)
}
}()

// Run the original function.
f()
}