huma
huma copied to clipboard
Huma middleware can't access some information comming from the http.Request
I'm in the process of adding opentelemetry support to one of our project that uses huma.
As I want to set the operationID that was matched to this request as a span name/attribute I need to make that at the huma middleware. (An router-level middleware wouldn't have this information)
In this middleware I need to crate a new opentelemetry span and then attach some attribute to it that describe the request.
This list of attribute is standardized. There are two attributes that are required by the standard and that can't be accessed from a huma.Context (unless I missed something)
- Whether or not tls is used for the request
- The http version used for the request (1.0/1.1/2.0...)
In other opentelemetry interceptors & middleware this information is extracted from the http.Request struct and specifically the r.TLS, r.Proto, r.ProtoMajor and r.ProtoMinor fields.
@aureliar8 this seems like a missing feature in the adapter interface. I'll see if I can get it added to enable this use case. Thanks for reporting!
@danielgtaylor any reason why huma.Context cannot expose the underlying *http.Request? That would save some hassle and we wouldn't have to deal with manual wiring of the adapter
It would be nice to also allow overriding the underlying http.ResponseWriter as other observability APIs automatically instrument it, eg. NewRelic
@george-rogers not all routers use the standard library for HTTP, for example Fiber has its own HTTP library with its own request type, so we couldn't just return an *http.Request. I've been spending some time thinking about how to simplify this but am not sure what we can do yet.
@danielgtaylor - just exporting the ChiContext would be sufficient for my use case. e.g. type chiContext struct { -> type ChiContext struct { as I could just cast it in the middleware. No?
On a similar observability topic, in a Huma middleware how can we get access to the Request and Response sizes. Chi's logging middleware does this by wrapping the ResponseWriter in a multiwriter so it can see the response size after it's been sent and log it.
As I want to set the operationID that was matched to this request as a span name/attribute I need to make that at the huma middleware. (An router-level middleware wouldn't have this information)
It's a bit of a hack, but I figured out how to create a router-level middleware that can get operation id by passing in a closure that returns an instance of the huma.API:
EDIT: This doesn't work for paths with parameters, so nevermind. 🫤
func OperationID(apiFn func() huma.API) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
method := r.Method
path := r.URL.Path
l := log.With().Str("path", path).Str("method", method).Logger()
api := apiFn()
if api == nil || api.OpenAPI() == nil {
l.Warn().Msg("Huma API is unexpectedly nil!")
h.ServeHTTP(w, r)
return
}
item, found := api.OpenAPI().Paths[path]
if !found {
l.Warn().Msg("Path not found in OpenAPI spec")
h.ServeHTTP(w, r)
return
}
var operationID string
switch method {
case http.MethodGet:
operationID = item.Get.OperationID
case http.MethodPut:
operationID = item.Put.OperationID
case http.MethodPost:
operationID = item.Post.OperationID
case http.MethodDelete:
operationID = item.Delete.OperationID
case http.MethodOptions:
operationID = item.Options.OperationID
case http.MethodHead:
operationID = item.Head.OperationID
case http.MethodPatch:
operationID = item.Patch.OperationID
case http.MethodTrace:
operationID = item.Trace.OperationID
}
l.Info().Str("operation_id", operationID).Msg("Operation ID")
h.ServeHTTP(w, r)
})
}
}
Initialization is done like:
router := chi.NewMux()
var api huma.API
router.Use(mw.OperationID(func() huma.API { return api }))
api = humachi.New(router, MyConfig())
@aureliar8 @polds my general advise - do NOT try to put this information inside huma (outside of OpenAPI part), but put it inside adapter.
For instance, we are using huma with fiber, and fiber instrumentation works perfectly for our needs.
You could do something like that inside huma middleware
var span = trace.SpanFromContext(ctx) // fiber instrumentation span! or other "adapter" instrumentation span
span.SetAttributes(/*...*/) // whatever ADDITIONAL you need from Huma - like "OperationID"