-
Notifications
You must be signed in to change notification settings - Fork 329
adds ClientTracer option to allow usage of httptrace package with ochttp #848
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// Copyright 2018, OpenCensus Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package ochttp | ||
|
||
import ( | ||
"crypto/tls" | ||
"net/http" | ||
"net/http/httptrace" | ||
"strings" | ||
|
||
"go.opencensus.io/trace" | ||
) | ||
|
||
// SpanAnnotator implements the annotation of all available hooks for | ||
// httptrace.ClientTrace. | ||
type SpanAnnotator struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why this needs to be public. If they want to use only a subset of the hooks, they can simply set the corresponding fields to nil in the returned ClientTrace. Likewise, if they want to use their own ClientTrace and copy across some of the fields, they can do this too by calling methods on ClientTrace directly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it is a matter of opinion on what is the friendlier method for consumers. Your preference is eliminating the hooks not desired: func NewSpanAnnotatorWrapper(req *http.Request, s *trace.Span) *httptrace.ClientTrace {
clientTrace := ochttp.NewSpanAnnotator(req, s)
// deregister unwanted hooks
clientTrace.GetConn = nil
clientTrace.Got100Continue = nil
...
return clientTrace
}
...
transport := ochttp.Transport{
NewClientTrace: NewSpanAnnotatorWrapper,
} Mine is choosing the hooks that are: func NewSpanAnnotator(req *http.Request, s *trace.Span) *httptrace.ClientTrace {
spanAnnotator := ochttp.SpanAnnotator{s}
// register wanted hooks
return &httptrace.ClientTrace{
GetConn: spanAnnotator.PutIdleConn,
GotFirstResponseByte: spanAnnotator.GotFirstResponseByte,
DNSStart: spanAnnotator.DNSStart,
...
}
}
...
transport := ochttp.Transport{
ClientTracer: NewSpanAnnotator,
} Which reminds me why I wanted SpanAnnotator to embed *trace.Span or otherwise have it be an exported property. Bear in mind that the actual resulting *httptrace.ClientTrace or SpanAnnotator is created and contained within ochttp's traceTransport.RoundTrip(). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still don't see why the type needs to be public. You could do this : t := &ochttp.Transport {
ClientTracer: func(r *http.Request, s *trace.Span) *httptrace.ClientTrace {
ann := ochttp.NewSpanAnnotator(r,s)
return &httptrace.ClientTrace {
GetConn: ann.GetConn
}
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a type adds complexity and distraction to the godoc There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I seemed to have forgotten because the function outputs *httptrace.ClientTrace I can always wrap it with a new one. Good thing vacation is coming up for some fresh eyes. Will not only unexport SpanAnnotator but all its methods can be unexported too. |
||
sp *trace.Span | ||
} | ||
|
||
// NewSpanAnnotator returns a httptrace.ClientTrace which annotates all emitted | ||
// httptrace events on the provided Span. | ||
func NewSpanAnnotator(req *http.Request, s *trace.Span) *httptrace.ClientTrace { | ||
spanAnnotator := SpanAnnotator{s} | ||
|
||
return &httptrace.ClientTrace{ | ||
GetConn: spanAnnotator.GetConn, | ||
GotConn: spanAnnotator.GotConn, | ||
PutIdleConn: spanAnnotator.PutIdleConn, | ||
GotFirstResponseByte: spanAnnotator.GotFirstResponseByte, | ||
Got100Continue: spanAnnotator.Got100Continue, | ||
DNSStart: spanAnnotator.DNSStart, | ||
DNSDone: spanAnnotator.DNSDone, | ||
ConnectStart: spanAnnotator.ConnectStart, | ||
ConnectDone: spanAnnotator.ConnectDone, | ||
TLSHandshakeStart: spanAnnotator.TLSHandshakeStart, | ||
TLSHandshakeDone: spanAnnotator.TLSHandshakeDone, | ||
WroteHeaders: spanAnnotator.WroteHeaders, | ||
Wait100Continue: spanAnnotator.Wait100Continue, | ||
WroteRequest: spanAnnotator.WroteRequest, | ||
} | ||
} | ||
|
||
// GetConn implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) GetConn(hostPort string) { | ||
attrs := []trace.Attribute{ | ||
trace.StringAttribute("httptrace.get_connection.host_port", hostPort), | ||
} | ||
s.sp.Annotate(attrs, "GetConn") | ||
} | ||
|
||
// GotConn implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) GotConn(info httptrace.GotConnInfo) { | ||
attrs := []trace.Attribute{ | ||
trace.BoolAttribute("httptrace.got_connection.reused", info.Reused), | ||
trace.BoolAttribute("httptrace.got_connection.was_idle", info.WasIdle), | ||
} | ||
if info.WasIdle { | ||
attrs = append(attrs, | ||
trace.StringAttribute("httptrace.got_connection.idle_time", info.IdleTime.String())) | ||
} | ||
s.sp.Annotate(attrs, "GotConn") | ||
} | ||
|
||
// PutIdleConn implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) PutIdleConn(err error) { | ||
var attrs []trace.Attribute | ||
if err != nil { | ||
attrs = append(attrs, | ||
trace.StringAttribute("httptrace.put_idle_connection.error", err.Error())) | ||
} | ||
s.sp.Annotate(attrs, "PutIdleConn") | ||
} | ||
|
||
// GotFirstResponseByte implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) GotFirstResponseByte() { | ||
s.sp.Annotate(nil, "GotFirstResponseByte") | ||
} | ||
|
||
// Got100Continue implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) Got100Continue() { | ||
s.sp.Annotate(nil, "Got100Continue") | ||
} | ||
|
||
// DNSStart implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) DNSStart(info httptrace.DNSStartInfo) { | ||
attrs := []trace.Attribute{ | ||
trace.StringAttribute("httptrace.dns_start.host", info.Host), | ||
} | ||
s.sp.Annotate(attrs, "DNSStart") | ||
} | ||
|
||
// DNSDone implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) DNSDone(info httptrace.DNSDoneInfo) { | ||
var addrs []string | ||
for _, addr := range info.Addrs { | ||
addrs = append(addrs, addr.String()) | ||
} | ||
attrs := []trace.Attribute{ | ||
trace.StringAttribute("httptrace.dns_done.addrs", strings.Join(addrs, " , ")), | ||
} | ||
if info.Err != nil { | ||
attrs = append(attrs, | ||
trace.StringAttribute("httptrace.dns_done.error", info.Err.Error())) | ||
} | ||
s.sp.Annotate(attrs, "DNSDone") | ||
} | ||
|
||
// ConnectStart implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) ConnectStart(network, addr string) { | ||
attrs := []trace.Attribute{ | ||
trace.StringAttribute("httptrace.connect_start.network", network), | ||
trace.StringAttribute("httptrace.connect_start.addr", addr), | ||
} | ||
s.sp.Annotate(attrs, "ConnectStart") | ||
} | ||
|
||
// ConnectDone implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) ConnectDone(network, addr string, err error) { | ||
attrs := []trace.Attribute{ | ||
trace.StringAttribute("httptrace.connect_done.network", network), | ||
trace.StringAttribute("httptrace.connect_done.addr", addr), | ||
} | ||
if err != nil { | ||
attrs = append(attrs, | ||
trace.StringAttribute("httptrace.connect_done.error", err.Error())) | ||
} | ||
s.sp.Annotate(attrs, "ConnectDone") | ||
} | ||
|
||
// TLSHandshakeStart implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) TLSHandshakeStart() { | ||
s.sp.Annotate(nil, "TLSHandshakeStart") | ||
} | ||
|
||
// TLSHandshakeDone implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) TLSHandshakeDone(_ tls.ConnectionState, err error) { | ||
var attrs []trace.Attribute | ||
if err != nil { | ||
attrs = append(attrs, | ||
trace.StringAttribute("httptrace.tls_handshake_done.error", err.Error())) | ||
} | ||
s.sp.Annotate(attrs, "TLSHandshakeDone") | ||
} | ||
|
||
// WroteHeaders implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) WroteHeaders() { | ||
s.sp.Annotate(nil, "WroteHeaders") | ||
} | ||
|
||
// Wait100Continue implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) Wait100Continue() { | ||
s.sp.Annotate(nil, "Wait100Continue") | ||
} | ||
|
||
// WroteRequest implements a httptrace.ClientTrace hook | ||
func (s SpanAnnotator) WroteRequest(info httptrace.WroteRequestInfo) { | ||
var attrs []trace.Attribute | ||
if info.Err != nil { | ||
attrs = append(attrs, | ||
trace.StringAttribute("httptrace.wrote_request.error", info.Err.Error())) | ||
} | ||
s.sp.Annotate(attrs, "WroteRequest") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright 2018, OpenCensus Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package ochttp_test | ||
|
||
import ( | ||
"errors" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"sync" | ||
"testing" | ||
|
||
"go.opencensus.io/plugin/ochttp" | ||
"go.opencensus.io/trace" | ||
) | ||
|
||
func TestSpanAnnotator(t *testing.T) { | ||
server := httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { | ||
resp.Write([]byte("Hello, world!")) | ||
})) | ||
defer server.Close() | ||
|
||
recorder := &testExporter{} | ||
|
||
trace.RegisterExporter(recorder) | ||
|
||
tr := ochttp.Transport{ClientTracer: ochttp.NewSpanAnnotator} | ||
|
||
req, err := http.NewRequest("POST", server.URL, strings.NewReader("req-body")) | ||
if err != nil { | ||
t.Errorf("error creating request: %v", err) | ||
} | ||
|
||
resp, err := tr.RoundTrip(req) | ||
if err != nil { | ||
t.Errorf("response error: %v", err) | ||
} | ||
if err := resp.Body.Close(); err != nil { | ||
t.Errorf("error closing response body: %v", err) | ||
} | ||
if got, want := resp.StatusCode, 200; got != want { | ||
t.Errorf("resp.StatusCode=%d; want=%d", got, want) | ||
} | ||
|
||
if got, want := len(recorder.spans), 1; got != want { | ||
t.Errorf("span count=%d; want=%d", got, want) | ||
} | ||
|
||
var annotations []string | ||
for _, annotation := range recorder.spans[0].Annotations { | ||
annotations = append(annotations, annotation.Message) | ||
} | ||
|
||
required := []string{ | ||
"GetConn", "GotConn", "GotFirstResponseByte", "ConnectStart", | ||
"ConnectDone", "WroteHeaders", "WroteRequest", | ||
} | ||
|
||
if errs := requiredAnnotations(required, annotations); len(errs) > 0 { | ||
for _, err := range errs { | ||
t.Error(err) | ||
} | ||
} | ||
|
||
} | ||
|
||
type testExporter struct { | ||
mu sync.Mutex | ||
spans []*trace.SpanData | ||
} | ||
|
||
func (t *testExporter) ExportSpan(s *trace.SpanData) { | ||
t.mu.Lock() | ||
t.spans = append(t.spans, s) | ||
t.mu.Unlock() | ||
} | ||
|
||
func requiredAnnotations(required []string, list []string) []error { | ||
var errs []error | ||
for _, item := range required { | ||
var found bool | ||
for _, v := range list { | ||
if v == item { | ||
found = true | ||
} | ||
} | ||
if !found { | ||
errs = append(errs, errors.New("missing expected annotation: "+item)) | ||
} | ||
} | ||
return errs | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point with the name is that this is not the ClientTrace itself - it is a constructor function for the ClientTrace - so I think the name "NewClientTrace" is more appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NewClientTrace doesn't sit well with me as a property of the Transport struct but I'll admit I see your point on ClientTracer being not great either. I'll use NewClientTrace.