2020-11-12 05:29:43 -06:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/getsentry/sentry-go"
|
2021-01-15 07:43:20 -06:00
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
2020-11-12 05:29:43 -06:00
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
|
|
log "github.com/inconshreveable/log15"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
type logScenarioFunc func(c *scenarioContext, logs []*log.Record)
|
|
|
|
|
|
|
|
func logSentryEventScenario(t *testing.T, desc string, event frontendSentryEvent, fn logScenarioFunc) {
|
|
|
|
t.Run(desc, func(t *testing.T) {
|
|
|
|
logs := []*log.Record{}
|
|
|
|
origHandler := frontendLogger.GetHandler()
|
|
|
|
frontendLogger.SetHandler(log.FuncHandler(func(r *log.Record) error {
|
|
|
|
logs = append(logs, r)
|
|
|
|
return nil
|
|
|
|
}))
|
|
|
|
t.Cleanup(func() {
|
|
|
|
frontendLogger.SetHandler(origHandler)
|
|
|
|
})
|
|
|
|
|
2020-11-13 02:52:38 -06:00
|
|
|
sc := setupScenarioContext(t, "/log")
|
2020-11-12 05:29:43 -06:00
|
|
|
hs := HTTPServer{}
|
|
|
|
|
2021-01-15 07:43:20 -06:00
|
|
|
handler := routing.Wrap(func(w http.ResponseWriter, c *models.ReqContext) response.Response {
|
2020-11-12 05:29:43 -06:00
|
|
|
sc.context = c
|
|
|
|
return hs.logFrontendMessage(c, event)
|
|
|
|
})
|
|
|
|
|
|
|
|
sc.m.Post(sc.url, handler)
|
|
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
|
|
fn(sc, logs)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFrontendLoggingEndpoint(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",
|
|
|
|
}
|
|
|
|
|
|
|
|
errorEvent := frontendSentryEvent{
|
|
|
|
&sentry.Event{
|
|
|
|
EventID: "123",
|
|
|
|
Level: sentry.LevelError,
|
|
|
|
Request: &request,
|
|
|
|
Timestamp: ts,
|
|
|
|
},
|
|
|
|
&frontendSentryException{
|
|
|
|
Values: []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 []*log.Record) {
|
|
|
|
assert.Equal(t, 200, sc.resp.Code)
|
|
|
|
assert.Len(t, logs, 1)
|
|
|
|
assertContextContains(t, logs[0], "logger", "frontend")
|
|
|
|
assertContextContains(t, logs[0], "url", errorEvent.Request.URL)
|
|
|
|
assertContextContains(t, logs[0], "user_agent", errorEvent.Request.Headers["User-Agent"])
|
|
|
|
assertContextContains(t, logs[0], "event_id", errorEvent.EventID)
|
|
|
|
assertContextContains(t, logs[0], "original_timestamp", errorEvent.Timestamp)
|
|
|
|
assertContextContains(t, logs[0], "stacktrace", `UserError: Please replace user and try again
|
|
|
|
at foofn (foo.js:123:23)
|
|
|
|
at barfn (bar.js:113:231)`)
|
2020-12-10 03:50:37 -06:00
|
|
|
assert.NotContains(t, logs[0].Ctx, "context")
|
2020-11-12 05:29:43 -06:00
|
|
|
})
|
|
|
|
|
|
|
|
messageEvent := frontendSentryEvent{
|
|
|
|
&sentry.Event{
|
|
|
|
EventID: "123",
|
|
|
|
Level: sentry.LevelInfo,
|
|
|
|
Request: &request,
|
|
|
|
Timestamp: ts,
|
|
|
|
Message: "hello world",
|
|
|
|
User: user,
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
logSentryEventScenario(t, "Should log received message event", messageEvent, func(sc *scenarioContext, logs []*log.Record) {
|
|
|
|
assert.Equal(t, 200, sc.resp.Code)
|
|
|
|
assert.Len(t, logs, 1)
|
|
|
|
assert.Equal(t, "hello world", logs[0].Msg)
|
|
|
|
assert.Equal(t, log.LvlInfo, logs[0].Lvl)
|
|
|
|
assertContextContains(t, logs[0], "logger", "frontend")
|
|
|
|
assertContextContains(t, logs[0], "url", messageEvent.Request.URL)
|
|
|
|
assertContextContains(t, logs[0], "user_agent", messageEvent.Request.Headers["User-Agent"])
|
|
|
|
assertContextContains(t, logs[0], "event_id", messageEvent.EventID)
|
|
|
|
assertContextContains(t, logs[0], "original_timestamp", messageEvent.Timestamp)
|
|
|
|
assert.NotContains(t, logs[0].Ctx, "stacktrace")
|
2020-12-10 03:50:37 -06:00
|
|
|
assert.NotContains(t, logs[0].Ctx, "context")
|
2020-11-12 05:29:43 -06:00
|
|
|
assertContextContains(t, logs[0], "user_email", user.Email)
|
|
|
|
assertContextContains(t, logs[0], "user_id", user.ID)
|
|
|
|
})
|
2020-12-10 03:50:37 -06:00
|
|
|
|
|
|
|
eventWithContext := frontendSentryEvent{
|
|
|
|
&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",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
logSentryEventScenario(t, "Should log event context", eventWithContext, func(sc *scenarioContext, logs []*log.Record) {
|
|
|
|
assert.Equal(t, 200, sc.resp.Code)
|
|
|
|
assert.Len(t, logs, 1)
|
|
|
|
assertContextContains(t, logs[0], "context_foo_one", "two")
|
|
|
|
assertContextContains(t, logs[0], "context_foo_three", "4")
|
|
|
|
assertContextContains(t, logs[0], "context_bar", "baz")
|
|
|
|
})
|
2020-11-12 05:29:43 -06:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func indexOf(arr []interface{}, item string) int {
|
|
|
|
for i, elem := range arr {
|
|
|
|
if elem == item {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
func assertContextContains(t *testing.T, logRecord *log.Record, label string, value interface{}) {
|
|
|
|
assert.Contains(t, logRecord.Ctx, label)
|
|
|
|
labelIdx := indexOf(logRecord.Ctx, label)
|
|
|
|
assert.Equal(t, value, logRecord.Ctx[labelIdx+1])
|
|
|
|
}
|