Frontend logging: Remove Sentry javascript agent support (#67493)
* remove Sentry * fix sourcemap resolve
This commit is contained in:
@@ -185,7 +185,6 @@ type FrontendSettingsDTO struct {
|
||||
RendererVersion string `json:"rendererVersion"`
|
||||
SecretsManagerPluginEnabled bool `json:"secretsManagerPluginEnabled"`
|
||||
Http2Enabled bool `json:"http2Enabled"`
|
||||
Sentry setting.Sentry `json:"sentry"`
|
||||
GrafanaJavascriptAgent setting.GrafanaJavascriptAgent `json:"grafanaJavascriptAgent"`
|
||||
PluginCatalogURL string `json:"pluginCatalogURL"`
|
||||
PluginAdminEnabled bool `json:"pluginAdminEnabled"`
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"html/template"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/navtree"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type IndexViewData struct {
|
||||
@@ -27,7 +26,6 @@ type IndexViewData struct {
|
||||
FavIcon template.URL
|
||||
AppleTouchIcon template.URL
|
||||
AppTitle string
|
||||
Sentry *setting.Sentry
|
||||
ContentDeliveryURL string
|
||||
LoadingLogo template.URL
|
||||
CSPContent string
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/frontendlogging"
|
||||
@@ -16,51 +15,8 @@ var frontendLogger = log.New("frontend")
|
||||
|
||||
type frontendLogMessageHandler func(hs *HTTPServer, c *web.Context)
|
||||
|
||||
const sentryLogEndpointPath = "/log"
|
||||
const grafanaJavascriptAgentEndpointPath = "/log-grafana-javascript-agent"
|
||||
|
||||
/** @deprecated will be removed in the next major version */
|
||||
func NewFrontendLogMessageHandler(store *frontendlogging.SourceMapStore) frontendLogMessageHandler {
|
||||
return func(hs *HTTPServer, c *web.Context) {
|
||||
event := frontendlogging.FrontendSentryEvent{}
|
||||
if err := web.Bind(c.Req, &event); err != nil {
|
||||
c.Resp.WriteHeader(http.StatusBadRequest)
|
||||
_, err = c.Resp.Write([]byte("bad request data"))
|
||||
if err != nil {
|
||||
hs.log.Error("could not write to response", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var msg = "unknown"
|
||||
|
||||
if len(event.Message) > 0 {
|
||||
msg = event.Message
|
||||
} else if event.Exception != nil && len(event.Exception.Values) > 0 {
|
||||
msg = event.Exception.Values[0].FmtMessage()
|
||||
}
|
||||
|
||||
var ctx = event.ToLogContext(store)
|
||||
|
||||
switch event.Level {
|
||||
case sentry.LevelError:
|
||||
frontendLogger.Error(msg, ctx...)
|
||||
case sentry.LevelWarning:
|
||||
frontendLogger.Warn(msg, ctx...)
|
||||
case sentry.LevelDebug:
|
||||
frontendLogger.Debug(msg, ctx...)
|
||||
default:
|
||||
frontendLogger.Info(msg, ctx...)
|
||||
}
|
||||
|
||||
c.Resp.WriteHeader(http.StatusAccepted)
|
||||
_, err := c.Resp.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
hs.log.Error("could not write to response", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GrafanaJavascriptAgentLogMessageHandler(store *frontendlogging.SourceMapStore) frontendLogMessageHandler {
|
||||
return func(hs *HTTPServer, c *web.Context) {
|
||||
event := frontendlogging.FrontendGrafanaJavascriptAgentEvent{}
|
||||
@@ -143,9 +99,9 @@ func GrafanaJavascriptAgentLogMessageHandler(store *frontendlogging.SourceMapSto
|
||||
// this is to avoid reporting errors in case config was changes but there are browser
|
||||
// sessions still open with older config
|
||||
func (hs *HTTPServer) frontendLogEndpoints() web.Handler {
|
||||
if !(hs.Cfg.GrafanaJavascriptAgent.Enabled || hs.Cfg.Sentry.Enabled) {
|
||||
if !(hs.Cfg.GrafanaJavascriptAgent.Enabled) {
|
||||
return func(ctx *web.Context) {
|
||||
if ctx.Req.Method == http.MethodPost && (ctx.Req.URL.Path == sentryLogEndpointPath || ctx.Req.URL.Path == grafanaJavascriptAgentEndpointPath) {
|
||||
if ctx.Req.Method == http.MethodPost && ctx.Req.URL.Path == grafanaJavascriptAgentEndpointPath {
|
||||
ctx.Resp.WriteHeader(http.StatusAccepted)
|
||||
_, err := ctx.Resp.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
@@ -156,33 +112,11 @@ func (hs *HTTPServer) frontendLogEndpoints() web.Handler {
|
||||
}
|
||||
|
||||
sourceMapStore := frontendlogging.NewSourceMapStore(hs.Cfg, hs.pluginStaticRouteResolver, frontendlogging.ReadSourceMapFromFS)
|
||||
|
||||
var rateLimiter *rate.Limiter
|
||||
var handler frontendLogMessageHandler
|
||||
handlerEndpoint := ""
|
||||
dummyEndpoint := ""
|
||||
|
||||
if hs.Cfg.GrafanaJavascriptAgent.Enabled {
|
||||
rateLimiter = rate.NewLimiter(rate.Limit(hs.Cfg.GrafanaJavascriptAgent.EndpointRPS), hs.Cfg.GrafanaJavascriptAgent.EndpointBurst)
|
||||
handler = GrafanaJavascriptAgentLogMessageHandler(sourceMapStore)
|
||||
handlerEndpoint = grafanaJavascriptAgentEndpointPath
|
||||
dummyEndpoint = sentryLogEndpointPath
|
||||
} else {
|
||||
rateLimiter = rate.NewLimiter(rate.Limit(hs.Cfg.Sentry.EndpointRPS), hs.Cfg.Sentry.EndpointBurst)
|
||||
handler = NewFrontendLogMessageHandler(sourceMapStore)
|
||||
handlerEndpoint = sentryLogEndpointPath
|
||||
dummyEndpoint = grafanaJavascriptAgentEndpointPath
|
||||
}
|
||||
rateLimiter := rate.NewLimiter(rate.Limit(hs.Cfg.GrafanaJavascriptAgent.EndpointRPS), hs.Cfg.GrafanaJavascriptAgent.EndpointBurst)
|
||||
handler := GrafanaJavascriptAgentLogMessageHandler(sourceMapStore)
|
||||
|
||||
return func(ctx *web.Context) {
|
||||
if ctx.Req.Method == http.MethodPost && ctx.Req.URL.Path == dummyEndpoint {
|
||||
ctx.Resp.WriteHeader(http.StatusAccepted)
|
||||
_, err := ctx.Resp.Write([]byte("OK"))
|
||||
if err != nil {
|
||||
hs.log.Error("could not write to response", "err", err)
|
||||
}
|
||||
}
|
||||
if ctx.Req.Method == http.MethodPost && ctx.Req.URL.Path == handlerEndpoint {
|
||||
if ctx.Req.Method == http.MethodPost && ctx.Req.URL.Path == grafanaJavascriptAgentEndpointPath {
|
||||
if !rateLimiter.AllowN(time.Now(), 1) {
|
||||
ctx.Resp.WriteHeader(http.StatusTooManyRequests)
|
||||
return
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -30,79 +29,6 @@ type SourceMapReadRecord struct {
|
||||
|
||||
type logScenarioFunc func(c *scenarioContext, logs map[string]interface{}, sourceMapReads []SourceMapReadRecord)
|
||||
|
||||
func logSentryEventScenario(t *testing.T, desc string, event frontendlogging.FrontendSentryEvent, fn logScenarioFunc) {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
var logcontent = make(map[string]interface{})
|
||||
logcontent["logger"] = "frontend"
|
||||
newfrontendLogger := log.Logger(log.LoggerFunc(func(keyvals ...interface{}) error {
|
||||
for i := 0; i < len(keyvals); i += 2 {
|
||||
logcontent[keyvals[i].(string)] = keyvals[i+1]
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
origHandler := frontendLogger.GetLogger()
|
||||
frontendLogger.Swap(level.NewFilter(newfrontendLogger, level.AllowInfo()))
|
||||
sourceMapReads := []SourceMapReadRecord{}
|
||||
|
||||
t.Cleanup(func() {
|
||||
frontendLogger.Swap(origHandler)
|
||||
})
|
||||
|
||||
sc := setupScenarioContext(t, "/log")
|
||||
|
||||
cdnRootURL, e := url.Parse("https://storage.googleapis.com/grafana-static-assets")
|
||||
require.NoError(t, e)
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
StaticRootPath: "/staticroot",
|
||||
CDNRootURL: cdnRootURL,
|
||||
}
|
||||
|
||||
readSourceMap := func(dir string, path string) ([]byte, error) {
|
||||
sourceMapReads = append(sourceMapReads, SourceMapReadRecord{
|
||||
dir: dir,
|
||||
path: path,
|
||||
})
|
||||
if strings.Contains(path, "error") {
|
||||
return nil, errors.New("epic hard drive failure")
|
||||
}
|
||||
if strings.HasSuffix(path, "foo.js.map") {
|
||||
f, err := os.ReadFile("./frontendlogging/test-data/foo.js.map")
|
||||
require.NoError(t, err)
|
||||
return f, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// fake plugin route so we will try to find a source map there
|
||||
pm := fakePluginStaticRouteResolver{
|
||||
routes: []*plugins.StaticRoute{
|
||||
{
|
||||
Directory: "/usr/local/telepathic-panel",
|
||||
PluginID: "telepathic",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
sourceMapStore := frontendlogging.NewSourceMapStore(cfg, &pm, readSourceMap)
|
||||
|
||||
loggingHandler := NewFrontendLogMessageHandler(sourceMapStore)
|
||||
|
||||
handler := routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
|
||||
sc.context = c
|
||||
c.Req.Body = mockRequestBody(event)
|
||||
c.Req.Header.Add("Content-Type", "application/json")
|
||||
loggingHandler(nil, c.Context)
|
||||
return response.Success("ok")
|
||||
})
|
||||
|
||||
sc.m.Post(sc.url, handler)
|
||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||
fn(sc, logcontent, sourceMapReads)
|
||||
})
|
||||
}
|
||||
|
||||
func logGrafanaJavascriptAgentEventScenario(t *testing.T, desc string, event frontendlogging.FrontendGrafanaJavascriptAgentEvent, fn logScenarioFunc) {
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
var logcontent = make(map[string]interface{})
|
||||
@@ -176,216 +102,6 @@ func logGrafanaJavascriptAgentEventScenario(t *testing.T, desc string, event fro
|
||||
})
|
||||
}
|
||||
|
||||
func TestFrontendLoggingEndpointSentry(t *testing.T) {
|
||||
ts, err := time.Parse("2006-01-02T15:04:05.000Z", "2020-10-22T06:29:29.078Z")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("FrontendLoggingEndpoint", func(t *testing.T) {
|
||||
request := sentry.Request{
|
||||
URL: "http://localhost:3000/",
|
||||
Headers: map[string]string{
|
||||
"User-Agent": "Chrome",
|
||||
},
|
||||
}
|
||||
|
||||
user := sentry.User{
|
||||
Email: "geralt@kaermorhen.com",
|
||||
ID: "45",
|
||||
}
|
||||
|
||||
event := sentry.Event{
|
||||
EventID: "123",
|
||||
Level: sentry.LevelError,
|
||||
Request: &request,
|
||||
Timestamp: ts,
|
||||
}
|
||||
|
||||
errorEvent := frontendlogging.FrontendSentryEvent{
|
||||
Event: &event,
|
||||
Exception: &frontendlogging.FrontendSentryException{
|
||||
Values: []frontendlogging.FrontendSentryExceptionValue{
|
||||
{
|
||||
Type: "UserError",
|
||||
Value: "Please replace user and try again",
|
||||
Stacktrace: sentry.Stacktrace{
|
||||
Frames: []sentry.Frame{
|
||||
{
|
||||
Function: "foofn",
|
||||
Filename: "foo.js",
|
||||
Lineno: 123,
|
||||
Colno: 23,
|
||||
},
|
||||
{
|
||||
Function: "barfn",
|
||||
Filename: "bar.js",
|
||||
Lineno: 113,
|
||||
Colno: 231,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
logSentryEventScenario(t, "Should log received error event", errorEvent,
|
||||
func(sc *scenarioContext, logs map[string]interface{}, sourceMapReads []SourceMapReadRecord) {
|
||||
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
||||
assertContextContains(t, logs, "logger", "frontend")
|
||||
assertContextContains(t, logs, "url", errorEvent.Request.URL)
|
||||
assertContextContains(t, logs, "user_agent", errorEvent.Request.Headers["User-Agent"])
|
||||
assertContextContains(t, logs, "event_id", errorEvent.EventID)
|
||||
assertContextContains(t, logs, "original_timestamp", errorEvent.Timestamp)
|
||||
assertContextContains(t, logs, "stacktrace", `UserError: Please replace user and try again
|
||||
at foofn (foo.js:123:23)
|
||||
at barfn (bar.js:113:231)`)
|
||||
assert.NotContains(t, logs, "context")
|
||||
})
|
||||
|
||||
messageEvent := frontendlogging.FrontendSentryEvent{
|
||||
Event: &sentry.Event{
|
||||
EventID: "123",
|
||||
Level: sentry.LevelInfo,
|
||||
Request: &request,
|
||||
Timestamp: ts,
|
||||
Message: "hello world",
|
||||
User: user,
|
||||
},
|
||||
Exception: nil,
|
||||
}
|
||||
|
||||
logSentryEventScenario(t, "Should log received message event", messageEvent,
|
||||
func(sc *scenarioContext, logs map[string]interface{}, sourceMapReads []SourceMapReadRecord) {
|
||||
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
||||
assert.Len(t, logs, 10)
|
||||
assertContextContains(t, logs, "logger", "frontend")
|
||||
assertContextContains(t, logs, "msg", "hello world")
|
||||
assertContextContains(t, logs, level.Key().(string), level.InfoValue())
|
||||
assertContextContains(t, logs, "logger", "frontend")
|
||||
assertContextContains(t, logs, "url", messageEvent.Request.URL)
|
||||
assertContextContains(t, logs, "user_agent", messageEvent.Request.Headers["User-Agent"])
|
||||
assertContextContains(t, logs, "event_id", messageEvent.EventID)
|
||||
assertContextContains(t, logs, "original_timestamp", messageEvent.Timestamp)
|
||||
assert.NotContains(t, logs, "stacktrace")
|
||||
assert.NotContains(t, logs, "context")
|
||||
assertContextContains(t, logs, "user_email", user.Email)
|
||||
assertContextContains(t, logs, "user_id", user.ID)
|
||||
})
|
||||
|
||||
eventWithContext := frontendlogging.FrontendSentryEvent{
|
||||
Event: &sentry.Event{
|
||||
EventID: "123",
|
||||
Level: sentry.LevelInfo,
|
||||
Request: &request,
|
||||
Timestamp: ts,
|
||||
Message: "hello world",
|
||||
User: user,
|
||||
Contexts: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"one": "two",
|
||||
"three": 4,
|
||||
},
|
||||
"bar": "baz",
|
||||
},
|
||||
},
|
||||
Exception: nil,
|
||||
}
|
||||
|
||||
logSentryEventScenario(t, "Should log event context", eventWithContext,
|
||||
func(sc *scenarioContext, logs map[string]interface{}, sourceMapReads []SourceMapReadRecord) {
|
||||
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
||||
assertContextContains(t, logs, "context_foo_one", "two")
|
||||
assertContextContains(t, logs, "context_foo_three", "4")
|
||||
assertContextContains(t, logs, "context_bar", "baz")
|
||||
})
|
||||
|
||||
errorEventForSourceMapping := frontendlogging.FrontendSentryEvent{
|
||||
Event: &event,
|
||||
Exception: &frontendlogging.FrontendSentryException{
|
||||
Values: []frontendlogging.FrontendSentryExceptionValue{
|
||||
{
|
||||
Type: "UserError",
|
||||
Value: "Please replace user and try again",
|
||||
Stacktrace: sentry.Stacktrace{
|
||||
Frames: []sentry.Frame{
|
||||
{
|
||||
Function: "foofn",
|
||||
Filename: "http://localhost:3000/public/build/moo/foo.js", // source map found and mapped, core
|
||||
Lineno: 2,
|
||||
Colno: 5,
|
||||
},
|
||||
{
|
||||
Function: "foofn",
|
||||
Filename: "http://localhost:3000/public/plugins/telepathic/foo.js", // plugin, source map found and mapped
|
||||
Lineno: 3,
|
||||
Colno: 10,
|
||||
},
|
||||
{
|
||||
Function: "explode",
|
||||
Filename: "http://localhost:3000/public/build/error.js", // reading source map throws error
|
||||
Lineno: 3,
|
||||
Colno: 10,
|
||||
},
|
||||
{
|
||||
Function: "wat",
|
||||
Filename: "http://localhost:3000/public/build/bar.js", // core, but source map not found on fs
|
||||
Lineno: 3,
|
||||
Colno: 10,
|
||||
},
|
||||
{
|
||||
Function: "nope",
|
||||
Filename: "http://localhost:3000/baz.js", // not core or plugin, wont even attempt to get source map
|
||||
Lineno: 3,
|
||||
Colno: 10,
|
||||
},
|
||||
{
|
||||
Function: "fake",
|
||||
Filename: "http://localhost:3000/public/build/../../secrets.txt", // path will be sanitized
|
||||
Lineno: 3,
|
||||
Colno: 10,
|
||||
},
|
||||
{
|
||||
Function: "cdn",
|
||||
Filename: "https://storage.googleapis.com/grafana-static-assets/grafana-oss/pre-releases/7.5.0-11925pre/public/build/foo.js", // source map found and mapped
|
||||
Lineno: 3,
|
||||
Colno: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
logSentryEventScenario(t, "Should load sourcemap and transform stacktrace line when possible",
|
||||
errorEventForSourceMapping, func(sc *scenarioContext, logs map[string]interface{}, sourceMapReads []SourceMapReadRecord) {
|
||||
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
||||
assert.Len(t, logs, 9)
|
||||
assertContextContains(t, logs, "stacktrace", `UserError: Please replace user and try again
|
||||
at ? (core|webpack:///./some_source.ts:2:2)
|
||||
at ? (telepathic|webpack:///./some_source.ts:3:2)
|
||||
at explode (http://localhost:3000/public/build/error.js:3:10)
|
||||
at wat (http://localhost:3000/public/build/bar.js:3:10)
|
||||
at nope (http://localhost:3000/baz.js:3:10)
|
||||
at fake (http://localhost:3000/public/build/../../secrets.txt:3:10)
|
||||
at ? (core|webpack:///./some_source.ts:3:2)`)
|
||||
assert.Len(t, sourceMapReads, 6)
|
||||
assert.Equal(t, "/staticroot", sourceMapReads[0].dir)
|
||||
assert.Equal(t, "build/moo/foo.js.map", sourceMapReads[0].path)
|
||||
assert.Equal(t, "/usr/local/telepathic-panel", sourceMapReads[1].dir)
|
||||
assert.Equal(t, "/foo.js.map", sourceMapReads[1].path)
|
||||
assert.Equal(t, "/staticroot", sourceMapReads[2].dir)
|
||||
assert.Equal(t, "build/error.js.map", sourceMapReads[2].path)
|
||||
assert.Equal(t, "/staticroot", sourceMapReads[3].dir)
|
||||
assert.Equal(t, "build/bar.js.map", sourceMapReads[3].path)
|
||||
assert.Equal(t, "/staticroot", sourceMapReads[4].dir)
|
||||
assert.Equal(t, "secrets.txt.map", sourceMapReads[4].path)
|
||||
assert.Equal(t, "/staticroot", sourceMapReads[5].dir)
|
||||
assert.Equal(t, "build/foo.js.map", sourceMapReads[5].path)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestFrontendLoggingEndpointGrafanaJavascriptAgent(t *testing.T) {
|
||||
ts, err := time.Parse("2006-01-02T15:04:05.000Z", "2020-10-22T06:29:29.078Z")
|
||||
require.NoError(t, err)
|
||||
@@ -536,13 +252,13 @@ func TestFrontendLoggingEndpointGrafanaJavascriptAgent(t *testing.T) {
|
||||
func(sc *scenarioContext, logs map[string]interface{}, sourceMapReads []SourceMapReadRecord) {
|
||||
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
||||
assertContextContains(t, logs, "stacktrace", `UserError: Please replace user and try again
|
||||
at ? (webpack:///./some_source.ts:2:2)
|
||||
at ? (webpack:///./some_source.ts:3:2)
|
||||
at ? (core|webpack:///./some_source.ts:2:2)
|
||||
at ? (telepathic|webpack:///./some_source.ts:3:2)
|
||||
at explode (http://localhost:3000/public/build/error.js:3:10)
|
||||
at wat (http://localhost:3000/public/build/bar.js:3:10)
|
||||
at nope (http://localhost:3000/baz.js:3:10)
|
||||
at fake (http://localhost:3000/public/build/../../secrets.txt:3:10)
|
||||
at ? (webpack:///./some_source.ts:3:2)`)
|
||||
at ? (core|webpack:///./some_source.ts:3:2)`)
|
||||
assert.Len(t, sourceMapReads, 6)
|
||||
assert.Equal(t, "/staticroot", sourceMapReads[0].dir)
|
||||
assert.Equal(t, "build/moo/foo.js.map", sourceMapReads[0].path)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CtxVector []interface{}
|
||||
|
||||
type FrontendGrafanaJavascriptAgentEvent struct {
|
||||
Exceptions []Exception `json:"exceptions,omitempty"`
|
||||
Logs []Log `json:"logs,omitempty"`
|
||||
|
||||
@@ -1,33 +1,5 @@
|
||||
package frontendlogging
|
||||
|
||||
// ResolveSourceLocation resolves minified source location to original source location
|
||||
func ResolveSourceLocation(store *SourceMapStore, frame *Frame) (*Frame, error) {
|
||||
smap, err := store.getSourceMap(frame.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if smap == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
file, function, line, col, ok := smap.consumer.Source(frame.Lineno, frame.Colno)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// unfortunately in many cases go-sourcemap fails to determine the original function name.
|
||||
// not a big issue as long as file, line and column are correct
|
||||
if len(function) == 0 {
|
||||
function = "?"
|
||||
}
|
||||
return &Frame{
|
||||
Filename: file,
|
||||
Lineno: line,
|
||||
Colno: col,
|
||||
Function: function,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TransformException will attempt to resolved all monified source locations in the stacktrace with original source locations
|
||||
func TransformException(ex *Exception, store *SourceMapStore) *Exception {
|
||||
if ex.Stacktrace == nil {
|
||||
@@ -37,7 +9,7 @@ func TransformException(ex *Exception, store *SourceMapStore) *Exception {
|
||||
|
||||
for _, frame := range ex.Stacktrace.Frames {
|
||||
frame := frame
|
||||
mappedFrame, err := ResolveSourceLocation(store, &frame)
|
||||
mappedFrame, err := store.resolveSourceLocation(frame)
|
||||
if err != nil {
|
||||
frames = append(frames, frame)
|
||||
} else if mappedFrame != nil {
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package frontendlogging
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
type CtxVector []interface{}
|
||||
|
||||
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 {
|
||||
stacktraces := make([]string, 0, len(exception.Values))
|
||||
for _, value := range exception.Values {
|
||||
stacktraces = append(stacktraces, value.FmtStacktrace(store))
|
||||
}
|
||||
return strings.Join(stacktraces, "\n\n")
|
||||
}
|
||||
|
||||
func addEventContextToLogContext(rootPrefix string, logCtx *CtxVector, eventCtx map[string]interface{}) {
|
||||
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:
|
||||
*logCtx = append(*logCtx, prefix, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
|
||||
if event.Exception != nil {
|
||||
ctx = append(ctx, "stacktrace", event.Exception.FmtStacktraces(store))
|
||||
}
|
||||
addEventContextToLogContext("context", &ctx, event.Contexts)
|
||||
if len(event.User.Email) > 0 {
|
||||
ctx = append(ctx, "user_email", event.User.Email, "user_id", event.User.ID)
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -9,13 +9,14 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
sourcemap "github.com/go-sourcemap/sourcemap"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var logger = log.New("frontendlogging")
|
||||
|
||||
type sourceMapLocation struct {
|
||||
dir string
|
||||
path string
|
||||
@@ -136,7 +137,7 @@ func (store *SourceMapStore) getSourceMap(sourceURL string) (*sourceMap, error)
|
||||
return smap, nil
|
||||
}
|
||||
|
||||
func (store *SourceMapStore) resolveSourceLocation(frame sentry.Frame) (*sentry.Frame, error) {
|
||||
func (store *SourceMapStore) resolveSourceLocation(frame Frame) (*Frame, error) {
|
||||
smap, err := store.getSourceMap(frame.Filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -157,7 +158,7 @@ func (store *SourceMapStore) resolveSourceLocation(frame sentry.Frame) (*sentry.
|
||||
if len(smap.pluginID) > 0 {
|
||||
module = smap.pluginID
|
||||
}
|
||||
return &sentry.Frame{
|
||||
return &Frame{
|
||||
Filename: file,
|
||||
Lineno: line,
|
||||
Colno: col,
|
||||
|
||||
@@ -184,7 +184,6 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
||||
RendererVersion: hs.RenderService.Version(),
|
||||
SecretsManagerPluginEnabled: secretsManagerPluginEnabled,
|
||||
Http2Enabled: hs.Cfg.Protocol == setting.HTTP2Scheme,
|
||||
Sentry: hs.Cfg.Sentry,
|
||||
GrafanaJavascriptAgent: hs.Cfg.GrafanaJavascriptAgent,
|
||||
PluginCatalogURL: hs.Cfg.PluginCatalogURL,
|
||||
PluginAdminEnabled: hs.Cfg.PluginAdminEnabled,
|
||||
|
||||
@@ -136,7 +136,6 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
|
||||
AppleTouchIcon: "public/img/apple-touch-icon.png",
|
||||
AppTitle: "Grafana",
|
||||
NavTree: navTree,
|
||||
Sentry: &hs.Cfg.Sentry,
|
||||
Nonce: c.RequestNonce,
|
||||
ContentDeliveryURL: hs.Cfg.GetContentDeliveryURL(hs.License.ContentDeliveryPrefix()),
|
||||
LoadingLogo: "public/img/grafana_icon.svg",
|
||||
|
||||
Reference in New Issue
Block a user