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