Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contrib/graph-gophers/graphql-go: add graphql tracing support #287

Merged
merged 5 commits into from
Aug 1, 2018

Conversation

dd-caleb
Copy link
Contributor

Fixes: #286

This adds GraphQL server tracing support.

)

const (
graphqlFieldTag = "graphql.field"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the tag* prefix here:

const (
	tagGraphqlField = "graphql.field"
	tagGraphqlQuery = "graphql.query"
	tagGraphqlType  = "graphql.type"
)

I find this is more Go idiomatic, we can see this in the wild too, like in the http package constants.

return ctx, func(err *errors.QueryError) {
// this is necessary otherwise the span gets marked as an error
if err != nil {
span.Finish(tracer.WithError(err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can leave only this line. The error will not be applied if it is nil (see here). Have you experienced otherwise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's weird: https://play.golang.org/p/8JiXCGldIxi

type e struct{}

func (*e) Error() string { return "this works" }

func main() {
	var err error = (*e)(nil)
	fmt.Println(err, err == nil, err.Error())
}

It happens because an interface nil is different from a pointer nil. In this example calling the Error method actually still works though, and I guess that's why it's allowed.

I don't know if there's a better way to handle this upstream. We could do it with reflection, but that's probably not worth the computational overhead.

I can update the comment to make it more clear why its necessary if that would be helpful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. This could even cause us problems if people have the same expectations as me. It could result in unexpected errors where there are in fact none. There should be an issue on https://github.com/golang/go about this if there is not. I understand how this behavior is correct but can be misleading for errors. Possibly something for Go 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed its confusing. There's a FAQ entry and a proposal golang/go#22729

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! Maybe we can mention this issue in the comment:

// must explicitly check for nil, see issue golang/go#22729

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the FAQ this looks like a beginner mistake, so I'm not sure if there is anything to do here at all, other than perhaps not make people think that it's ok to pass "nil" errors to tracer.WithError.

}

// New creates a new Tracer.
func New(opts ...Option) trace.Tracer {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it wouldn't be better to call this NewTracer. The package is used as graphqltrace which is an action, so in the context of graphqltrace.New might indicate an action too, not a constructor. Maybe graphqltrace.NewTracer would be better and more in line with the graphql.Tracer function. Not feeling too strongly on it though.

type Option func(*config)

func defaults(cfg *config) {
cfg.serviceName = "graphql.server"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something outside the scope of this task, but I'm just letting you know that we have #273, which indicates that this is in some cases unexpected behavior. For example, if someone starts the tracer using:

tracer.Start(tracer.WithServiceName("my-service"))

And then later instruments a service, that should be the default service name. One solution could be to add an extra internal package at the root level to share configuration settings between the tracer package and integrations. It should be thread-safe.

type Query {
hello: String!
}
`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be nicer to have tracer := graphqltrace.NewTracer()? Just so we can isolate this for the sake of the example.

)

return ctx, func(errs []*errors.QueryError) {
var err error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could simplify this, one idea is:

switch n := len(errs); n {
    case 0:
        // err = nil
    case 1:
        err = errs[0]
    default:
        err = fmt.Errorf("%s (and %d more errors)", errs[0], n-1)
}

The fmt package formatter %s will correctly call the error interface's Error method to obtain the string.

@gbbr
Copy link
Contributor

gbbr commented Jul 31, 2018 via email

@dd-caleb
Copy link
Contributor Author

@gbbr thanks for the review. I made the changes you requested.

For the default service name, it's a little tricky, because we set a service name by default based on the application name. I think we need some way to distinguish between a user having set the service name and us providing the default. Maybe a WithDefaultServiceName in addition to WithServiceName and the former would basically never be used by users, but only by our integrations?

Anyway that's probably best tackled in another PR since all the existing integrations will need to be updated in some way.

Copy link
Contributor

@gbbr gbbr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right about the service name. My suggestion would also technically cause a breaking change. Let's keep it in #273

)

// A Tracer implements the graphql-go/trace.Tracer interface by sending traces
// to the Datadog tracer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do the ole var _ Tracer = (*trace.Tracer)(nil) package-level constant to assert and show that this interface is implemented? Any thoughts on that? I've been using it everywhere in this package - unsure if good or bad.

@@ -0,0 +1,17 @@
package graphql // import "gopkg.in/DataDog/dd-trace-go.v1/contrib/graph-gophers/graphql-go"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot about the package level documentation. Can you please add that and give it a shot on godoc? It might be more intuitive to use the file graphql.go. We might want to move the canonical import path comment there too as it might be the file where people will look intuitively to find these headers.

@dd-caleb dd-caleb merged commit d3b3fd2 into v1 Aug 1, 2018
@dd-caleb dd-caleb deleted the caleb/graphql branch August 1, 2018 13:39
@gbbr gbbr added this to the 1.1.0 milestone Aug 1, 2018
@gbbr gbbr added the apm:ecosystem contrib/* related feature requests or bugs label Aug 1, 2018
mingrammer pushed a commit to mingrammer/dd-trace-go that referenced this pull request Dec 22, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
apm:ecosystem contrib/* related feature requests or bugs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants