mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:38:30 -06:00
API Server: Add temporary request log for queries (#88103)
This commit is contained in:
parent
dbf258b837
commit
9e6f18c947
@ -5,11 +5,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"golang.org/x/sync/errgroup"
|
||||
errorsK8s "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -21,10 +23,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/expr/mathexp"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
type queryREST struct {
|
||||
logger log.Logger
|
||||
builder *QueryAPIBuilder
|
||||
}
|
||||
|
||||
@ -36,6 +40,13 @@ var (
|
||||
_ rest.StorageMetadata = (*queryREST)(nil)
|
||||
)
|
||||
|
||||
func newQueryREST(builder *QueryAPIBuilder) *queryREST {
|
||||
return &queryREST{
|
||||
logger: log.New("query"),
|
||||
builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *queryREST) New() runtime.Object {
|
||||
// This is added as the "ResponseType" regarless what ProducesObject() says :)
|
||||
return &query.QueryDataResponse{}
|
||||
@ -67,7 +78,7 @@ func (r *queryREST) NewConnectOptions() (runtime.Object, bool, string) {
|
||||
return nil, false, "" // true means you can use the trailing path as a variable
|
||||
}
|
||||
|
||||
func (r *queryREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
||||
func (r *queryREST) Connect(ctx context.Context, name string, opts runtime.Object, incomingResponder rest.Responder) (http.Handler, error) {
|
||||
// See: /pkg/apiserver/builder/helper.go#L34
|
||||
// The name is set with a rewriter hack
|
||||
if name != "name" {
|
||||
@ -76,9 +87,30 @@ func (r *queryREST) Connect(ctx context.Context, name string, opts runtime.Objec
|
||||
b := r.builder
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, httpreq *http.Request) {
|
||||
start := time.Now()
|
||||
ctx, span := b.tracer.Start(httpreq.Context(), "QueryService.Query")
|
||||
defer span.End()
|
||||
|
||||
logger := r.logger.FromContext(ctx)
|
||||
|
||||
responder := newResponderWrapper(incomingResponder,
|
||||
func(statusCode int, obj runtime.Object) {
|
||||
if statusCode >= 400 {
|
||||
span.SetStatus(codes.Error, fmt.Sprintf("error with HTTP status code %s", strconv.Itoa(statusCode)))
|
||||
}
|
||||
|
||||
logRequest(logger, start, statusCode)
|
||||
},
|
||||
func(err error) {
|
||||
span.SetStatus(codes.Error, "request failed")
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
span.RecordError(err)
|
||||
logRequestError(logger, start, err)
|
||||
})
|
||||
|
||||
raw := &query.QueryDataRequest{}
|
||||
err := web.Bind(httpreq, raw)
|
||||
if err != nil {
|
||||
@ -337,3 +369,51 @@ func (b *QueryAPIBuilder) handleExpressions(ctx context.Context, req parsedReque
|
||||
}
|
||||
return qdr, nil
|
||||
}
|
||||
|
||||
// logRequest short-term hack until k8s have added support for traceIDs in request logs.
|
||||
func logRequest(logger log.Logger, startTime time.Time, statusCode int) {
|
||||
duration := time.Since(startTime)
|
||||
logger.Debug("Query request completed", "status", statusCode, "duration", duration.String())
|
||||
}
|
||||
|
||||
func logRequestError(logger log.Logger, startTime time.Time, err error) {
|
||||
var args []any
|
||||
var gfErr errutil.Error
|
||||
if !errors.As(err, &gfErr) {
|
||||
args = []any{"error", err.Error()}
|
||||
} else {
|
||||
args = []any{
|
||||
"errorReason", gfErr.Reason,
|
||||
"errorMessageID", gfErr.MessageID,
|
||||
"error", gfErr.LogMessage,
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
args = append(args, "duration", duration.String())
|
||||
logger.Error("Query request completed", args...)
|
||||
}
|
||||
|
||||
type responderWrapper struct {
|
||||
wrapped rest.Responder
|
||||
onObjectFn func(statusCode int, obj runtime.Object)
|
||||
onErrorFn func(err error)
|
||||
}
|
||||
|
||||
func newResponderWrapper(responder rest.Responder, onObjectFn func(statusCode int, obj runtime.Object), onErrorFn func(err error)) *responderWrapper {
|
||||
return &responderWrapper{
|
||||
wrapped: responder,
|
||||
onObjectFn: onObjectFn,
|
||||
onErrorFn: onErrorFn,
|
||||
}
|
||||
}
|
||||
|
||||
func (r responderWrapper) Object(statusCode int, obj runtime.Object) {
|
||||
r.onObjectFn(statusCode, obj)
|
||||
r.wrapped.Object(statusCode, obj)
|
||||
}
|
||||
|
||||
func (r responderWrapper) Error(err error) {
|
||||
r.onErrorFn(err)
|
||||
r.wrapped.Error(err)
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ func (b *QueryAPIBuilder) GetAPIGroupInfo(
|
||||
}
|
||||
|
||||
// The query endpoint -- NOTE, this uses a rewrite hack to allow requests without a name parameter
|
||||
storage["query"] = &queryREST{builder: b}
|
||||
storage["query"] = newQueryREST(b)
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[gv.Version] = storage
|
||||
return &apiGroupInfo, nil
|
||||
|
Loading…
Reference in New Issue
Block a user