This repository has been archived by the owner on Mar 11, 2021. It is now read-only.
/
sentry.go
126 lines (109 loc) · 3.04 KB
/
sentry.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
package sentry
import (
"context"
"fmt"
"github.com/fabric8-services/fabric8-auth/authorization/token/manager"
"github.com/fabric8-services/fabric8-auth/log"
"github.com/getsentry/raven-go"
goajwt "github.com/goadesign/goa/middleware/security/jwt"
)
// client encapsulates client to Sentry service
// also has mutex which controls access to the client
type client struct {
c *raven.Client
sendErr chan func()
}
var (
sentryClient *client
)
// Sentry returns client declared inside package
func Sentry() *client {
return sentryClient
}
// InitializeSentryClient initializes sentry client. This function returns
// function that can be used to close the sentry client and error.
func InitializeSentryClient(sentryDSN string, options ...func(*client)) (func(), error) {
c, err := raven.New(sentryDSN)
if err != nil {
return nil, err
}
sentryClient = &client{
c: c,
sendErr: make(chan func()),
}
// set all options passed by user
for _, opt := range options {
opt(sentryClient)
}
// wait on errors to be sent on channel of client object
go sentryClient.loop()
return func() {
close(sentryClient.sendErr)
}, nil
}
// WithRelease helps you set release/commit of currently running
// code while initializing sentry client using function InitializeSentryClient
func WithRelease(release string) func(*client) {
return func(c *client) {
c.c.SetRelease(release)
}
}
// WithEnvironment helps you set environment the deployed code is
// running in while initializing sentry client using function
// InitializeSentryClient
func WithEnvironment(env string) func(*client) {
return func(c *client) {
c.c.SetEnvironment(env)
}
}
// waits on functions to be sent on channel
// which are then executed
func (c *client) loop() {
for op := range c.sendErr {
op()
}
}
// CaptureError sends error 'err' to Sentry, meanwhile also sets user
// information by extracting user information from the context provided
func (c *client) CaptureError(ctx context.Context, err error) {
// if method called during test which has uninitialized client
if c == nil {
return
}
// Extract user information. Ignoring error here but then before using the
// object user make sure to check if it wasn't nil.
user, _ := extractUserInfo(ctx)
reqID := log.ExtractRequestID(ctx)
c.sendErr <- func() {
if user != nil {
c.c.SetUserContext(user)
}
additionalContext := make(map[string]string)
if reqID != "" {
additionalContext["req_ID"] = reqID
}
c.c.CaptureError(err, additionalContext)
c.c.ClearContext()
}
}
// extractUserInfo reads the context and returns sentry understandable
// user object's reference and error
func extractUserInfo(ctx context.Context) (*raven.User, error) {
m, err := manager.ReadTokenManagerFromContext(ctx)
if err != nil {
return nil, err
}
token := goajwt.ContextJWT(ctx)
if token == nil {
return nil, fmt.Errorf("no token found in context")
}
t, err := m.ParseToken(ctx, token.Raw)
if err != nil {
return nil, err
}
return &raven.User{
Username: t.Username,
Email: t.Email,
ID: t.Subject,
}, nil
}