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/k8s.io/client-go: add kubernetes integration, round-tripper #298

Merged
merged 5 commits into from
Aug 8, 2018

Conversation

dd-caleb
Copy link
Contributor

@dd-caleb dd-caleb commented Aug 3, 2018

Fixes #297

I thought that perhaps the easiest way to instrument Kubernetes would be to provide an http.RoundTripper with a couple extra integration points via Options:

	rt := httptrace.WrapRoundTripper(http.DefaultTransport,
		httptrace.WithRoundTripperBefore(func(req *http.Request, span ddtrace.Span) {
			span.SetTag("CalledBefore", true)
		}),
		httptrace.WithRoundTripperAfter(func(res *http.Response, span ddtrace.Span) {
			span.SetTag("CalledAfter", true)
		}))

This would supersede: #172

@dd-caleb dd-caleb added apm:ecosystem contrib/* related feature requests or bugs feature-request labels Aug 3, 2018
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.

Thanks. Left an initial round of comments

)

const (
apiPrefix = "/api/v1/"
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 same here as we did in the other PRs?

const (
    prefixApi
    prefixWatch
    prefixNamespaces
)

Let me know if you have differing thoughts about this approach.

if !strings.HasPrefix(path, apiPrefix) {
return resourceName
}
path = path[len(apiPrefix):]
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 path = strings.TrimPrefix(path, prefixAPI). Do you see any problems with that? It's clearer and has the same effect.


// strip out /watch
if strings.HasPrefix(path, watchPrefix) {
path = path[len(watchPrefix):]
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use path = strings.TrimPrefix(path, prefixWatch) here too?

err = errors.New(res.Status)
}
}
if rt.cfg.after != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

How would you feel about the following change?

  • We put this in a defer clause right after the before counter-part is called (is this a performance concern?)
  • We name the error return value of RoundTrip, capture it in the defer and pass it to the after func
  • The default after func sets the error on the span (we don't set it in Finish).

If people want to skip errors or selectively decide which ones to take into account, they can always make their own "after func"

On the other hand, if this is a performance concern, let's leave it like this (but do set the error by default). If the need arises to work with errors we can just provide a new option to hook into them.

if rt.cfg.after != nil {
rt.cfg.after(res, span)
}
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.

Can we defer this too at the top? After the span starts (or in the same defer as the "after func" in you do that).

cfg.before = f
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need WithServiceName? I realize people can just make a short "before" or "after" func for it but maybe it would be nice to simplify this and also keep it matching our other contribs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

WithServiceName already exists for the MuxOption

Copy link
Contributor

Choose a reason for hiding this comment

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

I can't easily figure out how MuxOption connects to WrapRoundTripper, which uses RoundTripperOption. There is no service set in RoundTrip currently, it will simply use whatever was set on the tracer. If nothing is set, the span will be refused by the agent. Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't create a WithServiceName function for RoundTripperOption because it's already used by the MuxOption which is in the same package. Should I create a WithRoundTripperServiceName function?

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 not sure. Would UseServiceName be bad?

assert.Len(t, spans, 2)
assert.Equal(t, spans[0].TraceID(), spans[1].TraceID())

{
Copy link
Contributor

Choose a reason for hiding this comment

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

I saw we've started using this pattern. I see it a bit as a "lazy mode to create a closure". Maybe I'm not looking at it the right way? I'm wondering if we can't just use a for loop and sub-tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I follow about the for loop... the things being tested for aren't the same for each span.

I used a block to re-use the s variable. I can change it to s1, s2.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd prefer it without the closure if possible. But I am open to considering it. I've been generally avoiding using it.


func (rt *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
span, _ := tracer.StartSpanFromContext(req.Context(), "http.request",
tracer.ResourceName(defaultResourceName),
Copy link
Contributor

Choose a reason for hiding this comment

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

We're going to need to set the right type for the rest of our pipeline, this is an important part:

tracer.SpanType(ext.SpanTypeHTTP)

When the span type is set on a root span, it classifies the service in the application and enables making use of other systems such as obfuscation, quantization, etc.

// {type}/{name}
lastType := ""
for i := 0; ; i++ {
idx := strings.IndexByte(path, '/')
Copy link
Contributor

Choose a reason for hiding this comment

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

This solution is very low level. Can we make use of the strings package more? For example:

parts := strings.Split(path, "/") // n := len(parts)
for i, p := range parts {

}

func requestToResource(method, path string) string {
resourceName := method + " "
Copy link
Contributor

@gbbr gbbr Aug 6, 2018

Choose a reason for hiding this comment

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

Instead of repeatedly concatenating string constants, can we use strings.Builder here?

if !strings.HasPrefix(path, prefixApi) {
    return method
}
var out strings.Builder
out.WriteString(method)
out.WriteByte(' ')
// ...
return out.String()

@gbbr gbbr added this to the next milestone Aug 6, 2018
@gbbr
Copy link
Contributor

gbbr commented Aug 6, 2018

Fixes #172, right?

@gbbr
Copy link
Contributor

gbbr commented Aug 6, 2018

This is basically 2 PRs, do you think we could split it? The reason is that it turns out to be very helpful to do that when putting together a release change log. Each commit message is a clear one line description of the affected package and the change, as well as a link to a PR. This makes it really easy to cover everything when releasing. It also makes it easier to not miss anything when navigating commit history.

I'd also be fine with two commits, except that would create an extra merge commit, but I'd be happy with that compromise if you prefer this alternative.

…atch upstream changes, use a string builder for resource naming, refactor loop to use Split instead of IndexByte
@dd-caleb
Copy link
Contributor Author

dd-caleb commented Aug 8, 2018

@gbbr The http changes were split out and merged to v1. I think this PR should be ready to merge. Let me know if you see any additional problems. Thanks.


path = strings.TrimPrefix(path, prefixAPI)

// strip out /watch
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we put this comment inside the if statement? That's what I've been doing all around. It makes sense (to me) because that's where the action happens (inside the if statement).

}

// {type}/{name}
lastType := ""
Copy link
Contributor

Choose a reason for hiding this comment

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

For "zero values" can we use?

var lastType string

@dd-caleb dd-caleb merged commit 7599e40 into v1 Aug 8, 2018
@dd-caleb dd-caleb deleted the caleb/kubernetes branch August 8, 2018 14:08
@gbbr gbbr modified the milestones: next, 1.2.0 Aug 22, 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