PublicDashboards: Log api layer errors and which datasources fail/succeed (#55056)

Adds pubdash api error logging and logs array of datasource names with success status when running pubdash queries.
This commit is contained in:
owensmallwood 2022-09-14 13:19:21 -06:00 committed by GitHub
parent d639b5a7f8
commit e37420f0a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 22 deletions

View File

@ -76,6 +76,25 @@ type MetricRequest struct {
HTTPRequest *http.Request `json:"-"` HTTPRequest *http.Request `json:"-"`
} }
func (mr *MetricRequest) GetUniqueDatasourceTypes() []string {
dsTypes := make(map[string]bool)
for _, query := range mr.Queries {
if dsType, ok := query.Get("datasource").CheckGet("type"); ok {
name := dsType.MustString()
if _, ok := dsTypes[name]; !ok {
dsTypes[name] = true
}
}
}
res := make([]string, 0)
for dsType := range dsTypes {
res = append(res, dsType)
}
return res
}
func (mr *MetricRequest) CloneWithQueries(queries []*simplejson.Json) MetricRequest { func (mr *MetricRequest) CloneWithQueries(queries []*simplejson.Json) MetricRequest {
return MetricRequest{ return MetricRequest{
From: mr.From, From: mr.From,

View File

@ -3,11 +3,71 @@ package dtos
import ( import (
"testing" "testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGetUniqueDatasourceTypes(t *testing.T) {
testcases := []struct {
desc string
queries []*simplejson.Json
result []string
}{
{
desc: "can get unique datasource names",
result: []string{"prometheus", "mysql"},
queries: []*simplejson.Json{
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"type": "prometheus",
"uid": "uid1",
},
}),
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"type": "prometheus",
"uid": "uid2",
},
}),
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"type": "mysql",
"uid": "uid3",
},
}),
},
},
{
desc: "returns empty slice when datasources have no type property",
result: []string{},
queries: []*simplejson.Json{
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"uid": "uid1",
},
}),
simplejson.NewFromAny(map[string]interface{}{
"datasource": map[string]interface{}{
"uid": "uid3",
},
}),
},
},
}
for _, testcase := range testcases {
t.Run(testcase.desc, func(t *testing.T) {
metReq := MetricRequest{
Queries: testcase.queries,
}
result := metReq.GetUniqueDatasourceTypes()
assert.Equal(t, testcase.result, result)
})
}
}
func TestIsHiddenUser(t *testing.T) { func TestIsHiddenUser(t *testing.T) {
emptyHiddenUsers := map[string]struct{}{} emptyHiddenUsers := map[string]struct{}{}
hiddenUser := map[string]struct{}{ hiddenUser := map[string]struct{}{

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
@ -25,6 +26,7 @@ type Api struct {
RouteRegister routing.RouteRegister RouteRegister routing.RouteRegister
AccessControl accesscontrol.AccessControl AccessControl accesscontrol.AccessControl
Features *featuremgmt.FeatureManager Features *featuremgmt.FeatureManager
Log log.Logger
} }
func ProvideApi( func ProvideApi(
@ -38,6 +40,7 @@ func ProvideApi(
RouteRegister: rr, RouteRegister: rr,
AccessControl: ac, AccessControl: ac,
Features: features, Features: features,
Log: log.New("publicdashboards.api"),
} }
// attach api if PublicDashboards feature flag is enabled // attach api if PublicDashboards feature flag is enabled
@ -83,7 +86,7 @@ func (api *Api) GetPublicDashboard(c *models.ReqContext) response.Response {
) )
if err != nil { if err != nil {
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard", err) return api.handleError(http.StatusInternalServerError, "failed to get public dashboard", err)
} }
meta := dtos.DashboardMeta{ meta := dtos.DashboardMeta{
@ -113,7 +116,7 @@ func (api *Api) GetPublicDashboard(c *models.ReqContext) response.Response {
func (api *Api) GetPublicDashboardConfig(c *models.ReqContext) response.Response { func (api *Api) GetPublicDashboardConfig(c *models.ReqContext) response.Response {
pdc, err := api.PublicDashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgID, web.Params(c.Req)[":uid"]) pdc, err := api.PublicDashboardService.GetPublicDashboardConfig(c.Req.Context(), c.OrgID, web.Params(c.Req)[":uid"])
if err != nil { if err != nil {
return handleDashboardErr(http.StatusInternalServerError, "Failed to get public dashboard config", err) return api.handleError(http.StatusInternalServerError, "failed to get public dashboard config", err)
} }
return response.JSON(http.StatusOK, pdc) return response.JSON(http.StatusOK, pdc)
} }
@ -124,7 +127,7 @@ func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Respons
// exit if we don't have a valid dashboardUid // exit if we don't have a valid dashboardUid
dashboardUid := web.Params(c.Req)[":uid"] dashboardUid := web.Params(c.Req)[":uid"]
if dashboardUid == "" || !util.IsValidShortUID(dashboardUid) { if dashboardUid == "" || !util.IsValidShortUID(dashboardUid) {
handleDashboardErr(http.StatusBadRequest, "no dashboardUid", dashboards.ErrDashboardIdentifierNotSet) api.handleError(http.StatusBadRequest, "no dashboardUid", dashboards.ErrDashboardIdentifierNotSet)
} }
pubdash := &PublicDashboard{} pubdash := &PublicDashboard{}
@ -144,7 +147,7 @@ func (api *Api) SavePublicDashboardConfig(c *models.ReqContext) response.Respons
// Save the public dashboard // Save the public dashboard
pubdash, err := api.PublicDashboardService.SavePublicDashboardConfig(c.Req.Context(), c.SignedInUser, &dto) pubdash, err := api.PublicDashboardService.SavePublicDashboardConfig(c.Req.Context(), c.SignedInUser, &dto)
if err != nil { if err != nil {
return handleDashboardErr(http.StatusInternalServerError, "Failed to save public dashboard configuration", err) return api.handleError(http.StatusInternalServerError, "failed to save public dashboard configuration", err)
} }
return response.JSON(http.StatusOK, pubdash) return response.JSON(http.StatusOK, pubdash)
@ -165,7 +168,7 @@ func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
resp, err := api.PublicDashboardService.GetQueryDataResponse(c.Req.Context(), c.SkipCache, reqDTO, panelId, web.Params(c.Req)[":accessToken"]) resp, err := api.PublicDashboardService.GetQueryDataResponse(c.Req.Context(), c.SkipCache, reqDTO, panelId, web.Params(c.Req)[":accessToken"])
if err != nil { if err != nil {
return handlePublicDashboardErr(err) return api.handleError(http.StatusInternalServerError, "error running public dashboard panel queries", err)
} }
return toJsonStreamingResponse(api.Features, resp) return toJsonStreamingResponse(api.Features, resp)
@ -174,10 +177,12 @@ func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
// util to help us unpack dashboard and publicdashboard errors or use default http code and message // util to help us unpack dashboard and publicdashboard errors or use default http code and message
// we should look to do some future refactoring of these errors as publicdashboard err is the same as a dashboarderr, just defined in a // we should look to do some future refactoring of these errors as publicdashboard err is the same as a dashboarderr, just defined in a
// different package. // different package.
func handleDashboardErr(defaultCode int, defaultMsg string, err error) response.Response { func (api *Api) handleError(code int, message string, err error) response.Response {
var publicDashboardErr PublicDashboardErr var publicDashboardErr PublicDashboardErr
// handle public dashboard er api.Log.Error(message, "error", err.Error())
// handle public dashboard error
if ok := errors.As(err, &publicDashboardErr); ok { if ok := errors.As(err, &publicDashboardErr); ok {
return response.Error(publicDashboardErr.StatusCode, publicDashboardErr.Error(), publicDashboardErr) return response.Error(publicDashboardErr.StatusCode, publicDashboardErr.Error(), publicDashboardErr)
} }
@ -188,11 +193,7 @@ func handleDashboardErr(defaultCode int, defaultMsg string, err error) response.
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr) return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr)
} }
return response.Error(defaultCode, defaultMsg, err) return response.Error(code, message, err)
}
func handlePublicDashboardErr(err error) response.Response {
return handleDashboardErr(http.StatusInternalServerError, "Unexpected Error", err)
} }
// Copied from pkg/api/metrics.go // Copied from pkg/api/metrics.go

View File

@ -26,33 +26,33 @@ func (e PublicDashboardErr) Error() string {
var ( var (
ErrPublicDashboardFailedGenerateUniqueUid = PublicDashboardErr{ ErrPublicDashboardFailedGenerateUniqueUid = PublicDashboardErr{
Reason: "Failed to generate unique public dashboard id", Reason: "failed to generate unique public dashboard id",
StatusCode: 500, StatusCode: 500,
} }
ErrPublicDashboardFailedGenerateAccesstoken = PublicDashboardErr{ ErrPublicDashboardFailedGenerateAccesstoken = PublicDashboardErr{
Reason: "Failed to public dashboard access token", Reason: "failed to public dashboard access token",
StatusCode: 500, StatusCode: 500,
} }
ErrPublicDashboardNotFound = PublicDashboardErr{ ErrPublicDashboardNotFound = PublicDashboardErr{
Reason: "Public dashboard not found", Reason: "public dashboard not found",
StatusCode: 404, StatusCode: 404,
Status: "not-found", Status: "not-found",
} }
ErrPublicDashboardPanelNotFound = PublicDashboardErr{ ErrPublicDashboardPanelNotFound = PublicDashboardErr{
Reason: "Panel not found in dashboard", Reason: "panel not found in dashboard",
StatusCode: 404, StatusCode: 404,
Status: "not-found", Status: "not-found",
} }
ErrPublicDashboardIdentifierNotSet = PublicDashboardErr{ ErrPublicDashboardIdentifierNotSet = PublicDashboardErr{
Reason: "No Uid for public dashboard specified", Reason: "no Uid for public dashboard specified",
StatusCode: 400, StatusCode: 400,
} }
ErrPublicDashboardHasTemplateVariables = PublicDashboardErr{ ErrPublicDashboardHasTemplateVariables = PublicDashboardErr{
Reason: "Public dashboard has template variables", Reason: "public dashboard has template variables",
StatusCode: 422, StatusCode: 422,
} }
ErrPublicDashboardBadRequest = PublicDashboardErr{ ErrPublicDashboardBadRequest = PublicDashboardErr{
Reason: "Bad Request", Reason: "bad Request",
StatusCode: 400, StatusCode: 400,
} }
) )

View File

@ -205,7 +205,17 @@ func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context,
return nil, err return nil, err
} }
return pd.QueryDataService.QueryDataMultipleSources(ctx, anonymousUser, skipCache, metricReq, true) res, err := pd.QueryDataService.QueryDataMultipleSources(ctx, anonymousUser, skipCache, metricReq, true)
// We want to track which datasources were successful and which were not
reqDatasources := metricReq.GetUniqueDatasourceTypes()
if err != nil {
pd.log.Error("Error querying datasources for public dashboard", "error", err.Error(), "datasources", reqDatasources)
return nil, err
}
pd.log.Info("Successfully queried datasources for public dashboard", "datasources", reqDatasources)
return res, nil
} }
func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, queryDto PublicDashboardQueryDTO) (dtos.MetricRequest, error) { func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, queryDto PublicDashboardQueryDTO) (dtos.MetricRequest, error) {

View File

@ -499,7 +499,7 @@ func TestBuildMetricRequest(t *testing.T) {
publicDashboardQueryDTO, publicDashboardQueryDTO,
) )
require.ErrorContains(t, err, "Panel not found") require.ErrorContains(t, err, ErrPublicDashboardPanelNotFound.Reason)
}) })
} }

View File

@ -25,7 +25,7 @@ func TestValidateSavePublicDashboard(t *testing.T) {
dto := &publicdashboardModels.SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil} dto := &publicdashboardModels.SavePublicDashboardConfigDTO{DashboardUid: "abc123", OrgId: 1, UserId: 1, PublicDashboard: nil}
err := ValidateSavePublicDashboard(dto, dashboard) err := ValidateSavePublicDashboard(dto, dashboard)
require.ErrorContains(t, err, "Public dashboard has template variables") require.ErrorContains(t, err, publicdashboardModels.ErrPublicDashboardHasTemplateVariables.Reason)
}) })
t.Run("Returns no validation error when dashboard has no template variables", func(t *testing.T) { t.Run("Returns no validation error when dashboard has no template variables", func(t *testing.T) {