OpenFeature: Add eval ctx and http status code to span attributes (#112572)

* Add HTTP status codes to handlers spans

* Add a span for namespace validation
This commit is contained in:
Tania
2025-10-17 21:21:33 +02:00
committed by GitHub
parent 8512b219c5
commit 688248198e
+15 -2
View File
@@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/infra/tracing"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -252,6 +253,7 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) {
if !b.validateNamespace(r) {
_ = tracing.Errorf(span, namespaceMismatchMsg)
span.SetAttributes(semconv.HTTPStatusCode(http.StatusUnauthorized))
b.logger.Error(namespaceMismatchMsg)
http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized)
return
@@ -260,6 +262,7 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) {
flagKey := mux.Vars(r)["flagKey"]
if flagKey == "" {
_ = tracing.Errorf(span, "flagKey parameter is required")
span.SetAttributes(semconv.HTTPStatusCode(http.StatusBadRequest))
http.Error(w, "flagKey parameter is required", http.StatusBadRequest)
return
}
@@ -272,6 +275,7 @@ func (b *APIBuilder) oneFlagHandler(w http.ResponseWriter, r *http.Request) {
// Unless the request is authenticated, we only allow public flags evaluations
if !isAuthedReq && !isPublicFlag(flagKey) {
_ = tracing.Errorf(span, "unauthorized to evaluate flag: %s", flagKey)
span.SetAttributes(semconv.HTTPStatusCode(http.StatusUnauthorized))
b.logger.Error("Unauthorized to evaluate flag", "flagKey", flagKey)
http.Error(w, "unauthorized to evaluate flag", http.StatusUnauthorized)
return
@@ -293,6 +297,7 @@ func (b *APIBuilder) allFlagsHandler(w http.ResponseWriter, r *http.Request) {
if !b.validateNamespace(r) {
_ = tracing.Errorf(span, namespaceMismatchMsg)
span.SetAttributes(semconv.HTTPStatusCode(http.StatusUnauthorized))
b.logger.Error(namespaceMismatchMsg)
http.Error(w, namespaceMismatchMsg, http.StatusUnauthorized)
return
@@ -357,7 +362,9 @@ func (b *APIBuilder) isAuthenticatedRequest(r *http.Request) bool {
// validateNamespace checks if the namespace in the evaluation context matches the namespace in the request
func (b *APIBuilder) validateNamespace(r *http.Request) bool {
// Extract namespace from request context or URL path
_, span := tracer.Start(r.Context(), "ofrep.validateNamespace")
defer span.End()
var namespace string
user, ok := types.AuthInfoFrom(r.Context())
if !ok {
@@ -370,19 +377,25 @@ func (b *APIBuilder) validateNamespace(r *http.Request) bool {
namespace = mux.Vars(r)["namespace"]
}
// Extract namespace from feature flag evaluation context
// Read request body for namespace validation and tracing
body, err := io.ReadAll(r.Body)
if err != nil {
_ = tracing.Errorf(span, "failed to read request body: %w", err)
b.logger.Error("Error reading evaluation request body", "error", err)
span.SetAttributes(attribute.Bool("validation.success", false))
return false
}
r.Body = io.NopCloser(bytes.NewBuffer(body))
span.SetAttributes(attribute.String("request.body", string(body)))
evalCtxNamespace := b.namespaceFromEvalCtx(body)
// "default" namespace case can only occur in on-prem grafana
if (namespace == "default" && evalCtxNamespace == "") || (evalCtxNamespace == namespace) {
span.SetAttributes(attribute.Bool("validation.success", true))
return true
}
span.SetAttributes(attribute.Bool("validation.success", false))
return false
}