elastic / apm-agent-go

https://www.elastic.co/guide/en/apm/agent/go/current/index.html
Apache License 2.0
418 stars 193 forks source link

Add support for gqlgen #924

Open Baseris opened 3 years ago

Baseris commented 3 years ago

I'm using gqlgen. Gqlgen is a Go library for building GraphQL servers. When i use gin module, all request seem POST /query. Is there any way for detailed transaction metrics without new module. If no, could you please add gqlgen support.

axw commented 3 years ago

I don't know if there's a way of using existing modules to extract useful span names out of gqlgen -- probably not, but if anyone sees this and knows please chime in.

I think it would be reasonable to add a module specifically for gqlgen/graphql, perhaps using some of the logic in https://github.com/99designs/gqlgen/blob/master/graphql/handler/apollotracing/tracer.go.

milesich commented 3 years ago

I am using something like this. It is not gin but maybe you can modify it. In Kibana you would then see GraphQL request like POST /graphql [userList]. Non GraphQL request would look the same as now.

package main

import ()

const (
    // GraphQLEndpoint is an endpoint for GraphQL
    GraphQLEndpoint = "/graphql"
)

type graphQLRequest struct {
    OperationName string      `json:"operationName"`
    Query         interface{} `json:"query"`
    Variables     interface{} `json:"variables"`
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc(GraphQLEndpoint, func(w http.ResponseWriter, r *http.Request) {
        srv := handler.NewDefaultServer(
            generated.NewExecutableSchema(generated.Config{Resolvers: &resolver.Resolver{}}),
        )
        srv.ServeHTTP(w, r)
    })

    apmHandler := apmhttp.Wrap(mux, apmhttp.WithServerRequestName(ServerRequestName))

    _ = http.ListenAndServe(":8080", apmHandler)

}

func ServerRequestName(req *http.Request) string {
    var b strings.Builder
    b.Grow(len(req.Method) + len(req.URL.Path) + 1)
    b.WriteString(req.Method)
    b.WriteByte(' ')
    b.WriteString(req.URL.Path)

    // if this is not GQL request return early
    if req.URL.Path != GraphQLEndpoint {
        return b.String()
    }

    // try to get operation name from GQL request
    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        b.Grow(len(err.Error()) + 3)
        b.WriteString(" [")
        b.WriteString(err.Error())
        b.WriteByte(']')
        return b.String()
    }

    // return body back so we can use it later
    req.Body = ioutil.NopCloser(bytes.NewBuffer(body))

    res := graphQLRequest{
        OperationName: "unknown",
    }
    _ = json.Unmarshal(body, &res)

    b.Grow(len(res.OperationName) + 3)
    b.WriteString(" [")
    b.WriteString(res.OperationName)
    b.WriteByte(']')

    return b.String()
}