Skip to content

Commit c717947

Browse files
Nitinraphael
Nitin
authored andcommitted
X-ray middleware (goadesign#1555)
* Add X-ray middleware * Add example * Address review suggestions * Fix build
1 parent c904b17 commit c717947

12 files changed

+1924
-6
lines changed

.golint_exclude

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ http/design/response.go
33
codegen/service/testing/.*
44
http/codegen/testing/.*
55
http/middleware/tracing/middleware.go
6+
http/middleware/xray/middleware.go
7+
http/middleware/xray/wrap_doer.go
8+
http/middleware/xray/wrap_doer_test.go

examples/tracing/cmd/calccli/main.go

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"flag"
7+
"fmt"
8+
"net/http"
9+
"net/url"
10+
"os"
11+
"strings"
12+
"time"
13+
14+
"goa.design/goa/examples/calc/gen/http/cli"
15+
goahttp "goa.design/goa/http"
16+
"goa.design/goa/http/middleware/tracing"
17+
"goa.design/goa/http/middleware/xray"
18+
)
19+
20+
func main() {
21+
var (
22+
addr = flag.String("url", "http://localhost:8080", "`URL` to service host")
23+
verbose = flag.Bool("verbose", false, "Print request and response details")
24+
v = flag.Bool("v", false, "Print request and response details")
25+
timeout = flag.Int("timeout", 30, "Maximum number of `seconds` to wait for response")
26+
)
27+
flag.Usage = usage
28+
flag.Parse()
29+
30+
var (
31+
scheme string
32+
host string
33+
debug bool
34+
)
35+
{
36+
u, err := url.Parse(*addr)
37+
if err != nil {
38+
fmt.Fprintf(os.Stderr, "invalid URL %#v: %s", *addr, err)
39+
os.Exit(1)
40+
}
41+
scheme = u.Scheme
42+
host = u.Host
43+
if scheme == "" {
44+
scheme = "http"
45+
}
46+
debug = *verbose || *v
47+
}
48+
49+
var (
50+
doer goahttp.Doer
51+
)
52+
{
53+
doer = &http.Client{Timeout: time.Duration(*timeout) * time.Second}
54+
if debug {
55+
doer = goahttp.NewDebugDoer(doer)
56+
}
57+
doer = xray.WrapDoer(doer)
58+
doer = tracing.WrapDoer(doer)
59+
}
60+
61+
endpoint, payload, err := cli.ParseEndpoint(
62+
scheme,
63+
host,
64+
doer,
65+
goahttp.RequestEncoder,
66+
goahttp.ResponseDecoder,
67+
debug,
68+
)
69+
if err != nil {
70+
if err == flag.ErrHelp {
71+
os.Exit(0)
72+
}
73+
fmt.Fprintln(os.Stderr, err.Error())
74+
fmt.Fprintln(os.Stderr, "run '"+os.Args[0]+" --help' for detailed usage.")
75+
os.Exit(1)
76+
}
77+
78+
data, err := endpoint(context.Background(), payload)
79+
80+
if debug {
81+
doer.(goahttp.DebugDoer).Fprint(os.Stderr)
82+
}
83+
84+
if err != nil {
85+
fmt.Fprintln(os.Stderr, err.Error())
86+
os.Exit(1)
87+
}
88+
89+
if data != nil && !debug {
90+
m, _ := json.MarshalIndent(data, "", " ")
91+
fmt.Println(string(m))
92+
}
93+
}
94+
95+
func usage() {
96+
fmt.Fprintf(os.Stderr, `%s is a command line client for the calc API.
97+
98+
Usage:
99+
%s [-url URL][-timeout SECONDS][-verbose|-v] SERVICE ENDPOINT [flags]
100+
101+
-url URL: specify service URL (http://localhost:8080)
102+
-timeout: maximum number of seconds to wait for response (30)
103+
-verbose|-v: print request and response details (false)
104+
105+
Commands:
106+
%s
107+
Additional help:
108+
%s SERVICE [ENDPOINT] --help
109+
110+
Example:
111+
%s
112+
`, os.Args[0], os.Args[0], indent(cli.UsageCommands()), os.Args[0], indent(cli.UsageExamples()))
113+
}
114+
115+
func indent(s string) string {
116+
if s == "" {
117+
return ""
118+
}
119+
return " " + strings.Replace(s, "\n", "\n ", -1)
120+
}

examples/tracing/cmd/calcsvc/main.go

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"os"
10+
"os/signal"
11+
"time"
12+
13+
calc "goa.design/goa/examples/calc"
14+
calcsvc "goa.design/goa/examples/calc/gen/calc"
15+
calcsvcsvr "goa.design/goa/examples/calc/gen/http/calc/server"
16+
openapisvr "goa.design/goa/examples/calc/gen/http/openapi/server"
17+
goahttp "goa.design/goa/http"
18+
"goa.design/goa/http/middleware/debugging"
19+
"goa.design/goa/http/middleware/logging"
20+
"goa.design/goa/http/middleware/tracing"
21+
"goa.design/goa/http/middleware/xray"
22+
)
23+
24+
func main() {
25+
// Define command line flags, add any other flag required to configure
26+
// the service.
27+
var (
28+
addr = flag.String("listen", ":8080", "HTTP listen `address`")
29+
daemon = flag.String("daemon", "127.0.0.1:2000", "X-Ray daemon address")
30+
dbg = flag.Bool("debug", false, "Log request and response bodies")
31+
)
32+
flag.Parse()
33+
34+
// Setup logger and goa log adapter. Replace logger with your own using
35+
// your log package of choice. The goa.design/middleware/logging/...
36+
// packages define log adapters for common log packages.
37+
var (
38+
logger *log.Logger
39+
adapter logging.Adapter
40+
)
41+
{
42+
logger = log.New(os.Stderr, "[calc] ", log.Ltime)
43+
adapter = logging.Adapt(logger)
44+
}
45+
46+
// Create the structs that implement the services.
47+
var (
48+
calcsvcSvc calcsvc.Service
49+
)
50+
{
51+
calcsvcSvc = calc.NewCalc(logger)
52+
}
53+
54+
// Wrap the services in endpoints that can be invoked from other
55+
// services potentially running in different processes.
56+
var (
57+
calcsvcEndpoints *calcsvc.Endpoints
58+
)
59+
{
60+
calcsvcEndpoints = calcsvc.NewEndpoints(calcsvcSvc)
61+
}
62+
63+
// Provide the transport specific request decoder and response encoder.
64+
// The goa http package has built-in support for JSON, XML and gob.
65+
// Other encodings can be used by providing the corresponding functions,
66+
// see goa.design/encoding.
67+
var (
68+
dec = goahttp.RequestDecoder
69+
enc = goahttp.ResponseEncoder
70+
)
71+
72+
// Build the service HTTP request multiplexer and configure it to serve
73+
// HTTP requests to the service endpoints.
74+
var mux goahttp.Muxer
75+
{
76+
mux = goahttp.NewMuxer()
77+
}
78+
79+
// Wrap the endpoints with the transport specific layers. The generated
80+
// server packages contains code generated from the design which maps
81+
// the service input and output data structures to HTTP requests and
82+
// responses.
83+
var (
84+
openapiServer *openapisvr.Server
85+
calcsvcServer *calcsvcsvr.Server
86+
)
87+
{
88+
openapiServer = openapisvr.New(nil, mux, dec, enc)
89+
calcsvcServer = calcsvcsvr.New(calcsvcEndpoints, mux, dec, enc)
90+
}
91+
92+
// Configure the mux.
93+
openapisvr.Mount(mux)
94+
calcsvcsvr.Mount(mux, calcsvcServer)
95+
96+
// Wrap the multiplexer with additional middlewares. Middlewares mounted
97+
// here apply to all the service endpoints.
98+
var handler http.Handler = mux
99+
{
100+
if *dbg {
101+
handler = debugging.New(mux, adapter)(handler)
102+
}
103+
handler = logging.New(adapter)(handler)
104+
xrayHndlr, err := xray.New("calc", *daemon)
105+
if err != nil {
106+
logger.Printf("[WARN] cannot connect to xray daemon %s: %s", *daemon, err)
107+
}
108+
// Wrap the Xray and the tracing handler. The order is very important.
109+
handler = xrayHndlr(handler)
110+
handler = tracing.New()(handler)
111+
}
112+
113+
// Create channel used by both the signal handler and server goroutines
114+
// to notify the main goroutine when to stop the server.
115+
errc := make(chan error)
116+
117+
// Setup interrupt handler. This optional step configures the process so
118+
// that SIGINT and SIGTERM signals cause the service to stop gracefully.
119+
go func() {
120+
c := make(chan os.Signal, 1)
121+
signal.Notify(c, os.Interrupt)
122+
errc <- fmt.Errorf("%s", <-c)
123+
}()
124+
125+
// Start HTTP server using default configuration, change the code to
126+
// configure the server as required by your service.
127+
srv := &http.Server{Addr: *addr, Handler: handler}
128+
go func() {
129+
for _, m := range openapiServer.Mounts {
130+
logger.Printf("[INFO] service %q file %q mounted on %s %s", openapiServer.Service(), m.Method, m.Verb, m.Pattern)
131+
}
132+
for _, m := range calcsvcServer.Mounts {
133+
logger.Printf("[INFO] service %q method %q mounted on %s %s", calcsvcServer.Service(), m.Method, m.Verb, m.Pattern)
134+
}
135+
logger.Printf("[INFO] listening on %s", *addr)
136+
errc <- srv.ListenAndServe()
137+
}()
138+
139+
// Wait for signal.
140+
logger.Printf("exiting (%v)", <-errc)
141+
142+
// Shutdown gracefully with a 30s timeout.
143+
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
144+
srv.Shutdown(ctx)
145+
146+
logger.Println("exited")
147+
}

http/middleware/tracing/middleware.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,7 @@ func SampleSize(s int) Option {
127127
}
128128

129129
// New returns a trace middleware that initializes the trace information in the
130-
// request context. The information can be retrieved using any of the ContextXXX
131-
// functions.
130+
// request context.
132131
//
133132
// samplingRate must be a value between 0 and 100. It represents the percentage of
134133
// requests that should be traced. If the incoming request has a Trace ID header
@@ -178,10 +177,7 @@ func New(opts ...Option) func(http.Handler) http.Handler {
178177

179178
// WrapDoer wraps a goa client Doer and sets the trace headers so that the
180179
// downstream service may properly retrieve the parent span ID and trace ID.
181-
//
182-
// ctx must contain the current request segment as set by the xray middleware or
183-
// the doer passed as argument is returned.
184-
func WrapDoer(ctx context.Context, doer Doer) Doer {
180+
func WrapDoer(doer Doer) Doer {
185181
return &tracedDoer{doer}
186182
}
187183

0 commit comments

Comments
 (0)