mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 20:24:18 -06:00
299 lines
10 KiB
Go
299 lines
10 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-kit/log"
|
|
"github.com/go-kit/log/level"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/api/frontendlogging"
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
type SourceMapReadRecord struct {
|
|
dir string
|
|
path string
|
|
}
|
|
|
|
type logScenarioFunc func(c *scenarioContext, logs map[string]any, sourceMapReads []SourceMapReadRecord)
|
|
|
|
func logGrafanaJavascriptAgentEventScenario(t *testing.T, desc string, event frontendlogging.FrontendGrafanaJavascriptAgentEvent, fn logScenarioFunc) {
|
|
t.Run(desc, func(t *testing.T) {
|
|
var logcontent = make(map[string]any)
|
|
logcontent["logger"] = "frontend"
|
|
newfrontendLogger := log.Logger(log.LoggerFunc(func(keyvals ...any) 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-grafana-javascript-agent")
|
|
|
|
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 := GrafanaJavascriptAgentLogMessageHandler(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 TestFrontendLoggingEndpointGrafanaJavascriptAgent(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("FrontendLoggingEndpointGrafanaJavascriptAgent", func(t *testing.T) {
|
|
user := frontendlogging.User{
|
|
Email: "test@example.com",
|
|
ID: "45",
|
|
}
|
|
meta := frontendlogging.Meta{
|
|
User: user,
|
|
Page: frontendlogging.Page{
|
|
URL: "http://localhost:3000/dashboard/db/test",
|
|
},
|
|
}
|
|
|
|
errorEvent := frontendlogging.FrontendGrafanaJavascriptAgentEvent{
|
|
Meta: meta,
|
|
Exceptions: []frontendlogging.Exception{
|
|
{
|
|
Type: "UserError",
|
|
Value: "Please replace user and try again\n at foofn (foo.js:123:23)\n at barfn (bar.js:113:231)",
|
|
Stacktrace: &frontendlogging.Stacktrace{
|
|
Frames: []frontendlogging.Frame{{
|
|
Function: "bla",
|
|
Filename: "http://localhost:3000/public/build/foo.js",
|
|
Lineno: 20,
|
|
Colno: 30,
|
|
},
|
|
},
|
|
},
|
|
Timestamp: ts,
|
|
},
|
|
},
|
|
}
|
|
|
|
logGrafanaJavascriptAgentEventScenario(t, "Should log received error event", errorEvent,
|
|
func(sc *scenarioContext, logs map[string]any, sourceMapReads []SourceMapReadRecord) {
|
|
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
|
assertContextContains(t, logs, "logger", "frontend")
|
|
assertContextContains(t, logs, "page_url", errorEvent.Meta.Page.URL)
|
|
assertContextContains(t, logs, "user_email", errorEvent.Meta.User.Email)
|
|
assertContextContains(t, logs, "user_id", errorEvent.Meta.User.ID)
|
|
assertContextContains(t, logs, "original_timestamp", errorEvent.Exceptions[0].Timestamp)
|
|
assertContextContains(t, logs, "msg", `UserError: Please replace user and try again
|
|
at foofn (foo.js:123:23)
|
|
at barfn (bar.js:113:231)`)
|
|
assert.NotContains(t, logs, "context")
|
|
})
|
|
|
|
logEvent := frontendlogging.FrontendGrafanaJavascriptAgentEvent{
|
|
Meta: meta,
|
|
Logs: []frontendlogging.Log{{
|
|
Message: "This is a test log message",
|
|
Timestamp: ts,
|
|
LogLevel: "info",
|
|
}},
|
|
}
|
|
|
|
logGrafanaJavascriptAgentEventScenario(t, "Should log received log event", logEvent,
|
|
func(sc *scenarioContext, logs map[string]any, sourceMapReads []SourceMapReadRecord) {
|
|
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
|
assert.Len(t, logs, 11)
|
|
assertContextContains(t, logs, "logger", "frontend")
|
|
assertContextContains(t, logs, "msg", "This is a test log message")
|
|
assertContextContains(t, logs, "original_log_level", frontendlogging.LogLevel("info"))
|
|
assertContextContains(t, logs, "original_timestamp", ts)
|
|
assert.NotContains(t, logs, "stacktrace")
|
|
assert.NotContains(t, logs, "context")
|
|
})
|
|
|
|
logEventWithContext := frontendlogging.FrontendGrafanaJavascriptAgentEvent{
|
|
Meta: meta,
|
|
Logs: []frontendlogging.Log{{
|
|
Message: "This is a test log message",
|
|
Timestamp: ts,
|
|
LogLevel: "info",
|
|
Context: map[string]string{
|
|
"one": "two",
|
|
"bar": "baz",
|
|
},
|
|
}},
|
|
}
|
|
|
|
logGrafanaJavascriptAgentEventScenario(t, "Should log received log context", logEventWithContext,
|
|
func(sc *scenarioContext, logs map[string]any, sourceMapReads []SourceMapReadRecord) {
|
|
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
|
assertContextContains(t, logs, "context_one", "two")
|
|
assertContextContains(t, logs, "context_bar", "baz")
|
|
})
|
|
errorEventForSourceMapping := frontendlogging.FrontendGrafanaJavascriptAgentEvent{
|
|
Meta: meta,
|
|
Exceptions: []frontendlogging.Exception{
|
|
{
|
|
Type: "UserError",
|
|
Value: "Please replace user and try again",
|
|
Stacktrace: &frontendlogging.Stacktrace{
|
|
Frames: []frontendlogging.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,
|
|
},
|
|
},
|
|
},
|
|
Timestamp: ts,
|
|
},
|
|
},
|
|
}
|
|
|
|
logGrafanaJavascriptAgentEventScenario(t, "Should load sourcemap and transform stacktrace line when possible", errorEventForSourceMapping,
|
|
func(sc *scenarioContext, logs map[string]any, sourceMapReads []SourceMapReadRecord) {
|
|
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
|
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)
|
|
})
|
|
|
|
logWebVitals := frontendlogging.FrontendGrafanaJavascriptAgentEvent{
|
|
Meta: meta,
|
|
Measurements: []frontendlogging.Measurement{{
|
|
Values: map[string]float64{
|
|
"CLS": 1.0,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
logGrafanaJavascriptAgentEventScenario(t, "Should log web vitals as context", logWebVitals,
|
|
func(sc *scenarioContext, logs map[string]any, sourceMapReads []SourceMapReadRecord) {
|
|
assert.Equal(t, http.StatusAccepted, sc.resp.Code)
|
|
assertContextContains(t, logs, "CLS", float64(1))
|
|
})
|
|
})
|
|
}
|
|
|
|
func assertContextContains(t *testing.T, logRecord map[string]any, label string, value any) {
|
|
assert.Contains(t, logRecord, label)
|
|
assert.Equal(t, value, logRecord[label])
|
|
}
|