/
main.go
138 lines (126 loc) · 4.6 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// 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 func() {
// Note that wg.Done() must be outside of RecoverRepanic, otherwise
// it would unblock wg.Wait() before RecoverRepanic is done doing
// its job (reporting panic to Sentry).
defer wg.Done()
RecoverRepanic(func() {
// Sleep to simulate some work.
//#nosec G404 -- We are fine using transparent, non-secure value here.
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.
//
// Note that if RecoverRepanic is called from multiple goroutines and they panic
// concurrently, then the repanic initiated from RecoverRepanic, unless handled
// further down the call stack, will cause the program to crash without waiting
// for other goroutines to finish their work. That means that most likely only
// the first panic will be successfully reported to Sentry.
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 error 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 {
// Create an event and enqueue it for reporting.
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. The value 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()
}