From 65b0f43dfd010093588e8132de140f0dc8ac3dab Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Mon, 28 Oct 2024 15:46:57 +0000 Subject: [PATCH 01/10] Port data source metrics middleware from core --- .../datasource_metrics_middleware.go | 153 +++++++++++++++++ .../datasource_metrics_middleware_test.go | 158 ++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 backend/httpclient/datasource_metrics_middleware.go create mode 100644 backend/httpclient/datasource_metrics_middleware_test.go diff --git a/backend/httpclient/datasource_metrics_middleware.go b/backend/httpclient/datasource_metrics_middleware.go new file mode 100644 index 000000000..d8dcb92b0 --- /dev/null +++ b/backend/httpclient/datasource_metrics_middleware.go @@ -0,0 +1,153 @@ +package httpclient + +import ( + "errors" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +const kb = 1024 +const mb = kb * kb +const gb = mb * kb + +var ( + datasourceRequestCounter = promauto.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "plugins", + Name: "datasource_request_total", + Help: "A counter for outgoing requests for an external data source", + }, + []string{"datasource", "datasource_type", "code", "method", "secure_socks_ds_proxy_enabled"}, + ) + + datasourceRequestHistogram = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "plugins", + Name: "datasource_request_duration_seconds", + Help: "histogram of durations of outgoing external data source requests sent from Grafana", + Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25, 50, 100}, + }, []string{"datasource", "datasource_type", "code", "method", "secure_socks_ds_proxy_enabled"}, + ) + + datasourceResponseHistogram = promauto.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "plugins", + Name: "datasource_response_size_bytes", + Help: "histogram of external data source response sizes returned to Grafana", + Buckets: []float64{128, 256, 512, 1 * kb, 2 * kb, 4 * kb, 8 * kb, 16 * kb, 32 * kb, 64 * kb, 128 * kb, 256 * kb, 512 * kb, 1 * mb, + 2 * mb, 4 * mb, 8 * mb, 16 * mb, 32 * mb, 64 * mb, 128 * mb, 256 * mb, 512 * mb, 1 * gb, + 2 * gb, 4 * gb, 8 * gb}, + NativeHistogramBucketFactor: 1.1, + NativeHistogramMaxBucketNumber: 100, + NativeHistogramMinResetDuration: time.Hour, + }, []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, + ) + + datasourceRequestsInFlight = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "plugins", + Name: "datasource_request_in_flight", + Help: "A gauge of outgoing external data source requests currently being sent by Grafana", + }, + []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, + ) +) + +// SanitizeLabelName removes all invalid chars from the label name. +// If the label name is empty or contains only invalid chars, it +// will return an error. +func SanitizeLabelName(name string) (string, error) { + if len(name) == 0 { + return "", errors.New("label name cannot be empty") + } + + out := strings.Builder{} + for i, b := range name { + if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0) { + out.WriteRune(b) + } else if b == ' ' { + out.WriteRune('_') + } + } + + if out.Len() == 0 { + return "", fmt.Errorf("label name only contains invalid chars: %q", name) + } + + return out.String(), nil +} + +const DataSourceMetricsMiddlewareName = "datasource_metrics" + +var executeMiddlewareFunc = executeMiddleware + +func DataSourceMetricsMiddleware() Middleware { + return NamedMiddlewareFunc(DataSourceMetricsMiddlewareName, func(opts Options, next http.RoundTripper) http.RoundTripper { + if opts.Labels == nil { + return next + } + + datasourceName, exists := opts.Labels["datasource_name"] + if !exists { + return next + } + + datasourceLabelName, err := SanitizeLabelName(datasourceName) + // if the datasource named cannot be turned into a prometheus + // label we will skip instrumenting these metrics. + if err != nil { + return next + } + + datasourceType, exists := opts.Labels["datasource_type"] + if !exists { + return next + } + datasourceLabelType, err := SanitizeLabelName(datasourceType) + // if the datasource type cannot be turned into a prometheus + // label we will skip instrumenting these metrics. + if err != nil { + return next + } + + labels := prometheus.Labels{ + "datasource": datasourceLabelName, + "datasource_type": datasourceLabelType, + "secure_socks_ds_proxy_enabled": strconv.FormatBool(opts.ProxyOptions != nil && opts.ProxyOptions.Enabled), + } + + return executeMiddlewareFunc(next, labels) + }) +} + +func executeMiddleware(next http.RoundTripper, labels prometheus.Labels) http.RoundTripper { + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + requestCounter := datasourceRequestCounter.MustCurryWith(labels) + requestHistogram := datasourceRequestHistogram.MustCurryWith(labels) + requestInFlight := datasourceRequestsInFlight.With(labels) + responseSizeHistogram := datasourceResponseHistogram.With(labels) + + res, err := promhttp.InstrumentRoundTripperDuration(requestHistogram, + promhttp.InstrumentRoundTripperCounter(requestCounter, + promhttp.InstrumentRoundTripperInFlight(requestInFlight, next))). + RoundTrip(r) + if err != nil { + return nil, err + } + + if res != nil && res.StatusCode != http.StatusSwitchingProtocols { + res.Body = CountBytesReader(res.Body, func(bytesRead int64) { + responseSizeHistogram.Observe(float64(bytesRead)) + }) + } + + return res, nil + }) +} diff --git a/backend/httpclient/datasource_metrics_middleware_test.go b/backend/httpclient/datasource_metrics_middleware_test.go new file mode 100644 index 000000000..44782f38b --- /dev/null +++ b/backend/httpclient/datasource_metrics_middleware_test.go @@ -0,0 +1,158 @@ +package httpclient + +import ( + "net/http" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend/proxy" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" +) + +func TestDataSourceMetricsMiddleware(t *testing.T) { + t.Run("Without label options set should return next http.RoundTripper", func(t *testing.T) { + origExecuteMiddlewareFunc := executeMiddlewareFunc + executeMiddlewareCalled := false + middlewareCalled := false + executeMiddlewareFunc = func(next http.RoundTripper, _ prometheus.Labels) http.RoundTripper { + executeMiddlewareCalled = true + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + middlewareCalled = true + return next.RoundTrip(r) + }) + } + t.Cleanup(func() { + executeMiddlewareFunc = origExecuteMiddlewareFunc + }) + + ctx := &testContext{} + finalRoundTripper := ctx.createRoundTripper("finalrt") + mw := DataSourceMetricsMiddleware() + rt := mw.CreateMiddleware(Options{}, finalRoundTripper) + require.NotNil(t, rt) + middlewareName, ok := mw.(MiddlewareName) + require.True(t, ok) + require.Equal(t, DataSourceMetricsMiddlewareName, middlewareName.MiddlewareName()) + + req, err := http.NewRequest(http.MethodGet, "http://", nil) + require.NoError(t, err) + res, err := rt.RoundTrip(req) + require.NoError(t, err) + require.NotNil(t, res) + if res.Body != nil { + require.NoError(t, res.Body.Close()) + } + require.Len(t, ctx.callChain, 1) + require.ElementsMatch(t, []string{"finalrt"}, ctx.callChain) + require.False(t, executeMiddlewareCalled) + require.False(t, middlewareCalled) + }) + + t.Run("Without data source name label options set should return next http.RoundTripper", func(t *testing.T) { + origExecuteMiddlewareFunc := executeMiddlewareFunc + executeMiddlewareCalled := false + middlewareCalled := false + executeMiddlewareFunc = func(next http.RoundTripper, _ prometheus.Labels) http.RoundTripper { + executeMiddlewareCalled = true + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + middlewareCalled = true + return next.RoundTrip(r) + }) + } + t.Cleanup(func() { + executeMiddlewareFunc = origExecuteMiddlewareFunc + }) + + ctx := &testContext{} + finalRoundTripper := ctx.createRoundTripper("finalrt") + mw := DataSourceMetricsMiddleware() + rt := mw.CreateMiddleware(Options{Labels: map[string]string{"test": "test"}}, finalRoundTripper) + require.NotNil(t, rt) + middlewareName, ok := mw.(MiddlewareName) + require.True(t, ok) + require.Equal(t, DataSourceMetricsMiddlewareName, middlewareName.MiddlewareName()) + + req, err := http.NewRequest(http.MethodGet, "http://", nil) + require.NoError(t, err) + res, err := rt.RoundTrip(req) + require.NoError(t, err) + require.NotNil(t, res) + if res.Body != nil { + require.NoError(t, res.Body.Close()) + } + require.Len(t, ctx.callChain, 1) + require.ElementsMatch(t, []string{"finalrt"}, ctx.callChain) + require.False(t, executeMiddlewareCalled) + require.False(t, middlewareCalled) + }) + + t.Run("With datasource name label options set should execute middleware", func(t *testing.T) { + origExecuteMiddlewareFunc := executeMiddlewareFunc + executeMiddlewareCalled := false + labels := prometheus.Labels{} + middlewareCalled := false + executeMiddlewareFunc = func(next http.RoundTripper, datasourceLabel prometheus.Labels) http.RoundTripper { + executeMiddlewareCalled = true + labels = datasourceLabel + return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + middlewareCalled = true + return next.RoundTrip(r) + }) + } + t.Cleanup(func() { + executeMiddlewareFunc = origExecuteMiddlewareFunc + }) + + testCases := []struct { + description string + httpClientOptions Options + expectedSecureSocksDSProxyEnabled string + }{ + { + description: "secure socks ds proxy is disabled", + httpClientOptions: Options{ + Labels: map[string]string{"datasource_name": "My Data Source 123", "datasource_type": "prometheus"}, + }, + expectedSecureSocksDSProxyEnabled: "false", + }, + { + description: "secure socks ds proxy is enabled", + httpClientOptions: Options{ + Labels: map[string]string{"datasource_name": "My Data Source 123", "datasource_type": "prometheus"}, + ProxyOptions: &proxy.Options{Enabled: true}, + }, + expectedSecureSocksDSProxyEnabled: "true", + }, + } + + for _, tt := range testCases { + t.Run(tt.description, func(t *testing.T) { + ctx := &testContext{} + finalRoundTripper := ctx.createRoundTripper("finalrt") + mw := DataSourceMetricsMiddleware() + rt := mw.CreateMiddleware(tt.httpClientOptions, finalRoundTripper) + require.NotNil(t, rt) + middlewareName, ok := mw.(MiddlewareName) + require.True(t, ok) + require.Equal(t, DataSourceMetricsMiddlewareName, middlewareName.MiddlewareName()) + + req, err := http.NewRequest(http.MethodGet, "http://", nil) + require.NoError(t, err) + res, err := rt.RoundTrip(req) + require.NoError(t, err) + require.NotNil(t, res) + if res.Body != nil { + require.NoError(t, res.Body.Close()) + } + require.Len(t, ctx.callChain, 1) + require.ElementsMatch(t, []string{"finalrt"}, ctx.callChain) + require.True(t, executeMiddlewareCalled) + require.Len(t, labels, 3) + require.Equal(t, "My_Data_Source_123", labels["datasource"]) + require.Equal(t, "prometheus", labels["datasource_type"]) + require.Equal(t, tt.expectedSecureSocksDSProxyEnabled, labels["secure_socks_ds_proxy_enabled"]) + require.True(t, middlewareCalled) + }) + } + }) +} From 26666de5cceb01400c81afaa5eef1582b4d75077 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Mon, 28 Oct 2024 15:47:09 +0000 Subject: [PATCH 02/10] Make use of metrics middleware --- backend/httpclient/http_client.go | 1 + backend/httpclient/http_client_test.go | 3 ++- backend/httpclient/provider_test.go | 9 ++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/httpclient/http_client.go b/backend/httpclient/http_client.go index 0d88f72e8..080b5302e 100644 --- a/backend/httpclient/http_client.go +++ b/backend/httpclient/http_client.go @@ -211,6 +211,7 @@ func DefaultMiddlewares() []Middleware { CustomHeadersMiddleware(), ContextualMiddleware(), ErrorSourceMiddleware(), + DataSourceMetricsMiddleware(), } } diff --git a/backend/httpclient/http_client_test.go b/backend/httpclient/http_client_test.go index e75e9bb73..525371daa 100644 --- a/backend/httpclient/http_client_test.go +++ b/backend/httpclient/http_client_test.go @@ -55,12 +55,13 @@ func TestNewClient(t *testing.T) { require.NoError(t, err) require.NotNil(t, client) - require.Len(t, usedMiddlewares, 5) + require.Len(t, usedMiddlewares, 6) require.Equal(t, TracingMiddlewareName, usedMiddlewares[0].(MiddlewareName).MiddlewareName()) require.Equal(t, BasicAuthenticationMiddlewareName, usedMiddlewares[1].(MiddlewareName).MiddlewareName()) require.Equal(t, CustomHeadersMiddlewareName, usedMiddlewares[2].(MiddlewareName).MiddlewareName()) require.Equal(t, ContextualMiddlewareName, usedMiddlewares[3].(MiddlewareName).MiddlewareName()) require.Equal(t, ErrorSourceMiddlewareName, usedMiddlewares[4].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, usedMiddlewares[5].(MiddlewareName).MiddlewareName()) }) t.Run("New() with opts middleware should return expected http.Client", func(t *testing.T) { diff --git a/backend/httpclient/provider_test.go b/backend/httpclient/provider_test.go index deb7525ce..5f36947e3 100644 --- a/backend/httpclient/provider_test.go +++ b/backend/httpclient/provider_test.go @@ -24,12 +24,13 @@ func TestProvider(t *testing.T) { client, err := ctx.provider.New() require.NoError(t, err) require.NotNil(t, client) - require.Len(t, ctx.usedMiddlewares, 5) + require.Len(t, ctx.usedMiddlewares, 6) require.Equal(t, TracingMiddlewareName, ctx.usedMiddlewares[0].(MiddlewareName).MiddlewareName()) require.Equal(t, BasicAuthenticationMiddlewareName, ctx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) require.Equal(t, CustomHeadersMiddlewareName, ctx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) require.Equal(t, ContextualMiddlewareName, ctx.usedMiddlewares[3].(MiddlewareName).MiddlewareName()) require.Equal(t, ErrorSourceMiddlewareName, ctx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, ctx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) }) t.Run("Transport should use default middlewares", func(t *testing.T) { @@ -37,12 +38,13 @@ func TestProvider(t *testing.T) { transport, err := ctx.provider.GetTransport() require.NoError(t, err) require.NotNil(t, transport) - require.Len(t, ctx.usedMiddlewares, 5) + require.Len(t, ctx.usedMiddlewares, 6) require.Equal(t, TracingMiddlewareName, ctx.usedMiddlewares[0].(MiddlewareName).MiddlewareName()) require.Equal(t, BasicAuthenticationMiddlewareName, ctx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) require.Equal(t, CustomHeadersMiddlewareName, ctx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) require.Equal(t, ContextualMiddlewareName, ctx.usedMiddlewares[3].(MiddlewareName).MiddlewareName()) require.Equal(t, ErrorSourceMiddlewareName, ctx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, ctx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) }) t.Run("New() with options and no middleware should return expected http client and transport", func(t *testing.T) { @@ -83,7 +85,7 @@ func TestProvider(t *testing.T) { require.Equal(t, DefaultTimeoutOptions.Timeout, client.Timeout) t.Run("Should use configured middlewares and implement MiddlewareName", func(t *testing.T) { - require.Len(t, pCtx.usedMiddlewares, 8) + require.Len(t, pCtx.usedMiddlewares, 9) require.Equal(t, "mw1", pCtx.usedMiddlewares[0].(MiddlewareName).MiddlewareName()) require.Equal(t, "mw2", pCtx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) require.Equal(t, "mw3", pCtx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) @@ -92,6 +94,7 @@ func TestProvider(t *testing.T) { require.Equal(t, CustomHeadersMiddlewareName, pCtx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) require.Equal(t, ContextualMiddlewareName, pCtx.usedMiddlewares[6].(MiddlewareName).MiddlewareName()) require.Equal(t, ErrorSourceMiddlewareName, pCtx.usedMiddlewares[7].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, pCtx.usedMiddlewares[8].(MiddlewareName).MiddlewareName()) }) t.Run("When roundtrip should call expected middlewares", func(t *testing.T) { From 8dd27ed70da2cd81e02ae2b1a98a738db33ff003 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 31 Oct 2024 17:52:31 +0000 Subject: [PATCH 03/10] Extract endpoint context --- backend/admission.go | 5 +++-- backend/{ => context}/endpoint.go | 0 backend/{ => context}/endpoint_test.go | 0 backend/conversion.go | 4 +++- backend/data.go | 3 ++- backend/diagnostics.go | 6 ++++-- backend/handler_middleware.go | 6 ++++-- backend/log.go | 3 ++- backend/metrics_middleware.go | 3 ++- backend/resource.go | 4 +++- backend/stream.go | 7 ++++--- backend/tracing_middleware.go | 3 ++- 12 files changed, 29 insertions(+), 15 deletions(-) rename backend/{ => context}/endpoint.go (100%) rename backend/{ => context}/endpoint_test.go (100%) diff --git a/backend/admission.go b/backend/admission.go index a8c430b9f..80f1f2245 100644 --- a/backend/admission.go +++ b/backend/admission.go @@ -3,15 +3,16 @@ package backend import ( "context" + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" ) const ( // EndpointValidateAdmission friendly name for the validate admission endpoint/handler. - EndpointValidateAdmission Endpoint = "validateAdmission" + EndpointValidateAdmission ctxHelpers.Endpoint = "validateAdmission" // EndpointMutateAdmission friendly name for the mutate admission endpoint/handler. - EndpointMutateAdmission Endpoint = "mutateAdmission" + EndpointMutateAdmission ctxHelpers.Endpoint = "mutateAdmission" ) // AdmissionHandler is an EXPERIMENTAL service that allows checking objects before they are saved diff --git a/backend/endpoint.go b/backend/context/endpoint.go similarity index 100% rename from backend/endpoint.go rename to backend/context/endpoint.go diff --git a/backend/endpoint_test.go b/backend/context/endpoint_test.go similarity index 100% rename from backend/endpoint_test.go rename to backend/context/endpoint_test.go diff --git a/backend/conversion.go b/backend/conversion.go index a258e3e69..5d986870f 100644 --- a/backend/conversion.go +++ b/backend/conversion.go @@ -2,11 +2,13 @@ package backend import ( "context" + + pCtx "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) const ( // EndpointConvertObjects friendly name for the convert objects endpoint/handler. - EndpointConvertObjects Endpoint = "convertObjects" + EndpointConvertObjects pCtx.Endpoint = "convertObjects" ) // ConversionHandler is an EXPERIMENTAL service that allows converting objects between versions diff --git a/backend/data.go b/backend/data.go index 5712115ac..90429837a 100644 --- a/backend/data.go +++ b/backend/data.go @@ -7,12 +7,13 @@ import ( "net/http" "time" + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/data" jsoniter "github.com/json-iterator/go" ) // EndpointQueryData friendly name for the query data endpoint/handler. -const EndpointQueryData Endpoint = "queryData" +const EndpointQueryData ctxHelpers.Endpoint = "queryData" // QueryDataHandler handles data queries. type QueryDataHandler interface { diff --git a/backend/diagnostics.go b/backend/diagnostics.go index 02810746d..90f28ba74 100644 --- a/backend/diagnostics.go +++ b/backend/diagnostics.go @@ -4,14 +4,16 @@ import ( "context" "net/http" "strconv" + + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) const ( // EndpointCheckHealth friendly name for the check health endpoint/handler. - EndpointCheckHealth Endpoint = "checkHealth" + EndpointCheckHealth ctxHelpers.Endpoint = "checkHealth" // EndpointCollectMetrics friendly name for the collect metrics endpoint/handler. - EndpointCollectMetrics Endpoint = "collectMetrics" + EndpointCollectMetrics ctxHelpers.Endpoint = "collectMetrics" ) // CheckHealthHandler enables users to send health check diff --git a/backend/handler_middleware.go b/backend/handler_middleware.go index c7d3502d8..29d5a0342 100644 --- a/backend/handler_middleware.go +++ b/backend/handler_middleware.go @@ -4,6 +4,8 @@ import ( "context" "errors" "slices" + + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) var ( @@ -46,9 +48,9 @@ func HandlerFromMiddlewares(finalHandler Handler, middlewares ...HandlerMiddlewa }, nil } -func (h *MiddlewareHandler) setupContext(ctx context.Context, pluginCtx PluginContext, endpoint Endpoint) context.Context { +func (h *MiddlewareHandler) setupContext(ctx context.Context, pluginCtx PluginContext, endpoint ctxHelpers.Endpoint) context.Context { ctx = initErrorSource(ctx) - ctx = WithEndpoint(ctx, endpoint) + ctx = ctxHelpers.WithEndpoint(ctx, endpoint) ctx = WithPluginContext(ctx, pluginCtx) ctx = WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig) ctx = WithUser(ctx, pluginCtx.User) diff --git a/backend/log.go b/backend/log.go index 5a5a88df0..7e8fe7c2a 100644 --- a/backend/log.go +++ b/backend/log.go @@ -5,6 +5,7 @@ import ( "go.opentelemetry.io/otel/trace" + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/backend/log" ) @@ -20,7 +21,7 @@ var NewLoggerWith = func(args ...interface{}) log.Logger { func withContextualLogAttributes(ctx context.Context, pCtx PluginContext) context.Context { args := []any{"pluginId", pCtx.PluginID} - endpoint := EndpointFromContext(ctx) + endpoint := ctxHelpers.EndpointFromContext(ctx) if !endpoint.IsEmpty() { args = append(args, "endpoint", string(endpoint)) } diff --git a/backend/metrics_middleware.go b/backend/metrics_middleware.go index ba81af90a..0081fb2b4 100644 --- a/backend/metrics_middleware.go +++ b/backend/metrics_middleware.go @@ -3,6 +3,7 @@ package backend import ( "context" + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/prometheus/client_golang/prometheus" ) @@ -41,7 +42,7 @@ type metricsMiddleware struct { func (m *metricsMiddleware) instrumentRequest(ctx context.Context, pCtx PluginContext, fn handlerWrapperFunc) error { status, err := fn(ctx) - endpoint := EndpointFromContext(ctx) + endpoint := ctxHelpers.EndpointFromContext(ctx) labelValues := []string{endpoint.String(), status.String(), string(ErrorSourceFromContext(ctx))} diff --git a/backend/resource.go b/backend/resource.go index 81359b4ac..4b2a66f95 100644 --- a/backend/resource.go +++ b/backend/resource.go @@ -4,10 +4,12 @@ import ( "context" "net/http" "net/textproto" + + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) // EndpointCallResource friendly name for the call resource endpoint/handler. -const EndpointCallResource Endpoint = "callResource" +const EndpointCallResource ctxHelpers.Endpoint = "callResource" // CallResourceRequest represents a request for a resource call. type CallResourceRequest struct { diff --git a/backend/stream.go b/backend/stream.go index 8d4934d03..d7a7aaefa 100644 --- a/backend/stream.go +++ b/backend/stream.go @@ -5,19 +5,20 @@ import ( "encoding/json" "fmt" + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" ) const ( // EndpointSubscribeStream friendly name for the subscribe stream endpoint/handler. - EndpointSubscribeStream Endpoint = "subscribeStream" + EndpointSubscribeStream ctxHelpers.Endpoint = "subscribeStream" // EndpointPublishStream friendly name for the publish stream endpoint/handler. - EndpointPublishStream Endpoint = "publishStream" + EndpointPublishStream ctxHelpers.Endpoint = "publishStream" // EndpointRunStream friendly name for the run stream endpoint/handler. - EndpointRunStream Endpoint = "runStream" + EndpointRunStream ctxHelpers.Endpoint = "runStream" ) // SubscribeStreamHandler handles stream subscription. diff --git a/backend/tracing_middleware.go b/backend/tracing_middleware.go index c67e6afb6..1f3745f73 100644 --- a/backend/tracing_middleware.go +++ b/backend/tracing_middleware.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -26,7 +27,7 @@ type tracingMiddleware struct { } func (m *tracingMiddleware) traceRequest(ctx context.Context, pCtx PluginContext, fn func(context.Context) (RequestStatus, error)) error { - endpoint := EndpointFromContext(ctx) + endpoint := ctxHelpers.EndpointFromContext(ctx) ctx, span := m.tracer.Start(ctx, fmt.Sprintf("sdk.%s", endpoint), trace.WithAttributes( attribute.String("plugin_id", pCtx.PluginID), attribute.Int64("org_id", pCtx.OrgID), From 0c5c47d943ace8091979daad82db821d81020da3 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 31 Oct 2024 17:52:40 +0000 Subject: [PATCH 04/10] Re-order middleware --- backend/httpclient/http_client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/httpclient/http_client.go b/backend/httpclient/http_client.go index 080b5302e..3a5403bdf 100644 --- a/backend/httpclient/http_client.go +++ b/backend/httpclient/http_client.go @@ -207,11 +207,11 @@ type ConfigureMiddlewareFunc func(opts Options, existingMiddleware []Middleware) func DefaultMiddlewares() []Middleware { return []Middleware{ TracingMiddleware(nil), + DataSourceMetricsMiddleware(), BasicAuthenticationMiddleware(), CustomHeadersMiddleware(), ContextualMiddleware(), ErrorSourceMiddleware(), - DataSourceMetricsMiddleware(), } } From 2efbb13545578bc0bd679928145fd0d71719a35f Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 31 Oct 2024 17:53:34 +0000 Subject: [PATCH 05/10] Rename sanitizeLabelName - Add error logs --- backend/httpclient/datasource_metrics_middleware.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/httpclient/datasource_metrics_middleware.go b/backend/httpclient/datasource_metrics_middleware.go index d8dcb92b0..6dfda29a2 100644 --- a/backend/httpclient/datasource_metrics_middleware.go +++ b/backend/httpclient/datasource_metrics_middleware.go @@ -60,10 +60,10 @@ var ( ) ) -// SanitizeLabelName removes all invalid chars from the label name. +// sanitizeLabelName removes all invalid chars from the label name. // If the label name is empty or contains only invalid chars, it // will return an error. -func SanitizeLabelName(name string) (string, error) { +func sanitizeLabelName(name string) (string, error) { if len(name) == 0 { return "", errors.New("label name cannot be empty") } @@ -99,10 +99,11 @@ func DataSourceMetricsMiddleware() Middleware { return next } - datasourceLabelName, err := SanitizeLabelName(datasourceName) + datasourceLabelName, err := sanitizeLabelName(datasourceName) // if the datasource named cannot be turned into a prometheus // label we will skip instrumenting these metrics. if err != nil { + log.DefaultLogger.Error("failed to sanitize datasource name", "error", err) return next } @@ -110,10 +111,11 @@ func DataSourceMetricsMiddleware() Middleware { if !exists { return next } - datasourceLabelType, err := SanitizeLabelName(datasourceType) + datasourceLabelType, err := sanitizeLabelName(datasourceType) // if the datasource type cannot be turned into a prometheus // label we will skip instrumenting these metrics. if err != nil { + log.DefaultLogger.Error("failed to sanitize datasource type", "error", err) return next } From b6c52a5c871f6774aec65f4cb56e320a76fb5af2 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 31 Oct 2024 17:53:42 +0000 Subject: [PATCH 06/10] Record endpoint --- backend/httpclient/datasource_metrics_middleware.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/httpclient/datasource_metrics_middleware.go b/backend/httpclient/datasource_metrics_middleware.go index 6dfda29a2..1e6f452c7 100644 --- a/backend/httpclient/datasource_metrics_middleware.go +++ b/backend/httpclient/datasource_metrics_middleware.go @@ -8,6 +8,8 @@ import ( "strings" "time" + ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -24,7 +26,7 @@ var ( Name: "datasource_request_total", Help: "A counter for outgoing requests for an external data source", }, - []string{"datasource", "datasource_type", "code", "method", "secure_socks_ds_proxy_enabled"}, + []string{"datasource", "datasource_type", "code", "method", "secure_socks_ds_proxy_enabled", "endpoint"}, ) datasourceRequestHistogram = promauto.NewHistogramVec( @@ -33,7 +35,7 @@ var ( Name: "datasource_request_duration_seconds", Help: "histogram of durations of outgoing external data source requests sent from Grafana", Buckets: []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25, 50, 100}, - }, []string{"datasource", "datasource_type", "code", "method", "secure_socks_ds_proxy_enabled"}, + }, []string{"datasource", "datasource_type", "code", "method", "secure_socks_ds_proxy_enabled", "endpoint"}, ) datasourceResponseHistogram = promauto.NewHistogramVec( @@ -47,7 +49,7 @@ var ( NativeHistogramBucketFactor: 1.1, NativeHistogramMaxBucketNumber: 100, NativeHistogramMinResetDuration: time.Hour, - }, []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, + }, []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled", "endpoint"}, ) datasourceRequestsInFlight = promauto.NewGaugeVec( @@ -56,7 +58,7 @@ var ( Name: "datasource_request_in_flight", Help: "A gauge of outgoing external data source requests currently being sent by Grafana", }, - []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, + []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled", "endpoint"}, ) ) @@ -131,6 +133,8 @@ func DataSourceMetricsMiddleware() Middleware { func executeMiddleware(next http.RoundTripper, labels prometheus.Labels) http.RoundTripper { return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { + ctx := r.Context() + labels["endpoint"] = ctxHelpers.EndpointFromContext(ctx).String() requestCounter := datasourceRequestCounter.MustCurryWith(labels) requestHistogram := datasourceRequestHistogram.MustCurryWith(labels) requestInFlight := datasourceRequestsInFlight.With(labels) From 777e9c0fb30f69847e75fec074762750b8170efc Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Thu, 31 Oct 2024 17:59:55 +0000 Subject: [PATCH 07/10] Update tests --- backend/httpclient/http_client_test.go | 10 ++++---- backend/httpclient/provider_test.go | 35 +++++++++++++------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/backend/httpclient/http_client_test.go b/backend/httpclient/http_client_test.go index 525371daa..43cc9f965 100644 --- a/backend/httpclient/http_client_test.go +++ b/backend/httpclient/http_client_test.go @@ -57,11 +57,11 @@ func TestNewClient(t *testing.T) { require.Len(t, usedMiddlewares, 6) require.Equal(t, TracingMiddlewareName, usedMiddlewares[0].(MiddlewareName).MiddlewareName()) - require.Equal(t, BasicAuthenticationMiddlewareName, usedMiddlewares[1].(MiddlewareName).MiddlewareName()) - require.Equal(t, CustomHeadersMiddlewareName, usedMiddlewares[2].(MiddlewareName).MiddlewareName()) - require.Equal(t, ContextualMiddlewareName, usedMiddlewares[3].(MiddlewareName).MiddlewareName()) - require.Equal(t, ErrorSourceMiddlewareName, usedMiddlewares[4].(MiddlewareName).MiddlewareName()) - require.Equal(t, DataSourceMetricsMiddlewareName, usedMiddlewares[5].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, usedMiddlewares[1].(MiddlewareName).MiddlewareName()) + require.Equal(t, BasicAuthenticationMiddlewareName, usedMiddlewares[2].(MiddlewareName).MiddlewareName()) + require.Equal(t, CustomHeadersMiddlewareName, usedMiddlewares[3].(MiddlewareName).MiddlewareName()) + require.Equal(t, ContextualMiddlewareName, usedMiddlewares[4].(MiddlewareName).MiddlewareName()) + require.Equal(t, ErrorSourceMiddlewareName, usedMiddlewares[5].(MiddlewareName).MiddlewareName()) }) t.Run("New() with opts middleware should return expected http.Client", func(t *testing.T) { diff --git a/backend/httpclient/provider_test.go b/backend/httpclient/provider_test.go index 5f36947e3..2ca879184 100644 --- a/backend/httpclient/provider_test.go +++ b/backend/httpclient/provider_test.go @@ -15,8 +15,9 @@ func TestProvider(t *testing.T) { provider := NewProvider() require.NotNil(t, provider) require.Equal(t, TracingMiddlewareName, provider.Opts.Middlewares[0].(MiddlewareName).MiddlewareName()) - require.Equal(t, BasicAuthenticationMiddlewareName, provider.Opts.Middlewares[1].(MiddlewareName).MiddlewareName()) - require.Equal(t, CustomHeadersMiddlewareName, provider.Opts.Middlewares[2].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, provider.Opts.Middlewares[1].(MiddlewareName).MiddlewareName()) + require.Equal(t, BasicAuthenticationMiddlewareName, provider.Opts.Middlewares[2].(MiddlewareName).MiddlewareName()) + require.Equal(t, CustomHeadersMiddlewareName, provider.Opts.Middlewares[3].(MiddlewareName).MiddlewareName()) }) t.Run("New client should use default middlewares", func(t *testing.T) { @@ -26,11 +27,11 @@ func TestProvider(t *testing.T) { require.NotNil(t, client) require.Len(t, ctx.usedMiddlewares, 6) require.Equal(t, TracingMiddlewareName, ctx.usedMiddlewares[0].(MiddlewareName).MiddlewareName()) - require.Equal(t, BasicAuthenticationMiddlewareName, ctx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) - require.Equal(t, CustomHeadersMiddlewareName, ctx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) - require.Equal(t, ContextualMiddlewareName, ctx.usedMiddlewares[3].(MiddlewareName).MiddlewareName()) - require.Equal(t, ErrorSourceMiddlewareName, ctx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) - require.Equal(t, DataSourceMetricsMiddlewareName, ctx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, ctx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) + require.Equal(t, BasicAuthenticationMiddlewareName, ctx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) + require.Equal(t, CustomHeadersMiddlewareName, ctx.usedMiddlewares[3].(MiddlewareName).MiddlewareName()) + require.Equal(t, ContextualMiddlewareName, ctx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) + require.Equal(t, ErrorSourceMiddlewareName, ctx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) }) t.Run("Transport should use default middlewares", func(t *testing.T) { @@ -40,11 +41,11 @@ func TestProvider(t *testing.T) { require.NotNil(t, transport) require.Len(t, ctx.usedMiddlewares, 6) require.Equal(t, TracingMiddlewareName, ctx.usedMiddlewares[0].(MiddlewareName).MiddlewareName()) - require.Equal(t, BasicAuthenticationMiddlewareName, ctx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) - require.Equal(t, CustomHeadersMiddlewareName, ctx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) - require.Equal(t, ContextualMiddlewareName, ctx.usedMiddlewares[3].(MiddlewareName).MiddlewareName()) - require.Equal(t, ErrorSourceMiddlewareName, ctx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) - require.Equal(t, DataSourceMetricsMiddlewareName, ctx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, ctx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) + require.Equal(t, BasicAuthenticationMiddlewareName, ctx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) + require.Equal(t, CustomHeadersMiddlewareName, ctx.usedMiddlewares[3].(MiddlewareName).MiddlewareName()) + require.Equal(t, ContextualMiddlewareName, ctx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) + require.Equal(t, ErrorSourceMiddlewareName, ctx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) }) t.Run("New() with options and no middleware should return expected http client and transport", func(t *testing.T) { @@ -90,11 +91,11 @@ func TestProvider(t *testing.T) { require.Equal(t, "mw2", pCtx.usedMiddlewares[1].(MiddlewareName).MiddlewareName()) require.Equal(t, "mw3", pCtx.usedMiddlewares[2].(MiddlewareName).MiddlewareName()) require.Equal(t, TracingMiddlewareName, pCtx.usedMiddlewares[3].(MiddlewareName).MiddlewareName()) - require.Equal(t, BasicAuthenticationMiddlewareName, pCtx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) - require.Equal(t, CustomHeadersMiddlewareName, pCtx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) - require.Equal(t, ContextualMiddlewareName, pCtx.usedMiddlewares[6].(MiddlewareName).MiddlewareName()) - require.Equal(t, ErrorSourceMiddlewareName, pCtx.usedMiddlewares[7].(MiddlewareName).MiddlewareName()) - require.Equal(t, DataSourceMetricsMiddlewareName, pCtx.usedMiddlewares[8].(MiddlewareName).MiddlewareName()) + require.Equal(t, DataSourceMetricsMiddlewareName, pCtx.usedMiddlewares[4].(MiddlewareName).MiddlewareName()) + require.Equal(t, BasicAuthenticationMiddlewareName, pCtx.usedMiddlewares[5].(MiddlewareName).MiddlewareName()) + require.Equal(t, CustomHeadersMiddlewareName, pCtx.usedMiddlewares[6].(MiddlewareName).MiddlewareName()) + require.Equal(t, ContextualMiddlewareName, pCtx.usedMiddlewares[7].(MiddlewareName).MiddlewareName()) + require.Equal(t, ErrorSourceMiddlewareName, pCtx.usedMiddlewares[8].(MiddlewareName).MiddlewareName()) }) t.Run("When roundtrip should call expected middlewares", func(t *testing.T) { From c94e9b463f012e8480200ba85f8af5ad3518d477 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Mon, 4 Nov 2024 13:14:18 +0000 Subject: [PATCH 08/10] Reset endpoint context changes and introduce internal package --- backend/admission.go | 5 ++--- backend/conversion.go | 4 +--- backend/data.go | 3 +-- backend/diagnostics.go | 6 ++---- backend/{context => }/endpoint.go | 14 +++++++------- backend/{context => }/endpoint_test.go | 0 backend/handler_middleware.go | 6 ++---- .../httpclient/datasource_metrics_middleware.go | 7 +++++-- backend/internal/endpointctx/endpointctx.go | 5 +++++ backend/log.go | 3 +-- backend/metrics_middleware.go | 3 +-- backend/resource.go | 4 +--- backend/stream.go | 7 +++---- backend/tracing_middleware.go | 3 +-- 14 files changed, 32 insertions(+), 38 deletions(-) rename backend/{context => }/endpoint.go (71%) rename backend/{context => }/endpoint_test.go (100%) create mode 100644 backend/internal/endpointctx/endpointctx.go diff --git a/backend/admission.go b/backend/admission.go index 80f1f2245..a8c430b9f 100644 --- a/backend/admission.go +++ b/backend/admission.go @@ -3,16 +3,15 @@ package backend import ( "context" - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" ) const ( // EndpointValidateAdmission friendly name for the validate admission endpoint/handler. - EndpointValidateAdmission ctxHelpers.Endpoint = "validateAdmission" + EndpointValidateAdmission Endpoint = "validateAdmission" // EndpointMutateAdmission friendly name for the mutate admission endpoint/handler. - EndpointMutateAdmission ctxHelpers.Endpoint = "mutateAdmission" + EndpointMutateAdmission Endpoint = "mutateAdmission" ) // AdmissionHandler is an EXPERIMENTAL service that allows checking objects before they are saved diff --git a/backend/conversion.go b/backend/conversion.go index 5d986870f..a258e3e69 100644 --- a/backend/conversion.go +++ b/backend/conversion.go @@ -2,13 +2,11 @@ package backend import ( "context" - - pCtx "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) const ( // EndpointConvertObjects friendly name for the convert objects endpoint/handler. - EndpointConvertObjects pCtx.Endpoint = "convertObjects" + EndpointConvertObjects Endpoint = "convertObjects" ) // ConversionHandler is an EXPERIMENTAL service that allows converting objects between versions diff --git a/backend/data.go b/backend/data.go index 90429837a..5712115ac 100644 --- a/backend/data.go +++ b/backend/data.go @@ -7,13 +7,12 @@ import ( "net/http" "time" - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/data" jsoniter "github.com/json-iterator/go" ) // EndpointQueryData friendly name for the query data endpoint/handler. -const EndpointQueryData ctxHelpers.Endpoint = "queryData" +const EndpointQueryData Endpoint = "queryData" // QueryDataHandler handles data queries. type QueryDataHandler interface { diff --git a/backend/diagnostics.go b/backend/diagnostics.go index 90f28ba74..02810746d 100644 --- a/backend/diagnostics.go +++ b/backend/diagnostics.go @@ -4,16 +4,14 @@ import ( "context" "net/http" "strconv" - - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) const ( // EndpointCheckHealth friendly name for the check health endpoint/handler. - EndpointCheckHealth ctxHelpers.Endpoint = "checkHealth" + EndpointCheckHealth Endpoint = "checkHealth" // EndpointCollectMetrics friendly name for the collect metrics endpoint/handler. - EndpointCollectMetrics ctxHelpers.Endpoint = "collectMetrics" + EndpointCollectMetrics Endpoint = "collectMetrics" ) // CheckHealthHandler enables users to send health check diff --git a/backend/context/endpoint.go b/backend/endpoint.go similarity index 71% rename from backend/context/endpoint.go rename to backend/endpoint.go index a337191dd..6245b7e83 100644 --- a/backend/context/endpoint.go +++ b/backend/endpoint.go @@ -1,6 +1,10 @@ package backend -import "context" +import ( + "context" + + endpointctx "github.com/grafana/grafana-plugin-sdk-go/backend/internal/endpointctx" +) // Endpoint used for defining names for endpoints/handlers. type Endpoint string @@ -14,18 +18,14 @@ func (e Endpoint) String() string { return string(e) } -type endpointCtxKeyType struct{} - -var endpointCtxKey = endpointCtxKeyType{} - // WithEndpoint adds endpoint to ctx. func WithEndpoint(ctx context.Context, endpoint Endpoint) context.Context { - return context.WithValue(ctx, endpointCtxKey, endpoint) + return context.WithValue(ctx, endpointctx.EndpointCtxKey, endpoint) } // EndpointFromContext extracts [Endpoint] from ctx if available, otherwise empty [Endpoint]. func EndpointFromContext(ctx context.Context) Endpoint { - if ep := ctx.Value(endpointCtxKey); ep != nil { + if ep := ctx.Value(endpointctx.EndpointCtxKey); ep != nil { return ep.(Endpoint) } diff --git a/backend/context/endpoint_test.go b/backend/endpoint_test.go similarity index 100% rename from backend/context/endpoint_test.go rename to backend/endpoint_test.go diff --git a/backend/handler_middleware.go b/backend/handler_middleware.go index 29d5a0342..c7d3502d8 100644 --- a/backend/handler_middleware.go +++ b/backend/handler_middleware.go @@ -4,8 +4,6 @@ import ( "context" "errors" "slices" - - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) var ( @@ -48,9 +46,9 @@ func HandlerFromMiddlewares(finalHandler Handler, middlewares ...HandlerMiddlewa }, nil } -func (h *MiddlewareHandler) setupContext(ctx context.Context, pluginCtx PluginContext, endpoint ctxHelpers.Endpoint) context.Context { +func (h *MiddlewareHandler) setupContext(ctx context.Context, pluginCtx PluginContext, endpoint Endpoint) context.Context { ctx = initErrorSource(ctx) - ctx = ctxHelpers.WithEndpoint(ctx, endpoint) + ctx = WithEndpoint(ctx, endpoint) ctx = WithPluginContext(ctx, pluginCtx) ctx = WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig) ctx = WithUser(ctx, pluginCtx.User) diff --git a/backend/httpclient/datasource_metrics_middleware.go b/backend/httpclient/datasource_metrics_middleware.go index 1e6f452c7..e4bcdf8e5 100644 --- a/backend/httpclient/datasource_metrics_middleware.go +++ b/backend/httpclient/datasource_metrics_middleware.go @@ -8,7 +8,7 @@ import ( "strings" "time" - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" + endpointctx "github.com/grafana/grafana-plugin-sdk-go/backend/internal/endpointctx" "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -134,7 +134,10 @@ func DataSourceMetricsMiddleware() Middleware { func executeMiddleware(next http.RoundTripper, labels prometheus.Labels) http.RoundTripper { return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { ctx := r.Context() - labels["endpoint"] = ctxHelpers.EndpointFromContext(ctx).String() + labels["endpoint"] = "" + if ep := ctx.Value(endpointctx.EndpointCtxKey); ep != nil { + labels["endpoint"] = ep.(string) + } requestCounter := datasourceRequestCounter.MustCurryWith(labels) requestHistogram := datasourceRequestHistogram.MustCurryWith(labels) requestInFlight := datasourceRequestsInFlight.With(labels) diff --git a/backend/internal/endpointctx/endpointctx.go b/backend/internal/endpointctx/endpointctx.go new file mode 100644 index 000000000..246e49c8c --- /dev/null +++ b/backend/internal/endpointctx/endpointctx.go @@ -0,0 +1,5 @@ +package internal + +type EndpointCtxKeyType struct{} + +var EndpointCtxKey = EndpointCtxKeyType{} diff --git a/backend/log.go b/backend/log.go index 7e8fe7c2a..5a5a88df0 100644 --- a/backend/log.go +++ b/backend/log.go @@ -5,7 +5,6 @@ import ( "go.opentelemetry.io/otel/trace" - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/backend/log" ) @@ -21,7 +20,7 @@ var NewLoggerWith = func(args ...interface{}) log.Logger { func withContextualLogAttributes(ctx context.Context, pCtx PluginContext) context.Context { args := []any{"pluginId", pCtx.PluginID} - endpoint := ctxHelpers.EndpointFromContext(ctx) + endpoint := EndpointFromContext(ctx) if !endpoint.IsEmpty() { args = append(args, "endpoint", string(endpoint)) } diff --git a/backend/metrics_middleware.go b/backend/metrics_middleware.go index 0081fb2b4..ba81af90a 100644 --- a/backend/metrics_middleware.go +++ b/backend/metrics_middleware.go @@ -3,7 +3,6 @@ package backend import ( "context" - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/prometheus/client_golang/prometheus" ) @@ -42,7 +41,7 @@ type metricsMiddleware struct { func (m *metricsMiddleware) instrumentRequest(ctx context.Context, pCtx PluginContext, fn handlerWrapperFunc) error { status, err := fn(ctx) - endpoint := ctxHelpers.EndpointFromContext(ctx) + endpoint := EndpointFromContext(ctx) labelValues := []string{endpoint.String(), status.String(), string(ErrorSourceFromContext(ctx))} diff --git a/backend/resource.go b/backend/resource.go index 4b2a66f95..81359b4ac 100644 --- a/backend/resource.go +++ b/backend/resource.go @@ -4,12 +4,10 @@ import ( "context" "net/http" "net/textproto" - - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" ) // EndpointCallResource friendly name for the call resource endpoint/handler. -const EndpointCallResource ctxHelpers.Endpoint = "callResource" +const EndpointCallResource Endpoint = "callResource" // CallResourceRequest represents a request for a resource call. type CallResourceRequest struct { diff --git a/backend/stream.go b/backend/stream.go index d7a7aaefa..8d4934d03 100644 --- a/backend/stream.go +++ b/backend/stream.go @@ -5,20 +5,19 @@ import ( "encoding/json" "fmt" - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" ) const ( // EndpointSubscribeStream friendly name for the subscribe stream endpoint/handler. - EndpointSubscribeStream ctxHelpers.Endpoint = "subscribeStream" + EndpointSubscribeStream Endpoint = "subscribeStream" // EndpointPublishStream friendly name for the publish stream endpoint/handler. - EndpointPublishStream ctxHelpers.Endpoint = "publishStream" + EndpointPublishStream Endpoint = "publishStream" // EndpointRunStream friendly name for the run stream endpoint/handler. - EndpointRunStream ctxHelpers.Endpoint = "runStream" + EndpointRunStream Endpoint = "runStream" ) // SubscribeStreamHandler handles stream subscription. diff --git a/backend/tracing_middleware.go b/backend/tracing_middleware.go index 1f3745f73..c67e6afb6 100644 --- a/backend/tracing_middleware.go +++ b/backend/tracing_middleware.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - ctxHelpers "github.com/grafana/grafana-plugin-sdk-go/backend/context" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -27,7 +26,7 @@ type tracingMiddleware struct { } func (m *tracingMiddleware) traceRequest(ctx context.Context, pCtx PluginContext, fn func(context.Context) (RequestStatus, error)) error { - endpoint := ctxHelpers.EndpointFromContext(ctx) + endpoint := EndpointFromContext(ctx) ctx, span := m.tracer.Start(ctx, fmt.Sprintf("sdk.%s", endpoint), trace.WithAttributes( attribute.String("plugin_id", pCtx.PluginID), attribute.Int64("org_id", pCtx.OrgID), From 6eb463095c6af04c0f4acbed2b4f600c0469b4d4 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Tue, 5 Nov 2024 12:01:19 +0000 Subject: [PATCH 09/10] Add comment --- backend/internal/endpointctx/endpointctx.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/internal/endpointctx/endpointctx.go b/backend/internal/endpointctx/endpointctx.go index 246e49c8c..7f7d69366 100644 --- a/backend/internal/endpointctx/endpointctx.go +++ b/backend/internal/endpointctx/endpointctx.go @@ -1,3 +1,4 @@ +// This package has been added to expose the EndpointCtxKey to allow the datasource_metrics_middleware to read it package internal type EndpointCtxKeyType struct{} From ae33ecabaac34c9ac36d341808e0e3bd8cb107fb Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Tue, 5 Nov 2024 12:03:20 +0000 Subject: [PATCH 10/10] Amend comment location --- backend/internal/endpointctx/endpointctx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/internal/endpointctx/endpointctx.go b/backend/internal/endpointctx/endpointctx.go index 7f7d69366..8c5160b9c 100644 --- a/backend/internal/endpointctx/endpointctx.go +++ b/backend/internal/endpointctx/endpointctx.go @@ -1,6 +1,6 @@ -// This package has been added to expose the EndpointCtxKey to allow the datasource_metrics_middleware to read it package internal type EndpointCtxKeyType struct{} +// This package has been added to expose the EndpointCtxKey to allow the datasource_metrics_middleware to read it var EndpointCtxKey = EndpointCtxKeyType{}