// New returns an AddService backed by an HTTP server living at the remote // instance. We expect instance to come from a service discovery system, so // likely of the form "host:port". func New(instance string, tracer stdopentracing.Tracer, logger log.Logger) (addsvc.Service, error) { if !strings.HasPrefix(instance, "http") { instance = "http://" + instance } u, err := url.Parse(instance) if err != nil { return nil, err } // We construct a single ratelimiter middleware, to limit the total outgoing // QPS from this client to all methods on the remote instance. We also // construct per-endpoint circuitbreaker middlewares to demonstrate how // that's done, although they could easily be combined into a single breaker // for the entire remote instance, too. limiter := ratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(100, 100)) var sumEndpoint endpoint.Endpoint { sumEndpoint = httptransport.NewClient( "POST", copyURL(u, "/sum"), addsvc.EncodeHTTPGenericRequest, addsvc.DecodeHTTPSumResponse, httptransport.SetClientBefore(opentracing.FromHTTPRequest(tracer, "Sum", logger)), ).Endpoint() sumEndpoint = opentracing.TraceClient(tracer, "Sum")(sumEndpoint) sumEndpoint = limiter(sumEndpoint) sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "Sum", Timeout: 30 * time.Second, }))(sumEndpoint) } var concatEndpoint endpoint.Endpoint { concatEndpoint = httptransport.NewClient( "POST", copyURL(u, "/concat"), addsvc.EncodeHTTPGenericRequest, addsvc.DecodeHTTPConcatResponse, httptransport.SetClientBefore(opentracing.FromHTTPRequest(tracer, "Concat", logger)), ).Endpoint() concatEndpoint = opentracing.TraceClient(tracer, "Concat")(concatEndpoint) concatEndpoint = limiter(concatEndpoint) sumEndpoint = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "Concat", Timeout: 30 * time.Second, }))(sumEndpoint) } return addsvc.Endpoints{ SumEndpoint: sumEndpoint, ConcatEndpoint: concatEndpoint, }, nil }
// MakeHTTPHandler returns a handler that makes a set of endpoints available // on predefined paths. func MakeHTTPHandler(ctx context.Context, endpoints Endpoints, tracer stdopentracing.Tracer, logger log.Logger) http.Handler { options := []httptransport.ServerOption{ httptransport.ServerErrorEncoder(errorEncoder), httptransport.ServerErrorLogger(logger), } m := http.NewServeMux() m.Handle("/sum", httptransport.NewServer( ctx, endpoints.SumEndpoint, DecodeHTTPSumRequest, EncodeHTTPGenericResponse, append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "Sum", logger)))..., )) m.Handle("/concat", httptransport.NewServer( ctx, endpoints.ConcatEndpoint, DecodeHTTPConcatRequest, EncodeHTTPGenericResponse, append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "Concat", logger)))..., )) return m }
func TestTraceHTTPRequestRoundtrip(t *testing.T) { logger := log.NewNopLogger() tracer := mocktracer.New() // Initialize the ctx with a Span to inject. beforeSpan := tracer.StartSpan("to_inject").(*mocktracer.MockSpan) defer beforeSpan.Finish() beforeSpan.SetBaggageItem("baggage", "check") beforeCtx := opentracing.ContextWithSpan(context.Background(), beforeSpan) toHTTPFunc := kitot.ToHTTPRequest(tracer, logger) req, _ := http.NewRequest("GET", "http://test.biz/url", nil) // Call the RequestFunc. afterCtx := toHTTPFunc(beforeCtx, req) // The Span should not have changed. afterSpan := opentracing.SpanFromContext(afterCtx) if beforeSpan != afterSpan { t.Errorf("Should not swap in a new span") } // No spans should have finished yet. finishedSpans := tracer.FinishedSpans() if want, have := 0, len(finishedSpans); want != have { t.Errorf("Want %v span(s), found %v", want, have) } // Use FromHTTPRequest to verify that we can join with the trace given a req. fromHTTPFunc := kitot.FromHTTPRequest(tracer, "joined", logger) joinCtx := fromHTTPFunc(afterCtx, req) joinedSpan := opentracing.SpanFromContext(joinCtx).(*mocktracer.MockSpan) joinedContext := joinedSpan.Context().(mocktracer.MockSpanContext) beforeContext := beforeSpan.Context().(mocktracer.MockSpanContext) if joinedContext.SpanID == beforeContext.SpanID { t.Error("SpanID should have changed", joinedContext.SpanID, beforeContext.SpanID) } // Check that the parent/child relationship is as expected for the joined span. if want, have := beforeContext.SpanID, joinedSpan.ParentID; want != have { t.Errorf("Want ParentID %q, have %q", want, have) } if want, have := "joined", joinedSpan.OperationName; want != have { t.Errorf("Want %q, have %q", want, have) } if want, have := "check", joinedSpan.BaggageItem("baggage"); want != have { t.Errorf("Want %q, have %q", want, have) } }
func main() { // Flag domain. Note that gRPC transitively registers flags via its import // of glog. So, we define a new flag set, to keep those domains distinct. fs := flag.NewFlagSet("", flag.ExitOnError) var ( debugAddr = fs.String("debug.addr", ":8000", "Address for HTTP debug/instrumentation server") httpAddr = fs.String("http.addr", ":8001", "Address for HTTP (JSON) server") grpcAddr = fs.String("grpc.addr", ":8002", "Address for gRPC server") netrpcAddr = fs.String("netrpc.addr", ":8003", "Address for net/rpc server") thriftAddr = fs.String("thrift.addr", ":8004", "Address for Thrift server") thriftProtocol = fs.String("thrift.protocol", "binary", "binary, compact, json, simplejson") thriftBufferSize = fs.Int("thrift.buffer.size", 0, "0 for unbuffered") thriftFramed = fs.Bool("thrift.framed", false, "true to enable framing") // Supported OpenTracing backends zipkinAddr = fs.String("zipkin.kafka.addr", "", "Enable Zipkin tracing via a Kafka server host:port") appdashAddr = fs.String("appdash.addr", "", "Enable Appdash tracing via an Appdash server host:port") lightstepAccessToken = fs.String("lightstep.token", "", "Enable LightStep tracing via a LightStep access token") ) flag.Usage = fs.Usage // only show our flags if err := fs.Parse(os.Args[1:]); err != nil { fmt.Fprintf(os.Stderr, "%v", err) os.Exit(1) } // package log var logger log.Logger { logger = log.NewLogfmtLogger(os.Stderr) logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC).With("caller", log.DefaultCaller) stdlog.SetFlags(0) // flags are handled by Go kit's logger stdlog.SetOutput(log.NewStdlibAdapter(logger)) // redirect anything using stdlib log to us } // package metrics var requestDuration metrics.TimeHistogram { requestDuration = metrics.NewTimeHistogram(time.Nanosecond, metrics.NewMultiHistogram( "request_duration_ns", expvar.NewHistogram("request_duration_ns", 0, 5e9, 1, 50, 95, 99), prometheus.NewSummary(stdprometheus.SummaryOpts{ Namespace: "myorg", Subsystem: "addsvc", Name: "duration_ns", Help: "Request duration in nanoseconds.", }, []string{"method"}), )) } // Set up OpenTracing var tracer opentracing.Tracer { switch { case *appdashAddr != "" && *lightstepAccessToken == "" && *zipkinAddr == "": tracer = appdashot.NewTracer(appdash.NewRemoteCollector(*appdashAddr)) case *appdashAddr == "" && *lightstepAccessToken != "" && *zipkinAddr == "": tracer = lightstep.NewTracer(lightstep.Options{ AccessToken: *lightstepAccessToken, }) defer lightstep.FlushLightStepTracer(tracer) case *appdashAddr == "" && *lightstepAccessToken == "" && *zipkinAddr != "": collector, err := zipkin.NewKafkaCollector( strings.Split(*zipkinAddr, ","), zipkin.KafkaLogger(logger), ) if err != nil { logger.Log("err", "unable to create collector", "fatal", err) os.Exit(1) } tracer, err = zipkin.NewTracer( zipkin.NewRecorder(collector, false, "localhost:80", "addsvc"), ) if err != nil { logger.Log("err", "unable to create zipkin tracer", "fatal", err) os.Exit(1) } case *appdashAddr == "" && *lightstepAccessToken == "" && *zipkinAddr == "": tracer = opentracing.GlobalTracer() // no-op default: logger.Log("fatal", "specify a single -appdash.addr, -lightstep.access.token or -zipkin.kafka.addr") os.Exit(1) } } // Business domain var svc server.AddService { svc = pureAddService{} svc = loggingMiddleware{svc, logger} svc = instrumentingMiddleware{svc, requestDuration} } // Mechanical stuff rand.Seed(time.Now().UnixNano()) root := context.Background() errc := make(chan error) go func() { errc <- interrupt() }() // Debug/instrumentation go func() { transportLogger := log.NewContext(logger).With("transport", "debug") transportLogger.Log("addr", *debugAddr) errc <- http.ListenAndServe(*debugAddr, nil) // DefaultServeMux }() // Transport: HTTP/JSON go func() { var ( transportLogger = log.NewContext(logger).With("transport", "HTTP/JSON") tracingLogger = log.NewContext(transportLogger).With("component", "tracing") mux = http.NewServeMux() sum, concat endpoint.Endpoint ) sum = makeSumEndpoint(svc) sum = kitot.TraceServer(tracer, "sum")(sum) mux.Handle("/sum", httptransport.NewServer( root, sum, server.DecodeSumRequest, server.EncodeSumResponse, httptransport.ServerErrorLogger(transportLogger), httptransport.ServerBefore(kitot.FromHTTPRequest(tracer, "sum", tracingLogger)), )) concat = makeConcatEndpoint(svc) concat = kitot.TraceServer(tracer, "concat")(concat) mux.Handle("/concat", httptransport.NewServer( root, concat, server.DecodeConcatRequest, server.EncodeConcatResponse, httptransport.ServerErrorLogger(transportLogger), httptransport.ServerBefore(kitot.FromHTTPRequest(tracer, "concat", tracingLogger)), )) transportLogger.Log("addr", *httpAddr) errc <- http.ListenAndServe(*httpAddr, mux) }() // Transport: gRPC go func() { transportLogger := log.NewContext(logger).With("transport", "gRPC") tracingLogger := log.NewContext(transportLogger).With("component", "tracing") ln, err := net.Listen("tcp", *grpcAddr) if err != nil { errc <- err return } s := grpc.NewServer() // uses its own, internal context pb.RegisterAddServer(s, newGRPCBinding(root, tracer, svc, tracingLogger)) transportLogger.Log("addr", *grpcAddr) errc <- s.Serve(ln) }() // Transport: net/rpc go func() { transportLogger := log.NewContext(logger).With("transport", "net/rpc") s := rpc.NewServer() if err := s.RegisterName("addsvc", netrpcBinding{svc}); err != nil { errc <- err return } s.HandleHTTP(rpc.DefaultRPCPath, rpc.DefaultDebugPath) transportLogger.Log("addr", *netrpcAddr) errc <- http.ListenAndServe(*netrpcAddr, s) }() // Transport: Thrift go func() { var protocolFactory thrift.TProtocolFactory switch *thriftProtocol { case "binary": protocolFactory = thrift.NewTBinaryProtocolFactoryDefault() case "compact": protocolFactory = thrift.NewTCompactProtocolFactory() case "json": protocolFactory = thrift.NewTJSONProtocolFactory() case "simplejson": protocolFactory = thrift.NewTSimpleJSONProtocolFactory() default: errc <- fmt.Errorf("invalid Thrift protocol %q", *thriftProtocol) return } var transportFactory thrift.TTransportFactory if *thriftBufferSize > 0 { transportFactory = thrift.NewTBufferedTransportFactory(*thriftBufferSize) } else { transportFactory = thrift.NewTTransportFactory() } if *thriftFramed { transportFactory = thrift.NewTFramedTransportFactory(transportFactory) } transport, err := thrift.NewTServerSocket(*thriftAddr) if err != nil { errc <- err return } transportLogger := log.NewContext(logger).With("transport", "thrift") transportLogger.Log("addr", *thriftAddr) errc <- thrift.NewTSimpleServer4( thriftadd.NewAddServiceProcessor(thriftBinding{svc}), transport, transportFactory, protocolFactory, ).Serve() }() logger.Log("fatal", <-errc) }