github.com/blend/go-sdk@v1.20220411.3/sentry/client.go (about) 1 /* 2 3 Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package sentry 9 10 import ( 11 "context" 12 "fmt" 13 "net" 14 "net/http" 15 "os" 16 "runtime" 17 18 raven "github.com/blend/sentry-go" 19 20 "github.com/blend/go-sdk/env" 21 "github.com/blend/go-sdk/ex" 22 "github.com/blend/go-sdk/logger" 23 "github.com/blend/go-sdk/webutil" 24 ) 25 26 var ( 27 _ Sender = (*Client)(nil) 28 ) 29 30 // MustNew returns a new client and panics on error. 31 func MustNew(cfg Config) *Client { 32 c, err := New(cfg) 33 if err != nil { 34 panic(err) 35 } 36 return c 37 } 38 39 // New returns a new client. 40 func New(cfg Config) (*Client, error) { 41 rc, err := raven.NewClient( 42 raven.ClientOptions{ 43 Dsn: cfg.DSN, 44 Environment: cfg.Environment, 45 ServerName: cfg.ServerName, 46 Dist: cfg.Dist, 47 Release: cfg.Release, 48 Debug: cfg.Debug, 49 DebugWriter: os.Stderr, 50 }, 51 ) 52 if err != nil { 53 return nil, err 54 } 55 return &Client{ 56 Config: cfg, 57 Client: rc, 58 }, nil 59 } 60 61 // Client is a wrapper for the sentry-go client. 62 type Client struct { 63 Config Config 64 Client *raven.Client 65 } 66 67 // Notify sends a notification. 68 func (c Client) Notify(ctx context.Context, ee logger.ErrorEvent) { 69 c.Client.CaptureEvent(errEvent(ctx, ee), nil, raven.NewScope()) 70 c.Client.Flush(c.Config.FlushTimeoutOrDefault()) // goose this a bit 71 } 72 73 func errEvent(ctx context.Context, ee logger.ErrorEvent) *raven.Event { 74 exceptions := []raven.Exception{ 75 { 76 Type: ex.ErrClass(ee.Err).Error(), 77 Value: ex.ErrMessage(ee.Err), 78 Stacktrace: errStackTrace(ee.Err), 79 }, 80 } 81 82 var innerErr error 83 for innerErr = ex.ErrInner(ee.Err); innerErr != nil; innerErr = ex.ErrInner(innerErr) { 84 rex := raven.Exception{ 85 Type: ex.ErrClass(innerErr).Error(), 86 Value: ex.ErrMessage(innerErr), 87 } 88 if st := errStackTrace(innerErr); st != nil && len(st.Frames) > 0 { 89 rex.Stacktrace = st 90 } 91 exceptions = append(exceptions, rex) 92 } 93 94 event := &raven.Event{ 95 Title: ex.ErrClass(ee.Err).Error(), 96 Timestamp: logger.GetEventTimestamp(ctx, ee), 97 Fingerprint: errFingerprint(ctx, ex.ErrClass(ee.Err).Error()), 98 Level: raven.Level(ee.GetFlag()), 99 Tags: errTags(ctx), 100 Extra: errExtra(ctx), 101 Platform: "go", 102 Contexts: map[string]interface{}{ 103 "device": map[string]interface{}{ 104 "arch": runtime.GOARCH, 105 "num_cpu": runtime.NumCPU(), 106 }, 107 "os": map[string]interface{}{ 108 "name": runtime.GOOS, 109 }, 110 "runtime": map[string]interface{}{ 111 "name": "go", 112 "version": runtime.Version(), 113 "go_numroutines": runtime.NumGoroutine(), 114 "go_maxprocs": runtime.GOMAXPROCS(0), 115 "go_numcgocalls": runtime.NumCgoCall(), 116 }, 117 }, 118 Sdk: raven.SdkInfo{ 119 Name: SDK, 120 Version: raven.Version, 121 Packages: []raven.SdkPackage{{ 122 Name: SDK, 123 Version: raven.Version, 124 }}, 125 }, 126 Request: errRequest(ee), 127 Message: ex.ErrClass(ee.Err).Error(), 128 Exception: exceptions, 129 } 130 131 // Set contextual information preserving existing data. For each context, if 132 // the existing value is not of type map[string]interface{}, then no 133 // additional information is added. 134 if deviceContext, ok := event.Contexts["device"].(map[string]interface{}); ok { 135 if _, ok := deviceContext["arch"]; !ok { 136 deviceContext["arch"] = runtime.GOARCH 137 } 138 if _, ok := deviceContext["num_cpu"]; !ok { 139 deviceContext["num_cpu"] = runtime.NumCPU() 140 } 141 } 142 if osContext, ok := event.Contexts["os"].(map[string]interface{}); ok { 143 if _, ok := osContext["name"]; !ok { 144 osContext["name"] = runtime.GOOS 145 } 146 } 147 if runtimeContext, ok := event.Contexts["runtime"].(map[string]interface{}); ok { 148 if _, ok := runtimeContext["name"]; !ok { 149 runtimeContext["name"] = "go" 150 } 151 if _, ok := runtimeContext["version"]; !ok { 152 runtimeContext["version"] = runtime.Version() 153 } 154 if _, ok := runtimeContext["go_numroutines"]; !ok { 155 runtimeContext["go_numroutines"] = runtime.NumGoroutine() 156 } 157 if _, ok := runtimeContext["go_maxprocs"]; !ok { 158 runtimeContext["go_maxprocs"] = runtime.GOMAXPROCS(0) 159 } 160 if _, ok := runtimeContext["go_numcgocalls"]; !ok { 161 runtimeContext["go_numcgocalls"] = runtime.NumCgoCall() 162 } 163 } 164 165 return event 166 } 167 168 func errFingerprint(ctx context.Context, extra ...string) []string { 169 if fingerprint := GetFingerprint(ctx); fingerprint != nil { 170 return fingerprint 171 } 172 return extra 173 } 174 175 func errTags(ctx context.Context) map[string]string { 176 labels := logger.GetLabels(ctx) 177 if labels == nil { 178 labels = make(map[string]string) 179 } 180 if hostname := env.GetVars(ctx).Hostname(); hostname != "" { 181 labels["hostname"] = hostname 182 } 183 return labels 184 } 185 186 func errExtra(ctx context.Context) map[string]interface{} { 187 return logger.GetAnnotations(ctx) 188 } 189 190 func errRequest(ee logger.ErrorEvent) *raven.Request { 191 if ee.State == nil { 192 return new(raven.Request) 193 } 194 typed, ok := ee.State.(*http.Request) 195 if !ok { 196 return &raven.Request{} 197 } 198 199 return newRavenRequest(typed) 200 } 201 202 func newRavenRequest(r *http.Request) *raven.Request { 203 protocol := webutil.SchemeHTTP 204 if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { 205 protocol = webutil.SchemeHTTPS 206 } 207 url := fmt.Sprintf("%s://%s%s", protocol, r.Host, r.URL.Path) 208 headers := make(map[string]string) 209 headers["Host"] = r.Host 210 var env map[string]string 211 if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil { 212 env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port} 213 } 214 return &raven.Request{ 215 URL: url, 216 Method: r.Method, 217 QueryString: r.URL.RawQuery, 218 Headers: headers, 219 Env: env, 220 } 221 } 222 223 func errStackTrace(err error) *raven.Stacktrace { 224 if err != nil { 225 frames := errFrames(err) 226 if len(frames) > 0 { 227 return &raven.Stacktrace{ 228 Frames: errFrames(err), 229 } 230 } 231 return nil 232 } 233 return nil 234 } 235 236 func errFrames(err error) []raven.Frame { 237 stacktrace := ex.ErrStackTrace(err) 238 if stacktrace == nil { 239 return nil 240 } 241 pointers, ok := stacktrace.(ex.StackPointers) 242 if !ok { 243 return nil 244 } 245 246 var output []raven.Frame 247 runtimeFrames := runtime.CallersFrames(pointers) 248 for { 249 callerFrame, more := runtimeFrames.Next() 250 // append in reverse order ... 251 output = append([]raven.Frame{ 252 raven.NewFrame(callerFrame), 253 }, output...) 254 if !more { 255 break 256 } 257 } 258 return output 259 }