mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 00:39:13 +08:00
Instrumentation: Log plugin and datasource info for each plugin request. (#54769)
Signed-off-by: bergquist <carl.bergquist@gmail.com>
This commit is contained in:
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
|
@ -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",
|
||||||
|
@ -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) {
|
||||||
|
@ -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",
|
||||||
|
@ -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"
|
||||||
|
Reference in New Issue
Block a user