...

Source file src/github.com/go-kit/kit/tracing/zipkin/http.go

Documentation: github.com/go-kit/kit/tracing/zipkin

     1  package zipkin
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"strconv"
     7  
     8  	zipkin "github.com/openzipkin/zipkin-go"
     9  	"github.com/openzipkin/zipkin-go/model"
    10  	"github.com/openzipkin/zipkin-go/propagation/b3"
    11  
    12  	kithttp "github.com/go-kit/kit/transport/http"
    13  	"github.com/go-kit/log"
    14  )
    15  
    16  // HTTPClientTrace enables native Zipkin tracing of a Go kit HTTP transport
    17  // Client.
    18  //
    19  // Go kit creates HTTP transport clients per remote endpoint. This middleware
    20  // can be set-up individually by adding the endpoint name for each of the Go kit
    21  // transport clients using the Name() TracerOption.
    22  // If wanting to use the HTTP Method (Get, Post, Put, etc.) as Span name you can
    23  // create a global client tracer omitting the Name() TracerOption, which you can
    24  // then feed to each Go kit transport client.
    25  // If instrumenting a client to an external (not on your platform) service, you
    26  // will probably want to disallow propagation of SpanContext using the
    27  // AllowPropagation TracerOption and setting it to false.
    28  func HTTPClientTrace(tracer *zipkin.Tracer, options ...TracerOption) kithttp.ClientOption {
    29  	config := tracerOptions{
    30  		tags:      make(map[string]string),
    31  		name:      "",
    32  		logger:    log.NewNopLogger(),
    33  		propagate: true,
    34  	}
    35  
    36  	for _, option := range options {
    37  		option(&config)
    38  	}
    39  
    40  	clientBefore := kithttp.ClientBefore(
    41  		func(ctx context.Context, req *http.Request) context.Context {
    42  			var (
    43  				spanContext model.SpanContext
    44  				name        string
    45  			)
    46  
    47  			if config.name != "" {
    48  				name = config.name
    49  			} else {
    50  				name = req.Method
    51  			}
    52  
    53  			if parent := zipkin.SpanFromContext(ctx); parent != nil {
    54  				spanContext = parent.Context()
    55  			}
    56  
    57  			tags := map[string]string{
    58  				string(zipkin.TagHTTPMethod): req.Method,
    59  				string(zipkin.TagHTTPUrl):    req.URL.String(),
    60  			}
    61  
    62  			span := tracer.StartSpan(
    63  				name,
    64  				zipkin.Kind(model.Client),
    65  				zipkin.Tags(config.tags),
    66  				zipkin.Tags(tags),
    67  				zipkin.Parent(spanContext),
    68  				zipkin.FlushOnFinish(false),
    69  			)
    70  
    71  			if config.propagate {
    72  				if err := b3.InjectHTTP(req)(span.Context()); err != nil {
    73  					config.logger.Log("err", err)
    74  				}
    75  			}
    76  
    77  			return zipkin.NewContext(ctx, span)
    78  		},
    79  	)
    80  
    81  	clientAfter := kithttp.ClientAfter(
    82  		func(ctx context.Context, res *http.Response) context.Context {
    83  			if span := zipkin.SpanFromContext(ctx); span != nil {
    84  				zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(res.ContentLength, 10))
    85  				zipkin.TagHTTPStatusCode.Set(span, strconv.Itoa(res.StatusCode))
    86  				if res.StatusCode > 399 {
    87  					zipkin.TagError.Set(span, strconv.Itoa(res.StatusCode))
    88  				}
    89  				span.Finish()
    90  			}
    91  
    92  			return ctx
    93  		},
    94  	)
    95  
    96  	clientFinalizer := kithttp.ClientFinalizer(
    97  		func(ctx context.Context, err error) {
    98  			if span := zipkin.SpanFromContext(ctx); span != nil {
    99  				if err != nil {
   100  					zipkin.TagError.Set(span, err.Error())
   101  				}
   102  				// calling span.Finish() a second time is a noop, if we didn't get to
   103  				// ClientAfter we can at least time the early bail out by calling it
   104  				// here.
   105  				span.Finish()
   106  				// send span to the Reporter
   107  				span.Flush()
   108  			}
   109  		},
   110  	)
   111  
   112  	return func(c *kithttp.Client) {
   113  		clientBefore(c)
   114  		clientAfter(c)
   115  		clientFinalizer(c)
   116  	}
   117  }
   118  
   119  // HTTPServerTrace enables native Zipkin tracing of a Go kit HTTP transport
   120  // Server.
   121  //
   122  // Go kit creates HTTP transport servers per HTTP endpoint. This middleware can
   123  // be set-up individually by adding the method name for each of the Go kit
   124  // method servers using the Name() TracerOption.
   125  // If wanting to use the HTTP method (Get, Post, Put, etc.) as Span name you can
   126  // create a global server tracer omitting the Name() TracerOption, which you can
   127  // then feed to each Go kit method server.
   128  //
   129  // If instrumenting a service to external (not on your platform) clients, you
   130  // will probably want to disallow propagation of a client SpanContext using
   131  // the AllowPropagation TracerOption and setting it to false.
   132  func HTTPServerTrace(tracer *zipkin.Tracer, options ...TracerOption) kithttp.ServerOption {
   133  	config := tracerOptions{
   134  		tags:      make(map[string]string),
   135  		name:      "",
   136  		logger:    log.NewNopLogger(),
   137  		propagate: true,
   138  	}
   139  
   140  	for _, option := range options {
   141  		option(&config)
   142  	}
   143  
   144  	serverBefore := kithttp.ServerBefore(
   145  		func(ctx context.Context, req *http.Request) context.Context {
   146  			var (
   147  				spanContext model.SpanContext
   148  				name        string
   149  			)
   150  
   151  			if config.name != "" {
   152  				name = config.name
   153  			} else {
   154  				name = req.Method
   155  			}
   156  
   157  			if config.propagate {
   158  				spanContext = tracer.Extract(b3.ExtractHTTP(req))
   159  
   160  				if spanContext.Sampled == nil && config.requestSampler != nil {
   161  					sample := config.requestSampler(req)
   162  					spanContext.Sampled = &sample
   163  				}
   164  
   165  				if spanContext.Err != nil {
   166  					config.logger.Log("err", spanContext.Err)
   167  				}
   168  			}
   169  
   170  			tags := map[string]string{
   171  				string(zipkin.TagHTTPMethod): req.Method,
   172  				string(zipkin.TagHTTPPath):   req.URL.Path,
   173  			}
   174  
   175  			span := tracer.StartSpan(
   176  				name,
   177  				zipkin.Kind(model.Server),
   178  				zipkin.Tags(config.tags),
   179  				zipkin.Tags(tags),
   180  				zipkin.Parent(spanContext),
   181  				zipkin.FlushOnFinish(false),
   182  			)
   183  
   184  			return zipkin.NewContext(ctx, span)
   185  		},
   186  	)
   187  
   188  	serverAfter := kithttp.ServerAfter(
   189  		func(ctx context.Context, _ http.ResponseWriter) context.Context {
   190  			if span := zipkin.SpanFromContext(ctx); span != nil {
   191  				span.Finish()
   192  			}
   193  
   194  			return ctx
   195  		},
   196  	)
   197  
   198  	serverFinalizer := kithttp.ServerFinalizer(
   199  		func(ctx context.Context, code int, r *http.Request) {
   200  			if span := zipkin.SpanFromContext(ctx); span != nil {
   201  				zipkin.TagHTTPStatusCode.Set(span, strconv.Itoa(code))
   202  				if code > 399 {
   203  					// set http status as error tag (if already set, this is a noop)
   204  					zipkin.TagError.Set(span, http.StatusText(code))
   205  				}
   206  				if rs, ok := ctx.Value(kithttp.ContextKeyResponseSize).(int64); ok {
   207  					zipkin.TagHTTPResponseSize.Set(span, strconv.FormatInt(rs, 10))
   208  				}
   209  
   210  				// calling span.Finish() a second time is a noop, if we didn't get to
   211  				// ServerAfter we can at least time the early bail out by calling it
   212  				// here.
   213  				span.Finish()
   214  				// send span to the Reporter
   215  				span.Flush()
   216  			}
   217  		},
   218  	)
   219  
   220  	return func(s *kithttp.Server) {
   221  		serverBefore(s)
   222  		serverAfter(s)
   223  		serverFinalizer(s)
   224  	}
   225  }
   226  

View as plain text