| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 | // 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 ochttpimport (	"io"	"net/http"	"net/http/httptrace"	"go.opencensus.io/plugin/ochttp/propagation/b3"	"go.opencensus.io/trace"	"go.opencensus.io/trace/propagation")// TODO(jbd): Add godoc examples.var defaultFormat propagation.HTTPFormat = &b3.HTTPFormat{}// Attributes recorded on the span for the requests.// Only trace exporters will need them.const (	HostAttribute       = "http.host"	MethodAttribute     = "http.method"	PathAttribute       = "http.path"	URLAttribute        = "http.url"	UserAgentAttribute  = "http.user_agent"	StatusCodeAttribute = "http.status_code")type traceTransport struct {	base           http.RoundTripper	startOptions   trace.StartOptions	format         propagation.HTTPFormat	formatSpanName func(*http.Request) string	newClientTrace func(*http.Request, *trace.Span) *httptrace.ClientTrace}// TODO(jbd): Add message events for request and response size.// RoundTrip creates a trace.Span and inserts it into the outgoing request's headers.// The created span can follow a parent span, if a parent is presented in// the request's context.func (t *traceTransport) RoundTrip(req *http.Request) (*http.Response, error) {	name := t.formatSpanName(req)	// TODO(jbd): Discuss whether we want to prefix	// outgoing requests with Sent.	ctx, span := trace.StartSpan(req.Context(), name,		trace.WithSampler(t.startOptions.Sampler),		trace.WithSpanKind(trace.SpanKindClient))	if t.newClientTrace != nil {		req = req.WithContext(httptrace.WithClientTrace(ctx, t.newClientTrace(req, span)))	} else {		req = req.WithContext(ctx)	}	if t.format != nil {		// SpanContextToRequest will modify its Request argument, which is		// contrary to the contract for http.RoundTripper, so we need to		// pass it a copy of the Request.		// However, the Request struct itself was already copied by		// the WithContext calls above and so we just need to copy the header.		header := make(http.Header)		for k, v := range req.Header {			header[k] = v		}		req.Header = header		t.format.SpanContextToRequest(span.SpanContext(), req)	}	span.AddAttributes(requestAttrs(req)...)	resp, err := t.base.RoundTrip(req)	if err != nil {		span.SetStatus(trace.Status{Code: trace.StatusCodeUnknown, Message: err.Error()})		span.End()		return resp, err	}	span.AddAttributes(responseAttrs(resp)...)	span.SetStatus(TraceStatus(resp.StatusCode, resp.Status))	// span.End() will be invoked after	// a read from resp.Body returns io.EOF or when	// resp.Body.Close() is invoked.	bt := &bodyTracker{rc: resp.Body, span: span}	resp.Body = wrappedBody(bt, resp.Body)	return resp, err}// bodyTracker wraps a response.Body and invokes// trace.EndSpan on encountering io.EOF on reading// the body of the original response.type bodyTracker struct {	rc   io.ReadCloser	span *trace.Span}var _ io.ReadCloser = (*bodyTracker)(nil)func (bt *bodyTracker) Read(b []byte) (int, error) {	n, err := bt.rc.Read(b)	switch err {	case nil:		return n, nil	case io.EOF:		bt.span.End()	default:		// For all other errors, set the span status		bt.span.SetStatus(trace.Status{			// Code 2 is the error code for Internal server error.			Code:    2,			Message: err.Error(),		})	}	return n, err}func (bt *bodyTracker) Close() error {	// Invoking endSpan on Close will help catch the cases	// in which a read returned a non-nil error, we set the	// span status but didn't end the span.	bt.span.End()	return bt.rc.Close()}// CancelRequest cancels an in-flight request by closing its connection.func (t *traceTransport) CancelRequest(req *http.Request) {	type canceler interface {		CancelRequest(*http.Request)	}	if cr, ok := t.base.(canceler); ok {		cr.CancelRequest(req)	}}func spanNameFromURL(req *http.Request) string {	return req.URL.Path}func requestAttrs(r *http.Request) []trace.Attribute {	userAgent := r.UserAgent()	attrs := make([]trace.Attribute, 0, 5)	attrs = append(attrs,		trace.StringAttribute(PathAttribute, r.URL.Path),		trace.StringAttribute(URLAttribute, r.URL.String()),		trace.StringAttribute(HostAttribute, r.Host),		trace.StringAttribute(MethodAttribute, r.Method),	)	if userAgent != "" {		attrs = append(attrs, trace.StringAttribute(UserAgentAttribute, userAgent))	}	return attrs}func responseAttrs(resp *http.Response) []trace.Attribute {	return []trace.Attribute{		trace.Int64Attribute(StatusCodeAttribute, int64(resp.StatusCode)),	}}// TraceStatus is a utility to convert the HTTP status code to a trace.Status that// represents the outcome as closely as possible.func TraceStatus(httpStatusCode int, statusLine string) trace.Status {	var code int32	if httpStatusCode < 200 || httpStatusCode >= 400 {		code = trace.StatusCodeUnknown	}	switch httpStatusCode {	case 499:		code = trace.StatusCodeCancelled	case http.StatusBadRequest:		code = trace.StatusCodeInvalidArgument	case http.StatusGatewayTimeout:		code = trace.StatusCodeDeadlineExceeded	case http.StatusNotFound:		code = trace.StatusCodeNotFound	case http.StatusForbidden:		code = trace.StatusCodePermissionDenied	case http.StatusUnauthorized: // 401 is actually unauthenticated.		code = trace.StatusCodeUnauthenticated	case http.StatusTooManyRequests:		code = trace.StatusCodeResourceExhausted	case http.StatusNotImplemented:		code = trace.StatusCodeUnimplemented	case http.StatusServiceUnavailable:		code = trace.StatusCodeUnavailable	case http.StatusOK:		code = trace.StatusCodeOK	}	return trace.Status{Code: code, Message: codeToStr[code]}}var codeToStr = map[int32]string{	trace.StatusCodeOK:                 `OK`,	trace.StatusCodeCancelled:          `CANCELLED`,	trace.StatusCodeUnknown:            `UNKNOWN`,	trace.StatusCodeInvalidArgument:    `INVALID_ARGUMENT`,	trace.StatusCodeDeadlineExceeded:   `DEADLINE_EXCEEDED`,	trace.StatusCodeNotFound:           `NOT_FOUND`,	trace.StatusCodeAlreadyExists:      `ALREADY_EXISTS`,	trace.StatusCodePermissionDenied:   `PERMISSION_DENIED`,	trace.StatusCodeResourceExhausted:  `RESOURCE_EXHAUSTED`,	trace.StatusCodeFailedPrecondition: `FAILED_PRECONDITION`,	trace.StatusCodeAborted:            `ABORTED`,	trace.StatusCodeOutOfRange:         `OUT_OF_RANGE`,	trace.StatusCodeUnimplemented:      `UNIMPLEMENTED`,	trace.StatusCodeInternal:           `INTERNAL`,	trace.StatusCodeUnavailable:        `UNAVAILABLE`,	trace.StatusCodeDataLoss:           `DATA_LOSS`,	trace.StatusCodeUnauthenticated:    `UNAUTHENTICATED`,}func isHealthEndpoint(path string) bool {	// Health checking is pretty frequent and	// traces collected for health endpoints	// can be extremely noisy and expensive.	// Disable canonical health checking endpoints	// like /healthz and /_ah/health for now.	if path == "/healthz" || path == "/_ah/health" {		return true	}	return false}
 |