Instrumentation: Log plugin and datasource info for each plugin request. (#54769)

Signed-off-by: bergquist <carl.bergquist@gmail.com>
This commit is contained in:
Carl Bergquist
2022-11-02 14:51:51 +01:00
committed by GitHub
parent d32a9a62b5
commit 06705a49e2
9 changed files with 72 additions and 25 deletions

View File

@ -78,6 +78,7 @@ export interface FeatureToggles {
queryLibrary?: boolean; queryLibrary?: boolean;
showDashboardValidationWarnings?: boolean; showDashboardValidationWarnings?: boolean;
mysqlAnsiQuotes?: boolean; mysqlAnsiQuotes?: boolean;
datasourceLogger?: boolean;
accessControlOnCall?: boolean; accessControlOnCall?: boolean;
nestedFolders?: boolean; nestedFolders?: boolean;
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/config"
pluginClient "github.com/grafana/grafana/pkg/plugins/manager/client" pluginClient "github.com/grafana/grafana/pkg/plugins/manager/client"
"github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/registry"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
@ -290,7 +291,7 @@ func TestDataSourceQueryError(t *testing.T) {
nil, nil,
&fakePluginRequestValidator{}, &fakePluginRequestValidator{},
&fakeDatasources.FakeDataSourceService{}, &fakeDatasources.FakeDataSourceService{},
pluginClient.ProvideService(r), pluginClient.ProvideService(r, &config.Cfg{}),
&fakeOAuthTokenService{}, &fakeOAuthTokenService{},
) )
hs.QuotaService = quotatest.NewQuotaServiceFake() hs.QuotaService = quotatest.NewQuotaServiceFake()

View File

@ -2,8 +2,13 @@
package instrumentation package instrumentation
import ( import (
"context"
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
) )
@ -23,8 +28,10 @@ var (
}, []string{"plugin_id", "endpoint"}) }, []string{"plugin_id", "endpoint"})
) )
var logger log.Logger = log.New("plugin.instrumentation")
// instrumentPluginRequest instruments success rate and latency of `fn` // instrumentPluginRequest instruments success rate and latency of `fn`
func instrumentPluginRequest(pluginID string, endpoint string, fn func() error) error { func instrumentPluginRequest(ctx context.Context, cfg *config.Cfg, pluginCtx *backend.PluginContext, endpoint string, fn func() error) error {
status := "ok" status := "ok"
start := time.Now() start := time.Now()
@ -34,29 +41,50 @@ func instrumentPluginRequest(pluginID string, endpoint string, fn func() error)
status = "error" status = "error"
} }
elapsed := time.Since(start) / time.Millisecond elapsed := time.Since(start)
pluginRequestDuration.WithLabelValues(pluginID, endpoint).Observe(float64(elapsed)) pluginRequestDuration.WithLabelValues(pluginCtx.PluginID, endpoint).Observe(float64(elapsed / time.Millisecond))
pluginRequestCounter.WithLabelValues(pluginID, endpoint, status).Inc() pluginRequestCounter.WithLabelValues(pluginCtx.PluginID, endpoint, status).Inc()
if cfg.LogDatasourceRequests {
logParams := []interface{}{
"status", status,
"duration", elapsed,
"pluginId", pluginCtx.PluginID,
"endpoint", endpoint,
}
traceID := tracing.TraceIDFromContext(ctx, false)
if traceID != "" {
logParams = append(logParams, "traceID", traceID)
}
if pluginCtx.DataSourceInstanceSettings != nil {
logParams = append(logParams, "dsName", pluginCtx.DataSourceInstanceSettings.Name)
logParams = append(logParams, "dsUID", pluginCtx.DataSourceInstanceSettings.UID)
}
logger.Info("Plugin Request Completed", logParams...)
}
return err return err
} }
// InstrumentCollectMetrics instruments collectMetrics. // InstrumentCollectMetrics instruments collectMetrics.
func InstrumentCollectMetrics(pluginID string, fn func() error) error { func InstrumentCollectMetrics(ctx context.Context, req *backend.PluginContext, cfg *config.Cfg, fn func() error) error {
return instrumentPluginRequest(pluginID, "collectMetrics", fn) return instrumentPluginRequest(ctx, cfg, req, "collectMetrics", fn)
} }
// InstrumentCheckHealthRequest instruments checkHealth. // InstrumentCheckHealthRequest instruments checkHealth.
func InstrumentCheckHealthRequest(pluginID string, fn func() error) error { func InstrumentCheckHealthRequest(ctx context.Context, req *backend.PluginContext, cfg *config.Cfg, fn func() error) error {
return instrumentPluginRequest(pluginID, "checkHealth", fn) return instrumentPluginRequest(ctx, cfg, req, "checkHealth", fn)
} }
// InstrumentCallResourceRequest instruments callResource. // InstrumentCallResourceRequest instruments callResource.
func InstrumentCallResourceRequest(pluginID string, fn func() error) error { func InstrumentCallResourceRequest(ctx context.Context, req *backend.PluginContext, cfg *config.Cfg, fn func() error) error {
return instrumentPluginRequest(pluginID, "callResource", fn) return instrumentPluginRequest(ctx, cfg, req, "callResource", fn)
} }
// InstrumentQueryDataRequest instruments success rate and latency of query data requests. // InstrumentQueryDataRequest instruments success rate and latency of query data requests.
func InstrumentQueryDataRequest(pluginID string, fn func() error) error { func InstrumentQueryDataRequest(ctx context.Context, req *backend.PluginContext, cfg *config.Cfg, fn func() error) error {
return instrumentPluginRequest(pluginID, "queryData", fn) return instrumentPluginRequest(ctx, cfg, req, "queryData", fn)
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana-azure-sdk-go/azsettings" "github.com/grafana/grafana-azure-sdk-go/azsettings"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -29,6 +30,8 @@ type Cfg struct {
Azure *azsettings.AzureSettings Azure *azsettings.AzureSettings
BuildVersion string // TODO Remove BuildVersion string // TODO Remove
LogDatasourceRequests bool
} }
func ProvideConfig(settingProvider setting.Provider, grafanaCfg *setting.Cfg) *Cfg { func ProvideConfig(settingProvider setting.Provider, grafanaCfg *setting.Cfg) *Cfg {
@ -67,6 +70,7 @@ func NewCfg(settingProvider setting.Provider, grafanaCfg *setting.Cfg) *Cfg {
ManagedIdentityEnabled: azure.KeyValue("managed_identity_enabled").MustBool(grafanaCfg.Azure.ManagedIdentityEnabled), ManagedIdentityEnabled: azure.KeyValue("managed_identity_enabled").MustBool(grafanaCfg.Azure.ManagedIdentityEnabled),
ManagedIdentityClientId: azure.KeyValue("managed_identity_client_id").MustString(grafanaCfg.Azure.ManagedIdentityClientId), ManagedIdentityClientId: azure.KeyValue("managed_identity_client_id").MustString(grafanaCfg.Azure.ManagedIdentityClientId),
}, },
LogDatasourceRequests: grafanaCfg.IsFeatureToggleEnabled(featuremgmt.FlagDatasourceLogger),
} }
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation" "github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/manager/registry" "github.com/grafana/grafana/pkg/plugins/manager/registry"
) )
@ -17,11 +18,13 @@ var _ plugins.Client = (*Service)(nil)
type Service struct { type Service struct {
pluginRegistry registry.Service pluginRegistry registry.Service
cfg *config.Cfg
} }
func ProvideService(pluginRegistry registry.Service) *Service { func ProvideService(pluginRegistry registry.Service, cfg *config.Cfg) *Service {
return &Service{ return &Service{
pluginRegistry: pluginRegistry, pluginRegistry: pluginRegistry,
cfg: cfg,
} }
} }
@ -32,7 +35,7 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
} }
var resp *backend.QueryDataResponse var resp *backend.QueryDataResponse
err := instrumentation.InstrumentQueryDataRequest(req.PluginContext.PluginID, func() (innerErr error) { err := instrumentation.InstrumentQueryDataRequest(ctx, &req.PluginContext, s.cfg, func() (innerErr error) {
resp, innerErr = plugin.QueryData(ctx, req) resp, innerErr = plugin.QueryData(ctx, req)
return return
}) })
@ -66,7 +69,7 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq
if !exists { if !exists {
return backendplugin.ErrPluginNotRegistered return backendplugin.ErrPluginNotRegistered
} }
err := instrumentation.InstrumentCallResourceRequest(p.PluginID(), func() error { err := instrumentation.InstrumentCallResourceRequest(ctx, &req.PluginContext, s.cfg, func() error {
if err := p.CallResource(ctx, req, sender); err != nil { if err := p.CallResource(ctx, req, sender); err != nil {
return err return err
} }
@ -87,7 +90,7 @@ func (s *Service) CollectMetrics(ctx context.Context, req *backend.CollectMetric
} }
var resp *backend.CollectMetricsResult var resp *backend.CollectMetricsResult
err := instrumentation.InstrumentCollectMetrics(p.PluginID(), func() (innerErr error) { err := instrumentation.InstrumentCollectMetrics(ctx, &req.PluginContext, s.cfg, func() (innerErr error) {
resp, innerErr = p.CollectMetrics(ctx, req) resp, innerErr = p.CollectMetrics(ctx, req)
return return
}) })
@ -105,7 +108,7 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
} }
var resp *backend.CheckHealthResult var resp *backend.CheckHealthResult
err := instrumentation.InstrumentCheckHealthRequest(p.PluginID(), func() (innerErr error) { err := instrumentation.InstrumentCheckHealthRequest(ctx, &req.PluginContext, s.cfg, func() (innerErr error) {
resp, innerErr = p.CheckHealth(ctx, req) resp, innerErr = p.CheckHealth(ctx, req)
return return
}) })

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/plugins/manager/fakes"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -16,7 +17,7 @@ import (
func TestQueryData(t *testing.T) { func TestQueryData(t *testing.T) {
t.Run("Empty registry should return not registered error", func(t *testing.T) { t.Run("Empty registry should return not registered error", func(t *testing.T) {
registry := fakes.NewFakePluginRegistry() registry := fakes.NewFakePluginRegistry()
client := ProvideService(registry) client := ProvideService(registry, &config.Cfg{})
_, err := client.QueryData(context.Background(), &backend.QueryDataRequest{}) _, err := client.QueryData(context.Background(), &backend.QueryDataRequest{})
require.Error(t, err) require.Error(t, err)
require.ErrorIs(t, err, plugins.ErrPluginNotRegistered) require.ErrorIs(t, err, plugins.ErrPluginNotRegistered)
@ -57,7 +58,7 @@ func TestQueryData(t *testing.T) {
err := registry.Add(context.Background(), p) err := registry.Add(context.Background(), p)
require.NoError(t, err) require.NoError(t, err)
client := ProvideService(registry) client := ProvideService(registry, &config.Cfg{})
_, err = client.QueryData(context.Background(), &backend.QueryDataRequest{ _, err = client.QueryData(context.Background(), &backend.QueryDataRequest{
PluginContext: backend.PluginContext{ PluginContext: backend.PluginContext{
PluginID: "grafana", PluginID: "grafana",

View File

@ -73,10 +73,11 @@ func TestIntegrationPluginManager(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
cfg := &setting.Cfg{ cfg := &setting.Cfg{
Raw: raw, Raw: raw,
StaticRootPath: staticRootPath, StaticRootPath: staticRootPath,
BundledPluginsPath: bundledPluginsPath, BundledPluginsPath: bundledPluginsPath,
Azure: &azsettings.AzureSettings{}, Azure: &azsettings.AzureSettings{},
IsFeatureToggleEnabled: func(_ string) bool { return false },
} }
tracer := tracing.InitializeTracerForTest() tracer := tracing.InitializeTracerForTest()
@ -115,7 +116,7 @@ func TestIntegrationPluginManager(t *testing.T) {
verifyBundledPlugins(t, ctx, ps) verifyBundledPlugins(t, ctx, ps)
verifyPluginStaticRoutes(t, ctx, ps) verifyPluginStaticRoutes(t, ctx, ps)
verifyBackendProcesses(t, reg.Plugins(ctx)) verifyBackendProcesses(t, reg.Plugins(ctx))
verifyPluginQuery(t, ctx, client.ProvideService(reg)) verifyPluginQuery(t, ctx, client.ProvideService(reg, pCfg))
} }
func verifyPluginQuery(t *testing.T, ctx context.Context, c plugins.Client) { func verifyPluginQuery(t *testing.T, ctx context.Context, c plugins.Client) {

View File

@ -340,6 +340,10 @@ var (
Description: "Use double quote to escape keyword in Mysql query", Description: "Use double quote to escape keyword in Mysql query",
State: FeatureStateAlpha, State: FeatureStateAlpha,
}, },
{
Name: "datasourceLogger",
Description: "Logs all datasource requests",
},
{ {
Name: "accessControlOnCall", Name: "accessControlOnCall",
Description: "Access control primitives for OnCall", Description: "Access control primitives for OnCall",

View File

@ -255,6 +255,10 @@ const (
// Use double quote to escape keyword in Mysql query // Use double quote to escape keyword in Mysql query
FlagMysqlAnsiQuotes = "mysqlAnsiQuotes" FlagMysqlAnsiQuotes = "mysqlAnsiQuotes"
// FlagDatasourceLogger
// Logs all datasource requests
FlagDatasourceLogger = "datasourceLogger"
// FlagAccessControlOnCall // FlagAccessControlOnCall
// Access control primitives for OnCall // Access control primitives for OnCall
FlagAccessControlOnCall = "accessControlOnCall" FlagAccessControlOnCall = "accessControlOnCall"