grafana/pkg/services/pluginsintegration/clientmiddleware/tracing_middleware.go

136 lines
5.2 KiB
Go
Raw Normal View History

Plugins: Improve instrumentation by adding metrics and tracing (#61035) * WIP: Plugins tracing * Trace ID middleware * Add prometheus metrics and tracing to plugins updater * Add TODOs * Add instrumented http client * Add tracing to grafana update checker * Goimports * Moved plugins tracing to middleware * goimports, fix tests * Removed X-Trace-Id header * Fix comment in NewTracingHeaderMiddleware * Add metrics to instrumented http client * Add instrumented http client options * Removed unused function * Switch to contextual logger * Refactoring, fix tests * Moved InstrumentedHTTPClient and PrometheusMetrics to their own package * Tracing middleware: handle errors * Report span status codes when recording errors * Add tests for tracing middleware * Moved fakeSpan and fakeTracer to pkg/infra/tracing * Add TestHTTPClientTracing * Lint * Changes after PR review * Tests: Made "ended" in FakeSpan private, allow calling End only once * Testing: panic in FakeSpan if span already ended * Refactoring: Simplify Grafana updater checks * Refactoring: Simplify plugins updater error checks and logs * Fix wrong call to checkForUpdates -> instrumentedCheckForUpdates * Tests: Fix wrong call to checkForUpdates -> instrumentedCheckForUpdates * Log update checks duration, use Info log level for check succeeded logs * Add plugin context span attributes in tracing_middleware * Refactor prometheus metrics as httpclient middleware * Fix call to ProvidePluginsService in plugins_test.go * Propagate context to update checker outgoing http requests * Plugin client tracing middleware: Removed operation name in status * Fix tests * Goimports tracing_middleware.go * Goimports * Fix imports * Changed span name to plugins client middleware * Add span name assertion in TestTracingMiddleware * Removed Prometheus metrics middleware from grafana and plugins updatechecker * Add span attributes for ds name, type, uid, panel and dashboard ids * Fix http header reading in tracing middlewares * Use contexthandler.FromContext, add X-Query-Group-Id * Add test for RunStream * Fix imports * Changes from PR review * TestTracingMiddleware: Changed assert to require for didPanic assertion * Lint * Fix imports
2023-03-28 17:01:06 +08:00
package clientmiddleware
import (
"context"
"net/http"
"strconv"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/query"
)
// NewTracingMiddleware returns a new middleware that creates a new span on every method call.
func NewTracingMiddleware(tracer tracing.Tracer) plugins.ClientMiddleware {
return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client {
return &TracingMiddleware{
tracer: tracer,
next: next,
}
})
}
type TracingMiddleware struct {
tracer tracing.Tracer
next plugins.Client
}
// setSpanAttributeFromHTTPHeader takes a ReqContext and a span, and adds the specified HTTP header as a span attribute
// (string value), if the header is present.
func setSpanAttributeFromHTTPHeader(headers http.Header, span tracing.Span, attributeName, headerName string) {
// Set the attribute as string
if v := headers.Get(headerName); v != "" {
span.SetAttributes(attributeName, v, attribute.Key(attributeName).String(v))
}
}
// traceWrap returns a new context.Context which wraps a newly created span. The span will also contain attributes for
// plugin id, org id, user login, ds, dashboard and panel info. The second function returned is a cleanup function,
// which should be called by the caller (deferred) and will set the span status/error and end the span.
func (m *TracingMiddleware) traceWrap(
ctx context.Context, pluginContext backend.PluginContext, opName string,
) (context.Context, func(error)) {
// Start span
ctx, span := m.tracer.Start(ctx, "PluginClient."+opName)
// Attach some plugin context information to span
span.SetAttributes("plugin_id", pluginContext.PluginID, attribute.String("plugin_id", pluginContext.PluginID))
span.SetAttributes("org_id", pluginContext.OrgID, attribute.Int64("org_id", pluginContext.OrgID))
if settings := pluginContext.DataSourceInstanceSettings; settings != nil {
span.SetAttributes("datasource_name", settings.Name, attribute.Key("datasource_name").String(settings.Name))
span.SetAttributes("datasource_uid", settings.UID, attribute.Key("datasource_uid").String(settings.UID))
}
if u := pluginContext.User; u != nil {
span.SetAttributes("user", u.Login, attribute.String("user", u.Login))
}
// Additional attributes from http headers
if reqCtx := contexthandler.FromContext(ctx); reqCtx != nil && reqCtx.Req != nil && len(reqCtx.Req.Header) > 0 {
if v, err := strconv.Atoi(reqCtx.Req.Header.Get(query.HeaderPanelID)); err == nil {
span.SetAttributes("panel_id", v, attribute.Key("panel_id").Int(v))
}
setSpanAttributeFromHTTPHeader(reqCtx.Req.Header, span, "query_group_id", query.HeaderQueryGroupID)
setSpanAttributeFromHTTPHeader(reqCtx.Req.Header, span, "dashboard_uid", query.HeaderDashboardUID)
}
// Return ctx with span + cleanup func
return ctx, func(err error) {
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.RecordError(err)
}
span.End()
}
}
func (m *TracingMiddleware) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
var err error
ctx, end := m.traceWrap(ctx, req.PluginContext, "queryData")
defer func() { end(err) }()
resp, err := m.next.QueryData(ctx, req)
return resp, err
}
func (m *TracingMiddleware) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
var err error
ctx, end := m.traceWrap(ctx, req.PluginContext, "callResource")
defer func() { end(err) }()
err = m.next.CallResource(ctx, req, sender)
return err
}
func (m *TracingMiddleware) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
var err error
ctx, end := m.traceWrap(ctx, req.PluginContext, "checkHealth")
defer func() { end(err) }()
resp, err := m.next.CheckHealth(ctx, req)
return resp, err
}
func (m *TracingMiddleware) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
var err error
ctx, end := m.traceWrap(ctx, req.PluginContext, "collectMetrics")
defer func() { end(err) }()
resp, err := m.next.CollectMetrics(ctx, req)
return resp, err
}
func (m *TracingMiddleware) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
var err error
ctx, end := m.traceWrap(ctx, req.PluginContext, "subscribeStream")
defer func() { end(err) }()
resp, err := m.next.SubscribeStream(ctx, req)
return resp, err
}
func (m *TracingMiddleware) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
var err error
ctx, end := m.traceWrap(ctx, req.PluginContext, "publishStream")
defer func() { end(err) }()
resp, err := m.next.PublishStream(ctx, req)
return resp, err
}
func (m *TracingMiddleware) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
var err error
ctx, end := m.traceWrap(ctx, req.PluginContext, "runStream")
defer func() { end(err) }()
err = m.next.RunStream(ctx, req, sender)
return err
}