API Server: Add temporary request log for queries (#88103)

This commit is contained in:
Marcus Efraimsson 2024-05-21 13:07:47 +02:00 committed by GitHub
parent dbf258b837
commit 9e6f18c947
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 82 additions and 2 deletions

View File

@ -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)
}

View File

@ -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