opentracing -> opencensus (#802)

* update vendor directory, add go.opencensus.io

* update imports

* oops

* s/opentracing/opencensus/ & remove prometheus / zipkin stuff & remove old stats

* the dep train rides again

* fix gin build

* deps from last guy

* start in on the agent metrics

* she builds

* remove tags for now, cardinality error is fussing. subscribe instead of register

* update to patched version of opencensus to proceed for now TODO switch to a release

* meh

fix imports

* println debug the bad boys

* lace it with the tags

* update deps again

* fix all inconsistent cardinality errors

* add our own logger

* fix init

* fix oom measure

* remove bugged removal code

* fix s3 measures

* fix prom handler nil
This commit is contained in:
Reed Allman
2018-03-05 09:35:28 -08:00
committed by GitHub
parent 924d27559c
commit 206aa3c203
5975 changed files with 158755 additions and 566592 deletions

View File

@@ -0,0 +1,131 @@
package http
import (
"errors"
"net/http"
"strconv"
"time"
zipkin "github.com/openzipkin/zipkin-go"
"github.com/openzipkin/zipkin-go/model"
)
// ErrValidTracerRequired error
var ErrValidTracerRequired = errors.New("valid tracer required")
// Client holds a Zipkin instrumented HTTP Client.
type Client struct {
*http.Client
tracer *zipkin.Tracer
httpTrace bool
defaultTags map[string]string
transportOptions []TransportOption
}
// ClientOption allows optional configuration of Client.
type ClientOption func(*Client)
// WithClient allows one to add a custom configured http.Client to use.
func WithClient(client *http.Client) ClientOption {
return func(c *Client) {
if client == nil {
client = &http.Client{}
}
c.Client = client
}
}
// ClientTrace allows one to enable Go's net/http/httptrace.
func ClientTrace(enabled bool) ClientOption {
return func(c *Client) {
c.httpTrace = enabled
}
}
// ClientTags adds default Tags to inject into client application spans.
func ClientTags(tags map[string]string) ClientOption {
return func(c *Client) {
c.defaultTags = tags
}
}
// TransportOptions passes optional Transport configuration to the internal
// transport used by Client.
func TransportOptions(options ...TransportOption) ClientOption {
return func(c *Client) {
c.transportOptions = options
}
}
// NewClient returns an HTTP Client adding Zipkin instrumentation around an
// embedded standard Go http.Client.
func NewClient(tracer *zipkin.Tracer, options ...ClientOption) (*Client, error) {
if tracer == nil {
return nil, ErrValidTracerRequired
}
c := &Client{tracer: tracer, Client: &http.Client{}}
for _, option := range options {
option(c)
}
c.transportOptions = append(
c.transportOptions,
// the following Client settings override provided transport settings.
RoundTripper(c.Client.Transport),
TransportTrace(c.httpTrace),
)
transport, err := NewTransport(tracer, c.transportOptions...)
if err != nil {
return nil, err
}
c.Client.Transport = transport
return c, nil
}
// DoWithAppSpan wraps http.Client's Do with tracing using an application span.
func (c *Client) DoWithAppSpan(req *http.Request, name string) (res *http.Response, err error) {
var parentContext model.SpanContext
if span := zipkin.SpanFromContext(req.Context()); span != nil {
parentContext = span.Context()
}
appSpan := c.tracer.StartSpan(name, zipkin.Parent(parentContext))
zipkin.TagHTTPMethod.Set(appSpan, req.Method)
zipkin.TagHTTPUrl.Set(appSpan, req.URL.String())
zipkin.TagHTTPPath.Set(appSpan, req.URL.Path)
res, err = c.Client.Do(
req.WithContext(zipkin.NewContext(req.Context(), appSpan)),
)
if err != nil {
zipkin.TagError.Set(appSpan, err.Error())
appSpan.Finish()
return
}
if c.httpTrace {
appSpan.Annotate(time.Now(), "wr")
}
if res.ContentLength > 0 {
zipkin.TagHTTPResponseSize.Set(appSpan, strconv.FormatInt(res.ContentLength, 10))
}
if res.StatusCode < 200 || res.StatusCode > 299 {
statusCode := strconv.FormatInt(int64(res.StatusCode), 10)
zipkin.TagHTTPStatusCode.Set(appSpan, statusCode)
if res.StatusCode > 399 {
zipkin.TagError.Set(appSpan, statusCode)
}
}
res.Body = &spanCloser{
ReadCloser: res.Body,
sp: appSpan,
traceEnabled: c.httpTrace,
}
return
}

View File

@@ -0,0 +1,81 @@
package http_test
import (
"net/http"
"testing"
zipkin "github.com/openzipkin/zipkin-go"
httpclient "github.com/openzipkin/zipkin-go/middleware/http"
"github.com/openzipkin/zipkin-go/reporter/recorder"
)
func TestHTTPClient(t *testing.T) {
reporter := recorder.NewReporter()
defer reporter.Close()
ep, _ := zipkin.NewEndpoint("httpClient", "")
tracer, err := zipkin.NewTracer(reporter, zipkin.WithLocalEndpoint(ep))
if err != nil {
t.Fatalf("unable to create tracer: %+v", err)
}
clientTags := map[string]string{
"client": "testClient",
}
transportTags := map[string]string{
"conf.timeout": "default",
}
client, err := httpclient.NewClient(
tracer,
httpclient.WithClient(&http.Client{}),
httpclient.ClientTrace(true),
httpclient.ClientTags(clientTags),
httpclient.TransportOptions(httpclient.TransportTags(transportTags)),
)
if err != nil {
t.Fatalf("unable to create http client: %+v", err)
}
req, _ := http.NewRequest("GET", "https://www.google.com", nil)
res, err := client.DoWithAppSpan(req, "Get Google")
if err != nil {
t.Fatalf("unable to execute client request: %+v", err)
}
res.Body.Close()
spans := reporter.Flush()
if len(spans) < 2 {
t.Errorf("Span Count want 2+, have %d", len(spans))
}
req, _ = http.NewRequest("GET", "https://www.google.com", nil)
res, err = client.Do(req)
if err != nil {
t.Fatalf("unable to execute client request: %+v", err)
}
res.Body.Close()
spans = reporter.Flush()
if len(spans) == 0 {
t.Errorf("Span Count want 1+, have 0")
}
span := tracer.StartSpan("ParentSpan")
req, _ = http.NewRequest("GET", "http://www.google.com", nil)
ctx := zipkin.NewContext(req.Context(), span)
req = req.WithContext(ctx)
res, err = client.DoWithAppSpan(req, "ChildSpan")
if err != nil {
t.Fatalf("unable to execute client request: %+v", err)
}
res.Body.Close()
}

View File

@@ -0,0 +1,5 @@
/*
Package http contains several http middlewares which can be used for
instrumenting calls with Zipkin.
*/
package http

View File

@@ -0,0 +1,149 @@
package http
import (
"net/http"
"strconv"
"sync/atomic"
zipkin "github.com/openzipkin/zipkin-go"
"github.com/openzipkin/zipkin-go/model"
"github.com/openzipkin/zipkin-go/propagation/b3"
)
type handler struct {
tracer *zipkin.Tracer
name string
next http.Handler
tagResponseSize bool
defaultTags map[string]string
}
// ServerOption allows Middleware to be optionally configured.
type ServerOption func(*handler)
// ServerTags adds default Tags to inject into server spans.
func ServerTags(tags map[string]string) ServerOption {
return func(h *handler) {
h.defaultTags = tags
}
}
// TagResponseSize will instruct the middleware to Tag the http response size
// in the server side span.
func TagResponseSize(enabled bool) ServerOption {
return func(h *handler) {
h.tagResponseSize = enabled
}
}
// SpanName sets the name of the spans the middleware creates. Use this if
// wrapping each endpoint with its own Middleware.
// If omitting the SpanName option, the middleware will use the http request
// method as span name.
func SpanName(name string) ServerOption {
return func(h *handler) {
h.name = name
}
}
// NewServerMiddleware returns a http.Handler middleware with Zipkin tracing.
func NewServerMiddleware(t *zipkin.Tracer, options ...ServerOption) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
h := &handler{
tracer: t,
next: next,
}
for _, option := range options {
option(h)
}
return h
}
}
// ServeHTTP implements http.Handler.
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var spanName string
// try to extract B3 Headers from upstream
sc := h.tracer.Extract(b3.ExtractHTTP(r))
remoteEndpoint, _ := zipkin.NewEndpoint("", r.RemoteAddr)
if len(h.name) == 0 {
spanName = r.Method
} else {
spanName = h.name
}
// create Span using SpanContext if found
sp := h.tracer.StartSpan(
spanName,
zipkin.Kind(model.Server),
zipkin.Parent(sc),
zipkin.RemoteEndpoint(remoteEndpoint),
)
for k, v := range h.defaultTags {
sp.Tag(k, v)
}
// add our span to context
ctx := zipkin.NewContext(r.Context(), sp)
// tag typical HTTP request items
zipkin.TagHTTPMethod.Set(sp, r.Method)
zipkin.TagHTTPUrl.Set(sp, r.URL.String())
zipkin.TagHTTPRequestSize.Set(sp, strconv.FormatInt(r.ContentLength, 10))
// create http.ResponseWriter interceptor for tracking response size and
// status code.
ri := &rwInterceptor{w: w, statusCode: 200}
// tag found response size and status code on exit
defer func() {
code := ri.getStatusCode()
sCode := strconv.Itoa(code)
if code > 399 {
zipkin.TagError.Set(sp, sCode)
}
zipkin.TagHTTPStatusCode.Set(sp, sCode)
if h.tagResponseSize {
zipkin.TagHTTPResponseSize.Set(sp, ri.getResponseSize())
}
sp.Finish()
}()
// call next http Handler func using our updated context.
h.next.ServeHTTP(ri, r.WithContext(ctx))
}
// rwInterceptor intercepts the ResponseWriter so it can track response size
// and returned status code.
type rwInterceptor struct {
w http.ResponseWriter
size uint64
statusCode int
}
func (r *rwInterceptor) Header() http.Header {
return r.w.Header()
}
func (r *rwInterceptor) Write(b []byte) (n int, err error) {
n, err = r.w.Write(b)
atomic.AddUint64(&r.size, uint64(n))
return
}
func (r *rwInterceptor) WriteHeader(i int) {
r.statusCode = i
r.w.WriteHeader(i)
}
func (r *rwInterceptor) getStatusCode() int {
return r.statusCode
}
func (r *rwInterceptor) getResponseSize() string {
return strconv.FormatUint(atomic.LoadUint64(&r.size), 10)
}

View File

@@ -0,0 +1,141 @@
package http_test
import (
"bytes"
"net/http"
"net/http/httptest"
"strconv"
"testing"
zipkin "github.com/openzipkin/zipkin-go"
mw "github.com/openzipkin/zipkin-go/middleware/http"
"github.com/openzipkin/zipkin-go/reporter/recorder"
)
var (
lep, _ = zipkin.NewEndpoint("testSvc", "127.0.0.1:0")
)
func httpHandler(code int, headers http.Header, body *bytes.Buffer) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(code)
for key, value := range headers {
w.Header().Add(key, value[0])
}
w.Write(body.Bytes())
}
}
func TestHTTPHandlerWrapping(t *testing.T) {
var (
spanRecorder = &recorder.ReporterRecorder{}
tr, _ = zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep))
httpRecorder = httptest.NewRecorder()
requestBuf = bytes.NewBufferString("incoming data")
responseBuf = bytes.NewBufferString("oh oh we have a 404")
headers = make(http.Header)
spanName = "wrapper_test"
code = 404
)
headers.Add("some-key", "some-value")
headers.Add("other-key", "other-value")
request, err := http.NewRequest("POST", "/test", requestBuf)
if err != nil {
t.Fatalf("unable to create request")
}
httpHandlerFunc := http.HandlerFunc(httpHandler(code, headers, responseBuf))
tags := map[string]string{
"component": "testServer",
}
handler := mw.NewServerMiddleware(
tr,
mw.SpanName(spanName),
mw.TagResponseSize(true),
mw.ServerTags(tags),
)(httpHandlerFunc)
handler.ServeHTTP(httpRecorder, request)
spans := spanRecorder.Flush()
if want, have := 1, len(spans); want != have {
t.Errorf("Expected %d spans, got %d", want, have)
}
span := spans[0]
if want, have := spanName, span.Name; want != have {
t.Errorf("Expected span name %s, got %s", want, have)
}
if want, have := strconv.Itoa(requestBuf.Len()), span.Tags["http.request.size"]; want != have {
t.Errorf("Expected span request size %s, got %s", want, have)
}
if want, have := strconv.Itoa(responseBuf.Len()), span.Tags["http.response.size"]; want != have {
t.Errorf("Expected span response size %s, got %s", want, have)
}
if want, have := strconv.Itoa(code), span.Tags["http.status_code"]; want != have {
t.Errorf("Expected span status code %s, got %s", want, have)
}
if want, have := strconv.Itoa(code), span.Tags["error"]; want != have {
t.Errorf("Expected span error %q, got %q", want, have)
}
if want, have := len(headers), len(httpRecorder.HeaderMap); want != have {
t.Errorf("Expected http header count %d, got %d", want, have)
}
if want, have := code, httpRecorder.Code; want != have {
t.Errorf("Expected http status code %d, got %d", want, have)
}
for key, value := range headers {
if want, have := value, httpRecorder.HeaderMap.Get(key); want[0] != have {
t.Errorf("Expected header %s value %s, got %s", key, want, have)
}
}
if want, have := responseBuf.String(), httpRecorder.Body.String(); want != have {
t.Errorf("Expected body value %q, got %q", want, have)
}
}
func TestHTTPDefaultSpanName(t *testing.T) {
var (
spanRecorder = &recorder.ReporterRecorder{}
tr, _ = zipkin.NewTracer(spanRecorder, zipkin.WithLocalEndpoint(lep))
httpRecorder = httptest.NewRecorder()
requestBuf = bytes.NewBufferString("incoming data")
methodType = "POST"
)
request, err := http.NewRequest(methodType, "/test", requestBuf)
if err != nil {
t.Fatalf("unable to create request")
}
httpHandlerFunc := http.HandlerFunc(httpHandler(200, nil, bytes.NewBufferString("")))
handler := mw.NewServerMiddleware(tr)(httpHandlerFunc)
handler.ServeHTTP(httpRecorder, request)
spans := spanRecorder.Flush()
if want, have := 1, len(spans); want != have {
t.Errorf("Expected %d spans, got %d", want, have)
}
span := spans[0]
if want, have := methodType, span.Name; want != have {
t.Errorf("Expected span name %s, got %s", want, have)
}
}

View File

@@ -0,0 +1,23 @@
package http
import (
"io"
"time"
zipkin "github.com/openzipkin/zipkin-go"
)
type spanCloser struct {
io.ReadCloser
sp zipkin.Span
traceEnabled bool
}
func (s *spanCloser) Close() (err error) {
if s.traceEnabled {
s.sp.Annotate(time.Now(), "Body Close")
}
err = s.ReadCloser.Close()
s.sp.Finish()
return
}

View File

@@ -0,0 +1,103 @@
package http
import (
"crypto/tls"
"fmt"
"net/http/httptrace"
"strings"
"time"
zipkin "github.com/openzipkin/zipkin-go"
)
type spanTrace struct {
zipkin.Span
c *httptrace.ClientTrace
}
func (s *spanTrace) getConn(hostPort string) {
s.Annotate(time.Now(), "Connecting")
s.Tag("httptrace.get_connection.host_port", hostPort)
}
func (s *spanTrace) gotConn(info httptrace.GotConnInfo) {
s.Annotate(time.Now(), "Connected")
s.Tag("httptrace.got_connection.reused", fmt.Sprintf("%t", info.Reused))
s.Tag("httptrace.got_connection.was_idle", fmt.Sprintf("%t", info.WasIdle))
if info.WasIdle {
s.Tag("httptrace.got_connection.idle_time", info.IdleTime.String())
}
}
func (s *spanTrace) putIdleConn(err error) {
s.Annotate(time.Now(), "Put Idle Connection")
if err != nil {
s.Tag("httptrace.put_idle_connection.error", err.Error())
}
}
func (s *spanTrace) gotFirstResponseByte() {
s.Annotate(time.Now(), "First Response Byte")
}
func (s *spanTrace) got100Continue() {
s.Annotate(time.Now(), "Got 100 Continue")
}
func (s *spanTrace) dnsStart(info httptrace.DNSStartInfo) {
s.Annotate(time.Now(), "DNS Start")
s.Tag("httptrace.dns_start.host", info.Host)
}
func (s *spanTrace) dnsDone(info httptrace.DNSDoneInfo) {
s.Annotate(time.Now(), "DNS Done")
var addrs []string
for _, addr := range info.Addrs {
addrs = append(addrs, addr.String())
}
s.Tag("httptrace.dns_done.addrs", strings.Join(addrs, " , "))
if info.Err != nil {
s.Tag("httptrace.dns_done.error", info.Err.Error())
}
}
func (s *spanTrace) connectStart(network, addr string) {
s.Annotate(time.Now(), "Connect Start")
s.Tag("httptrace.connect_start.network", network)
s.Tag("httptrace.connect_start.addr", addr)
}
func (s *spanTrace) connectDone(network, addr string, err error) {
s.Annotate(time.Now(), "Connect Done")
s.Tag("httptrace.connect_done.network", network)
s.Tag("httptrace.connect_done.addr", addr)
if err != nil {
s.Tag("httptrace.connect_done.error", err.Error())
}
}
func (s *spanTrace) tlsHandshakeStart() {
s.Annotate(time.Now(), "TLS Handshake Start")
}
func (s *spanTrace) tlsHandshakeDone(_ tls.ConnectionState, err error) {
s.Annotate(time.Now(), "TLS Handshake Done")
if err != nil {
s.Tag("httptrace.tls_handshake_done.error", err.Error())
}
}
func (s *spanTrace) wroteHeaders() {
s.Annotate(time.Now(), "Wrote Headers")
}
func (s *spanTrace) wait100Continue() {
s.Annotate(time.Now(), "Wait 100 Continue")
}
func (s *spanTrace) wroteRequest(info httptrace.WroteRequestInfo) {
s.Annotate(time.Now(), "Wrote Request")
if info.Err != nil {
s.Tag("httptrace.wrote_request.error", info.Err.Error())
}
}

View File

@@ -0,0 +1,128 @@
package http
import (
"net/http"
"net/http/httptrace"
"strconv"
zipkin "github.com/openzipkin/zipkin-go"
"github.com/openzipkin/zipkin-go/model"
"github.com/openzipkin/zipkin-go/propagation/b3"
)
type transport struct {
tracer *zipkin.Tracer
rt http.RoundTripper
httpTrace bool
defaultTags map[string]string
}
// TransportOption allows one to configure optional transport configuration.
type TransportOption func(*transport)
// RoundTripper adds the Transport RoundTripper to wrap.
func RoundTripper(rt http.RoundTripper) TransportOption {
return func(t *transport) {
if rt != nil {
t.rt = rt
}
}
}
// TransportTags adds default Tags to inject into transport spans.
func TransportTags(tags map[string]string) TransportOption {
return func(t *transport) {
t.defaultTags = tags
}
}
// TransportTrace allows one to enable Go's net/http/httptrace.
func TransportTrace(enable bool) TransportOption {
return func(t *transport) {
t.httpTrace = enable
}
}
// NewTransport returns a new Zipkin instrumented http RoundTripper which can be
// used with a standard library http Client.
func NewTransport(tracer *zipkin.Tracer, options ...TransportOption) (http.RoundTripper, error) {
if tracer == nil {
return nil, ErrValidTracerRequired
}
t := &transport{
tracer: tracer,
rt: http.DefaultTransport,
httpTrace: false,
}
for _, option := range options {
option(t)
}
return t, nil
}
// RoundTrip satisfies the RoundTripper interface.
func (t *transport) RoundTrip(req *http.Request) (res *http.Response, err error) {
sp, _ := t.tracer.StartSpanFromContext(
req.Context(), req.URL.Scheme+"/"+req.Method, zipkin.Kind(model.Client),
)
for k, v := range t.defaultTags {
sp.Tag(k, v)
}
if t.httpTrace {
sptr := spanTrace{
Span: sp,
}
sptr.c = &httptrace.ClientTrace{
GetConn: sptr.getConn,
GotConn: sptr.gotConn,
PutIdleConn: sptr.putIdleConn,
GotFirstResponseByte: sptr.gotFirstResponseByte,
Got100Continue: sptr.got100Continue,
DNSStart: sptr.dnsStart,
DNSDone: sptr.dnsDone,
ConnectStart: sptr.connectStart,
ConnectDone: sptr.connectDone,
TLSHandshakeStart: sptr.tlsHandshakeStart,
TLSHandshakeDone: sptr.tlsHandshakeDone,
WroteHeaders: sptr.wroteHeaders,
Wait100Continue: sptr.wait100Continue,
WroteRequest: sptr.wroteRequest,
}
req = req.WithContext(
httptrace.WithClientTrace(req.Context(), sptr.c),
)
}
zipkin.TagHTTPMethod.Set(sp, req.Method)
zipkin.TagHTTPUrl.Set(sp, req.URL.String())
zipkin.TagHTTPPath.Set(sp, req.URL.Path)
_ = b3.InjectHTTP(req)(sp.Context())
res, err = t.rt.RoundTrip(req)
if err != nil {
zipkin.TagError.Set(sp, err.Error())
sp.Finish()
return
}
if res.ContentLength > 0 {
zipkin.TagHTTPResponseSize.Set(sp, strconv.FormatInt(res.ContentLength, 10))
}
if res.StatusCode < 200 || res.StatusCode > 299 {
statusCode := strconv.FormatInt(int64(res.StatusCode), 10)
zipkin.TagHTTPStatusCode.Set(sp, statusCode)
if res.StatusCode > 399 {
zipkin.TagError.Set(sp, statusCode)
}
}
sp.Finish()
return
}