| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | package frontendlogging | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-11-29 17:18:01 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/getsentry/sentry-go" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 22:28:05 +08:00
										 |  |  | type CtxVector []interface{} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | var logger = log.New("frontendlogging") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type FrontendSentryExceptionValue struct { | 
					
						
							|  |  |  | 	Value      string            `json:"value,omitempty"` | 
					
						
							|  |  |  | 	Type       string            `json:"type,omitempty"` | 
					
						
							|  |  |  | 	Stacktrace sentry.Stacktrace `json:"stacktrace,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type FrontendSentryException struct { | 
					
						
							|  |  |  | 	Values []FrontendSentryExceptionValue `json:"values,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type FrontendSentryEvent struct { | 
					
						
							|  |  |  | 	*sentry.Event | 
					
						
							|  |  |  | 	Exception *FrontendSentryException `json:"exception,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (value *FrontendSentryExceptionValue) FmtMessage() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("%s: %s", value.Type, value.Value) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func fmtLine(frame sentry.Frame) string { | 
					
						
							|  |  |  | 	module := "" | 
					
						
							|  |  |  | 	if len(frame.Module) > 0 { | 
					
						
							|  |  |  | 		module = frame.Module + "|" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return fmt.Sprintf("\n  at %s (%s%s:%v:%v)", frame.Function, module, frame.Filename, frame.Lineno, frame.Colno) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (value *FrontendSentryExceptionValue) FmtStacktrace(store *SourceMapStore) string { | 
					
						
							|  |  |  | 	var stacktrace = value.FmtMessage() | 
					
						
							|  |  |  | 	for _, frame := range value.Stacktrace.Frames { | 
					
						
							|  |  |  | 		mappedFrame, err := store.resolveSourceLocation(frame) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			logger.Error("Error resolving stack trace frame source location", "err", err) | 
					
						
							|  |  |  | 			stacktrace += fmtLine(frame) // even if reading source map fails for unexpected reason, still better to log compiled location than nothing at all
 | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			if mappedFrame != nil { | 
					
						
							|  |  |  | 				stacktrace += fmtLine(*mappedFrame) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				stacktrace += fmtLine(frame) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return stacktrace | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (exception *FrontendSentryException) FmtStacktraces(store *SourceMapStore) string { | 
					
						
							|  |  |  | 	var stacktraces []string | 
					
						
							|  |  |  | 	for _, value := range exception.Values { | 
					
						
							|  |  |  | 		stacktraces = append(stacktraces, value.FmtStacktrace(store)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return strings.Join(stacktraces, "\n\n") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 22:28:05 +08:00
										 |  |  | func addEventContextToLogContext(rootPrefix string, logCtx *CtxVector, eventCtx map[string]interface{}) { | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | 	for key, element := range eventCtx { | 
					
						
							|  |  |  | 		prefix := fmt.Sprintf("%s_%s", rootPrefix, key) | 
					
						
							|  |  |  | 		switch v := element.(type) { | 
					
						
							|  |  |  | 		case map[string]interface{}: | 
					
						
							|  |  |  | 			addEventContextToLogContext(prefix, logCtx, v) | 
					
						
							|  |  |  | 		default: | 
					
						
							| 
									
										
										
										
											2022-01-06 22:28:05 +08:00
										 |  |  | 			*logCtx = append(*logCtx, prefix, fmt.Sprintf("%v", v)) | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-06 22:28:05 +08:00
										 |  |  | func (event *FrontendSentryEvent) ToLogContext(store *SourceMapStore) []interface{} { | 
					
						
							|  |  |  | 	var ctx = CtxVector{"url", event.Request.URL, "user_agent", event.Request.Headers["User-Agent"], | 
					
						
							|  |  |  | 		"event_id", event.EventID, "original_timestamp", event.Timestamp} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | 	if event.Exception != nil { | 
					
						
							| 
									
										
										
										
											2022-01-06 22:28:05 +08:00
										 |  |  | 		ctx = append(ctx, "stacktrace", event.Exception.FmtStacktraces(store)) | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-01-06 22:28:05 +08:00
										 |  |  | 	addEventContextToLogContext("context", &ctx, event.Contexts) | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | 	if len(event.User.Email) > 0 { | 
					
						
							| 
									
										
										
										
											2022-01-06 22:28:05 +08:00
										 |  |  | 		ctx = append(ctx, "user_email", event.User.Email, "user_id", event.User.ID) | 
					
						
							| 
									
										
										
										
											2021-02-04 14:37:28 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ctx | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-29 17:18:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (event *FrontendSentryEvent) MarshalJSON() ([]byte, error) { | 
					
						
							|  |  |  | 	eventJSON, err := json.Marshal(event.Event) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	exceptionJSON, err := json.Marshal(map[string]interface{}{"exception": event.Exception}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	exceptionJSON[0] = ',' | 
					
						
							|  |  |  | 	return append(eventJSON[:len(eventJSON)-1], exceptionJSON...), nil | 
					
						
							|  |  |  | } |