1package zipkin 2 3import ( 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 "github.com/go-kit/kit/log" 13 kithttp "github.com/go-kit/kit/transport/http" 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. 28func 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. 132func 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