1package frontendlogging 2 3import ( 4 "fmt" 5 "strings" 6 7 "github.com/getsentry/sentry-go" 8 "github.com/grafana/grafana/pkg/infra/log" 9 "github.com/inconshreveable/log15" 10) 11 12var logger = log.New("frontendlogging") 13 14type FrontendSentryExceptionValue struct { 15 Value string `json:"value,omitempty"` 16 Type string `json:"type,omitempty"` 17 Stacktrace sentry.Stacktrace `json:"stacktrace,omitempty"` 18} 19 20type FrontendSentryException struct { 21 Values []FrontendSentryExceptionValue `json:"values,omitempty"` 22} 23 24type FrontendSentryEvent struct { 25 *sentry.Event 26 Exception *FrontendSentryException `json:"exception,omitempty"` 27} 28 29func (value *FrontendSentryExceptionValue) FmtMessage() string { 30 return fmt.Sprintf("%s: %s", value.Type, value.Value) 31} 32 33func fmtLine(frame sentry.Frame) string { 34 module := "" 35 if len(frame.Module) > 0 { 36 module = frame.Module + "|" 37 } 38 return fmt.Sprintf("\n at %s (%s%s:%v:%v)", frame.Function, module, frame.Filename, frame.Lineno, frame.Colno) 39} 40 41func (value *FrontendSentryExceptionValue) FmtStacktrace(store *SourceMapStore) string { 42 var stacktrace = value.FmtMessage() 43 for _, frame := range value.Stacktrace.Frames { 44 mappedFrame, err := store.resolveSourceLocation(frame) 45 if err != nil { 46 logger.Error("Error resolving stack trace frame source location", "err", err) 47 stacktrace += fmtLine(frame) // even if reading source map fails for unexpected reason, still better to log compiled location than nothing at all 48 } else { 49 if mappedFrame != nil { 50 stacktrace += fmtLine(*mappedFrame) 51 } else { 52 stacktrace += fmtLine(frame) 53 } 54 } 55 } 56 return stacktrace 57} 58 59func (exception *FrontendSentryException) FmtStacktraces(store *SourceMapStore) string { 60 var stacktraces []string 61 for _, value := range exception.Values { 62 stacktraces = append(stacktraces, value.FmtStacktrace(store)) 63 } 64 return strings.Join(stacktraces, "\n\n") 65} 66 67func addEventContextToLogContext(rootPrefix string, logCtx log15.Ctx, eventCtx map[string]interface{}) { 68 for key, element := range eventCtx { 69 prefix := fmt.Sprintf("%s_%s", rootPrefix, key) 70 switch v := element.(type) { 71 case map[string]interface{}: 72 addEventContextToLogContext(prefix, logCtx, v) 73 default: 74 logCtx[prefix] = fmt.Sprintf("%v", v) 75 } 76 } 77} 78 79func (event *FrontendSentryEvent) ToLogContext(store *SourceMapStore) log15.Ctx { 80 var ctx = make(log15.Ctx) 81 ctx["url"] = event.Request.URL 82 ctx["user_agent"] = event.Request.Headers["User-Agent"] 83 ctx["event_id"] = event.EventID 84 ctx["original_timestamp"] = event.Timestamp 85 if event.Exception != nil { 86 ctx["stacktrace"] = event.Exception.FmtStacktraces(store) 87 } 88 addEventContextToLogContext("context", ctx, event.Contexts) 89 if len(event.User.Email) > 0 { 90 ctx["user_email"] = event.User.Email 91 ctx["user_id"] = event.User.ID 92 } 93 94 return ctx 95} 96