diff --git a/debug/README.md b/debug/README.md index 37d7592d..81cd5e4a 100644 --- a/debug/README.md +++ b/debug/README.md @@ -13,25 +13,60 @@ the content of request and result payloads, and to profile a microservice. ### Toggling Debug Logs -The `debug` package provides a `MountDebugLogEnabler` function which accepts a -URL prefix, a mux and returns a HTTP middleware. The function configures the mux -to add a handler at the given URL prefix that accepts requests of the form -`/?enable=on` and `/?enable=off` to turn debug logs on and off. - -The function returns a middleware that should be used on all the handlers that -should support toggling debug logs. The package also include unary and stream -gRPC interceptors that should be used on all methods that should support -toggling debug logs. Note that gRPC services must still expose HTTP endpoints -to enable toggling debug logs, the interceptor merely configures the log level -for each request. +The `debug` package provides a `MountDebugLogEnabler` function which adds a +handler to the given mux under the `/debug` path that accepts requests of the +form `/debug?debug-logs=on` and `/debug?debug-logs=off` to manage debug logs +state. The handler returns the current state of debug logs in the response body +and does not change the state if the request does not contain a `debug-logs` +query parameter. The path, query parameter name and value can be customized by +passing options to the `MountDebugLogEnabler` function. + +Note that for the debug log state to take effect, HTTP servers must use handlers +returned by the HTTP function and gRPC servers must make use of the +UnaryInterceptor or StreamInterceptor interceptors. Also note that gRPC +services must expose an HTTP endpoint to control the debug log state. + +```go +// HTTP +mux := http.NewServeMux() +debug.MountDebugLogEnabler(mux) +// ... configure mux with other handlers +srv := &http.Server{Handler: debug.HTTP(mux)} +srv.ListenAndServe() +``` + +```go +// gRPC +mux := http.NewServeMux() +debug.MountDebugLogEnabler(mux) +srv := &http.Server{Handler: mux} +go srv.ListenAndServe() +gsrv := grpc.NewServer(grpc.UnaryInterceptor(debug.UnaryInterceptor)) +lis, _ := net.Listen("tcp", ":8080") +gsrv.Serve(lis) +``` + +The package also provides a Goa muxer adapter that can be used to mount the +debug log enabler handler on a Goa muxer. + +```go +mux := goa.NewMuxer() +debug.MountDebugLogEnabler(debug.Adapt(mux)) +``` ### Logging Request and Result Payloads The `debug` package provides a `LogPayloads` Goa endpoint middleware that logs the content of request and result payloads. The middleware can be used with the -generated `Use` functions on `Endpoint` structs. The middleware is a no-op if +generated `Use` functions on `Endpoints` structs. The middleware is a no-op if debug logs are disabled. +```go +endpoints := genforecaster.NewEndpoints(svc) +endpoints.Use(debug.LogPayloads()) +endpoints.Use(log.Endpoint) +``` + ### Profiling The `debug` package provides a `MountPprofHandlers` function which configures a @@ -53,6 +88,15 @@ list of handlers is: See the [net/http/pprof](https://pkg.go.dev/net/http/pprof) package documentation for more information. +The path prefix can be customized by passing an option to the +`MountPprofHandlers` function. + +```go +mux := http.NewServeMux() +debug.MountPprofHandlers(mux) +// ... configure mux with other handlers +``` + ### Example The weather example illustrates how to make use of this package. In particular @@ -69,7 +113,7 @@ Additionally the two "back" services (which would not be exposed to the internet in production) also mount the pprof handlers (see [here](https://github.com/goadesign/clue/blob/main/example/weather/services/forecaster/cmd/forecaster/main.go#L105)). With this setup, requests made to the HTTP servers of each service of the form -`/debug?enable=on` turn on debug logs and requests of the form -`/debug?enable=off` turns them back off. Requests made to `/debug/pprof/` return -the pprof package index page while a request to `/debug/pprof/profile` profile -the service for 30s. \ No newline at end of file +`/debug?debug-logs=on` turn on debug logs and requests of the form +`/debug?debug-logs=off` turns them back off. Requests made to `/debug/pprof/` +return the pprof package index page while a request to `/debug/pprof/profile` +profile the service for 30s. \ No newline at end of file diff --git a/debug/debug.go b/debug/debug.go index c7e621dc..b27fd847 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/http/pprof" + "strings" goa "goa.design/goa/v3/pkg" @@ -26,38 +27,38 @@ var ( pprofEnabled bool ) -// MountDebugLogEnabler mounts an endpoint under the given prefix and returns a -// HTTP middleware that manages debug logs. The endpoint accepts a single query -// parameter "debug-logs". If the parameter is set to "true" then debug logs are -// enabled for requests made to handlers returned by the middleware. If the -// parameter is set to "false" then debug logs are disabled. In all other cases -// the endpoint returns the current debug logs status. -func MountDebugLogEnabler(prefix string, mux Muxer) func(http.Handler) http.Handler { - mux.Handle(prefix, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Query().Get("debug-logs") { - case "true": +// MountDebugLogEnabler mounts an endpoint under "/debug" and returns a HTTP +// middleware that manages the status of debug logs. The endpoint accepts a +// single query parameter "debug-logs". If the parameter is set to "on" then +// debug logs are enabled. If the parameter is set to "off" then debug logs are +// disabled. In all other cases the endpoint returns the current debug logs +// status. The path, query parameter name and values can be changed using the +// WithPath, WithQuery, WithOnValue and WithOffValue options. +// +// Note: the endpoint merely controls the status of debug logs. It does not +// actually configure the current logger. The logger is configured by the +// middleware returned by the HTTP function or by the gRPC interceptors returned +// by the UnaryServerInterceptor and StreamServerInterceptor functions. +func MountDebugLogEnabler(mux Muxer, opts ...DebugLogEnablerOption) { + o := defaultDebugLogEnablerOptions() + for _, opt := range opts { + opt(o) + } + if !strings.HasPrefix(o.path, "/") { + o.path = "/" + o.path + } + mux.Handle(o.path, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if q := r.URL.Query().Get(o.query); q == o.onval { debugLogs = true - w.Write([]byte(`{"debug-logs":true}`)) - case "false": + } else if q == o.offval { debugLogs = false - w.Write([]byte(`{"debug-logs":false}`)) - default: - w.Write([]byte(`{"debug-logs":` + fmt.Sprintf("%t", debugLogs) + `}`)) + } + if debugLogs { + w.Write([]byte(fmt.Sprintf(`{"%s":"%s"}`, o.query, o.onval))) + } else { + w.Write([]byte(fmt.Sprintf(`{"%s":"%s"}`, o.query, o.offval))) } })) - return func(next http.Handler) http.Handler { - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if debugLogs { - ctx := log.Context(r.Context(), log.WithDebug()) - r = r.WithContext(ctx) - } else { - ctx := log.Context(r.Context(), log.WithNoDebug()) - r = r.WithContext(ctx) - } - next.ServeHTTP(w, r) - }) - return handler - } } // MountPprofHandlers mounts pprof handlers under /debug/pprof/. The list of @@ -77,14 +78,25 @@ func MountDebugLogEnabler(prefix string, mux Muxer) func(http.Handler) http.Hand // // See the pprof package documentation for more information. // +// The path prefix ("/debug/pprof/") can be changed using WithPprofPrefix. // Note: do not call this function on production servers accessible to the // public! It exposes sensitive information about the server. -func MountPprofHandlers(mux Muxer) { - mux.HandleFunc("/debug/pprof/", pprof.Index) - mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - mux.HandleFunc("/debug/pprof/profile", pprof.Profile) - mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - mux.HandleFunc("/debug/pprof/trace", pprof.Trace) +func MountPprofHandlers(mux Muxer, opts ...PprofOption) { + o := defaultPprofOptions() + for _, opt := range opts { + opt(o) + } + if !strings.HasPrefix(o.prefix, "/") { + o.prefix = "/" + o.prefix + } + if !strings.HasSuffix(o.prefix, "/") { + o.prefix = o.prefix + "/" + } + mux.HandleFunc(o.prefix, pprof.Index) + mux.HandleFunc(o.prefix+"cmdline", pprof.Cmdline) + mux.HandleFunc(o.prefix+"profile", pprof.Profile) + mux.HandleFunc(o.prefix+"symbol", pprof.Symbol) + mux.HandleFunc(o.prefix+"trace", pprof.Trace) } // LogPayloads returns a Goa endpoint middleware that logs request payloads and @@ -94,7 +106,7 @@ func MountPprofHandlers(mux Muxer) { // standard JSON marshaller. It only marshals if debug logs are enabled. func LogPayloads(opts ...LogPayloadsOption) func(goa.Endpoint) goa.Endpoint { return func(next goa.Endpoint) goa.Endpoint { - options := defaultOptions() + options := defaultLogPayloadsOptions() for _, opt := range opts { if opt != nil { opt(options) diff --git a/debug/debug_test.go b/debug/debug_test.go index 2a8e205f..b5fe500d 100644 --- a/debug/debug_test.go +++ b/debug/debug_test.go @@ -15,75 +15,70 @@ import ( ) func TestMountDebugLogEnabler(t *testing.T) { - mux := http.NewServeMux() - var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - w.WriteHeader(http.StatusNotFound) - return - } - log.Info(r.Context(), log.KV{K: "test", V: "info"}) - log.Debug(r.Context(), log.KV{K: "test", V: "debug"}) - w.WriteHeader(http.StatusOK) - w.Write([]byte("OK")) - }) - handler = MountDebugLogEnabler("/debug", mux)(handler) - var buf bytes.Buffer - ctx := log.Context(context.Background(), log.WithOutput(&buf), log.WithFormat(logKeyValsOnly)) - log.FlushAndDisableBuffering(ctx) - handler = log.HTTP(ctx)(handler) - mux.Handle("/", handler) - ts := httptest.NewServer(mux) - defer ts.Close() - - steps := []struct { + cases := []struct { name string - enable bool - disable bool + prefix string + query string + onval string + offval string + url string expectedResp string - expectedLogs string }{ - {"default", false, false, "", "test=info "}, - {"enable debug", true, false, `{"debug-logs":true}`, "test=info test=debug "}, - {"disable debug", false, true, `{"debug-logs":false}`, "test=info "}, + {"defaults", "", "", "", "", "/debug", `{"debug-logs":"off"}`}, + {"defaults-enable", "", "", "", "", "/debug?debug-logs=on", `{"debug-logs":"on"}`}, + {"defaults-disable", "", "", "", "", "/debug?debug-logs=off", `{"debug-logs":"off"}`}, + {"prefix", "test", "", "", "", "/test", `{"debug-logs":"off"}`}, + {"prefix-enable", "test", "", "", "", "/test?debug-logs=on", `{"debug-logs":"on"}`}, + {"prefix-disable", "test", "", "", "", "/test?debug-logs=off", `{"debug-logs":"off"}`}, + {"query", "", "debug", "", "", "/debug", `{"debug":"off"}`}, + {"query-enable", "", "debug", "", "", "/debug?debug=on", `{"debug":"on"}`}, + {"query-disable", "", "debug", "", "", "/debug?debug=off", `{"debug":"off"}`}, + {"onval-enable", "", "", "foo", "", "/debug?debug-logs=foo", `{"debug-logs":"foo"}`}, + {"offval-disable", "", "", "", "bar", "/debug?debug-logs=bar", `{"debug-logs":"bar"}`}, + {"prefix-query-enable", "test", "debug", "", "", "/test?debug=on", `{"debug":"on"}`}, + {"prefix-query-disable", "test", "debug", "", "", "/test?debug=off", `{"debug":"off"}`}, + {"prefix-onval-enable", "test", "", "foo", "", "/test?debug-logs=foo", `{"debug-logs":"foo"}`}, + {"prefix-offval-disable", "test", "", "", "bar", "/test?debug-logs=bar", `{"debug-logs":"bar"}`}, + {"prefix-query-onval-enable", "test", "debug", "foo", "", "/test?debug=foo", `{"debug":"foo"}`}, + {"prefix-query-offval-disable", "test", "debug", "", "bar", "/test?debug=bar", `{"debug":"bar"}`}, } - for _, c := range steps { - if c.enable { - status, resp := makeRequest(t, ts.URL+"/debug?debug-logs=true") - if status != http.StatusOK { - t.Errorf("%s: got status %d, expected %d", c.name, status, http.StatusOK) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + mux := http.NewServeMux() + var options []DebugLogEnablerOption + if c.prefix != "" { + options = append(options, WithPath(c.prefix)) } - if resp != c.expectedResp { - t.Errorf("%s: got body %q, expected %q", c.name, resp, c.expectedResp) + if c.query != "" { + options = append(options, WithQuery(c.query)) } - } - if c.disable { - status, resp := makeRequest(t, ts.URL+"/debug?debug-logs=false") + if c.onval != "" { + options = append(options, WithOnValue(c.onval)) + } + if c.offval != "" { + options = append(options, WithOffValue(c.offval)) + } + MountDebugLogEnabler(mux, options...) + ts := httptest.NewServer(mux) + defer ts.Close() + + status, resp := makeRequest(t, ts.URL+c.url) + if status != http.StatusOK { t.Errorf("%s: got status %d, expected %d", c.name, status, http.StatusOK) } if resp != c.expectedResp { t.Errorf("%s: got body %q, expected %q", c.name, resp, c.expectedResp) } - } - buf.Reset() - - status, resp := makeRequest(t, ts.URL) - if status != http.StatusOK { - t.Errorf("%s: got status %d, expected %d", c.name, status, http.StatusOK) - } - if resp != "OK" { - t.Errorf("%s: got body %q, expected %q", c.name, resp, "OK") - } - if buf.String() != c.expectedLogs { - t.Errorf("%s: got logs %q, expected %q", c.name, buf.String(), c.expectedLogs) - } + }) } } func TestMountPprofHandlers(t *testing.T) { mux := http.NewServeMux() MountPprofHandlers(mux) + MountPprofHandlers(mux, WithPrefix("test")) var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) @@ -106,6 +101,7 @@ func TestMountPprofHandlers(t *testing.T) { paths := []string{ "/debug/pprof/", + "/test/", "/debug/pprof/allocs", "/debug/pprof/block", "/debug/pprof/cmdline", diff --git a/debug/grpc.go b/debug/grpc.go index 81f9b3ec..9b3fdd88 100644 --- a/debug/grpc.go +++ b/debug/grpc.go @@ -9,7 +9,8 @@ import ( ) // UnaryServerInterceptor return an interceptor that manages whether debug log -// entries are written. +// entries are written. This interceptor should be used in conjunction with the +// MountDebugLogEnabler function. func UnaryServerInterceptor() grpc.UnaryServerInterceptor { return func( ctx context.Context, @@ -28,7 +29,8 @@ func UnaryServerInterceptor() grpc.UnaryServerInterceptor { // StreamServerInterceptor returns a stream interceptor that manages whether // debug log entries are written. Note: a change in the debug setting is -// effective only for the next stream request. +// effective only for the next stream request. This interceptor should be used +// in conjunction with the MountDebugLogEnabler function. func StreamServerInterceptor() grpc.StreamServerInterceptor { return func( srv interface{}, diff --git a/debug/grpc_test.go b/debug/grpc_test.go index 0cfec346..6512a19e 100644 --- a/debug/grpc_test.go +++ b/debug/grpc_test.go @@ -3,6 +3,8 @@ package debug import ( "bytes" "context" + "net/http" + "net/http/httptest" "testing" "google.golang.org/grpc" @@ -18,24 +20,36 @@ func TestUnaryServerInterceptor(t *testing.T) { testsvc.WithServerOptions(grpc.ChainUnaryInterceptor(log.UnaryServerInterceptor(ctx), UnaryServerInterceptor())), testsvc.WithUnaryFunc(logUnaryMethod)) defer stop() + mux := http.NewServeMux() + MountDebugLogEnabler(mux) + ts := httptest.NewServer(mux) + defer ts.Close() steps := []struct { - name string - enableDebugLogs bool - expectedLogs string + name string + on bool + off bool + expectedLogs string }{ - {"no debug logs", false, ""}, - {"debug logs", true, "debug=message "}, - {"revert to no debug logs", false, ""}, + {"start", false, false, ""}, + {"turn debug logs on", true, false, "debug=message "}, + {"with debug logs on", false, false, "debug=message "}, + {"turn debug logs off", false, true, ""}, + {"with debug logs off", false, false, ""}, } - for _, c := range steps { - debugLogs = c.enableDebugLogs + for _, step := range steps { + if step.on { + makeRequest(t, ts.URL+"/debug?debug-logs=on") + } + if step.off { + makeRequest(t, ts.URL+"/debug?debug-logs=off") + } _, err := cli.GRPCMethod(context.Background(), nil) if err != nil { t.Errorf("unexpected error: %v", err) } - if buf.String() != c.expectedLogs { - t.Errorf("expected log %q, got %q", c.expectedLogs, buf.String()) + if buf.String() != step.expectedLogs { + t.Errorf("expected log %q, got %q", step.expectedLogs, buf.String()) } buf.Reset() } diff --git a/debug/http.go b/debug/http.go new file mode 100644 index 00000000..9a2f5891 --- /dev/null +++ b/debug/http.go @@ -0,0 +1,26 @@ +package debug + +import ( + "net/http" + + "goa.design/clue/log" +) + +// HTTP returns a middleware that manages whether debug log entries are written. +// This middleware should be used in conjunction with the MountDebugLogEnabler +// function. +func HTTP() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if debugLogs { + ctx := log.Context(r.Context(), log.WithDebug()) + r = r.WithContext(ctx) + } else { + ctx := log.Context(r.Context(), log.WithNoDebug()) + r = r.WithContext(ctx) + } + next.ServeHTTP(w, r) + }) + return handler + } +} diff --git a/debug/http_test.go b/debug/http_test.go new file mode 100644 index 00000000..9c0cdf4a --- /dev/null +++ b/debug/http_test.go @@ -0,0 +1,68 @@ +package debug + +import ( + "bytes" + "context" + "net/http" + "net/http/httptest" + "testing" + + "goa.design/clue/log" +) + +func TestHTTP(t *testing.T) { + mux := http.NewServeMux() + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + return + } + log.Info(r.Context(), log.KV{K: "test", V: "info"}) + log.Debug(r.Context(), log.KV{K: "test", V: "debug"}) + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) + }) + MountDebugLogEnabler(mux) + var buf bytes.Buffer + ctx := log.Context(context.Background(), log.WithOutput(&buf), log.WithFormat(logKeyValsOnly)) + log.FlushAndDisableBuffering(ctx) + handler = HTTP()(handler) + handler = log.HTTP(ctx)(handler) + mux.Handle("/", handler) + ts := httptest.NewServer(mux) + defer ts.Close() + steps := []struct { + name string + on bool + off bool + expectedResp string + expectedLogs string + }{ + {"start", false, false, "", "test=info "}, + {"turn debug logs on", true, false, `{"debug-logs":true}`, "test=info test=debug "}, + {"with debug logs on", false, false, `{"debug-logs":true}`, "test=info test=debug "}, + {"turn debug logs off", false, true, `{"debug-logs":false}`, "test=info "}, + {"with debug logs off", false, false, `{"debug-logs":false}`, "test=info "}, + } + for _, step := range steps { + if step.on { + makeRequest(t, ts.URL+"/debug?debug-logs=on") + } + if step.off { + makeRequest(t, ts.URL+"/debug?debug-logs=off") + } + + status, resp := makeRequest(t, ts.URL) + + if status != http.StatusOK { + t.Errorf("%s: got status %d, expected %d", step.name, status, http.StatusOK) + } + if resp != "OK" { + t.Errorf("%s: got body %q, expected %q", step.name, resp, "OK") + } + if buf.String() != step.expectedLogs { + t.Errorf("%s: got logs %q, expected %q", step.name, buf.String(), step.expectedLogs) + } + buf.Reset() + } +} diff --git a/debug/options.go b/debug/options.go index f870af3e..d49f853f 100644 --- a/debug/options.go +++ b/debug/options.go @@ -9,45 +9,104 @@ import ( type ( // LogPayloadsOption is a function that applies a configuration option // to the LogPayloads middleware. - LogPayloadsOption func(*options) + LogPayloadsOption func(*lpOptions) + + // DebugLogEnablerOption is a function that applies a configuration option + // to MountDebugLogEnabler. + DebugLogEnablerOption func(*dleOptions) + + // PprofOption is a function that applies a configuration option to + // MountPprofHandlers. + PprofOption func(*pprofOptions) // FormatFunc is used to format the logged value for payloads and // results. FormatFunc func(context.Context, interface{}) string - options struct { + lpOptions struct { maxsize int // maximum number of bytes in a single log message or value format FormatFunc client bool + prefix string + } + + dleOptions struct { + path string + query string + onval string + offval string + } + + pprofOptions struct { + prefix string } ) // DefaultMaxSize is the default maximum size for a logged request or result -// value in bytes. +// value in bytes used by LogPayloads. const DefaultMaxSize = 1024 -// WithFormat sets the log format. +// WithFormat sets the log format used by LogPayloads. func WithFormat(fn FormatFunc) LogPayloadsOption { - return func(o *options) { + return func(o *lpOptions) { o.format = fn } } -// WithMaxSize sets the maximum size of a single log message or value. +// WithMaxSize sets the maximum size of a single log message or value used by +// LogPayloads. func WithMaxSize(n int) LogPayloadsOption { - return func(o *options) { + return func(o *lpOptions) { o.maxsize = n } } -// WithClient prefixes the log keys with "client-". This is useful when -// logging client requests and responses. +// WithClient prefixes the log keys used by LogPayloads with "client-". This is +// useful when logging client requests and responses. func WithClient() LogPayloadsOption { - return func(o *options) { + return func(o *lpOptions) { o.client = true } } +// WithPath sets the URL path used by MountDebugLogEnabler. +func WithPath(path string) DebugLogEnablerOption { + return func(o *dleOptions) { + o.path = path + } +} + +// WIthPrefix sets the path prefix used by MountPprofHandlers. +func WithPrefix(prefix string) PprofOption { + return func(o *pprofOptions) { + o.prefix = prefix + } +} + +// WithQuery sets the query string parameter name used by MountDebugLogEnabler +// to enable or disable debug logs. +func WithQuery(query string) DebugLogEnablerOption { + return func(o *dleOptions) { + o.query = query + } +} + +// WithOnValue sets the query string parameter value used by +// MountDebugLogEnabler to enable debug logs. +func WithOnValue(onval string) DebugLogEnablerOption { + return func(o *dleOptions) { + o.onval = onval + } +} + +// WithOffValue sets the query string parameter value used by +// MountDebugLogEnabler to disable debug logs. +func WithOffValue(offval string) DebugLogEnablerOption { + return func(o *dleOptions) { + o.offval = offval + } +} + // FormatJSON returns a function that formats the given value as JSON. func FormatJSON(ctx context.Context, v interface{}) string { js, err := json.Marshal(v) @@ -57,10 +116,27 @@ func FormatJSON(ctx context.Context, v interface{}) string { return string(js) } -// defaultOptions returns a new options struct with default values. -func defaultOptions() *options { - return &options{ +// defaultLogPayloadsOptions returns a new lpOptions struct with default values. +func defaultLogPayloadsOptions() *lpOptions { + return &lpOptions{ format: FormatJSON, maxsize: DefaultMaxSize, } } + +// defaultDebugLogEnablerOptions returns a new dleOptions struct with default values. +func defaultDebugLogEnablerOptions() *dleOptions { + return &dleOptions{ + path: "debug", + query: "debug-logs", + onval: "on", + offval: "off", + } +} + +// defaultPprofOptions returns a new pprofOptions struct with default values. +func defaultPprofOptions() *pprofOptions { + return &pprofOptions{ + prefix: "/debug/pprof/", + } +} diff --git a/debug/options_test.go b/debug/options_test.go index 5993fbd8..25ea42dd 100644 --- a/debug/options_test.go +++ b/debug/options_test.go @@ -6,8 +6,8 @@ import ( "testing" ) -func TestDefaultOptions(t *testing.T) { - opts := defaultOptions() +func TestDefaultLogPayloadsOptions(t *testing.T) { + opts := defaultLogPayloadsOptions() if fmt.Sprintf("%p", opts.format) != fmt.Sprintf("%p", FormatJSON) { t.Errorf("got format %p, expected %p", opts.format, FormatJSON) } @@ -16,8 +16,31 @@ func TestDefaultOptions(t *testing.T) { } } +func TestDefaultDebugLogEnablerOptions(t *testing.T) { + opts := defaultDebugLogEnablerOptions() + if opts.path != "debug" { + t.Errorf("got prefix %q, expected %q", opts.path, "debug") + } + if opts.query != "debug-logs" { + t.Errorf("got query %q, expected %q", opts.query, "debug-logs") + } + if opts.onval != "on" { + t.Errorf("got onval %q, expected %q", opts.onval, "on") + } + if opts.offval != "off" { + t.Errorf("got offval %q, expected %q", opts.offval, "off") + } +} + +func TestDefaultPprofOptions(t *testing.T) { + opts := defaultPprofOptions() + if opts.prefix != "/debug/pprof/" { + t.Errorf("got prefix %q, expected %q", opts.prefix, "/debug/pprof/") + } +} + func TestWithFormat(t *testing.T) { - opts := defaultOptions() + opts := defaultLogPayloadsOptions() WithFormat(FormatJSON)(opts) if fmt.Sprintf("%p", opts.format) != fmt.Sprintf("%p", FormatJSON) { t.Errorf("got format %p, expected %p", opts.format, FormatJSON) @@ -25,7 +48,7 @@ func TestWithFormat(t *testing.T) { } func TestWithMaxSize(t *testing.T) { - opts := defaultOptions() + opts := defaultLogPayloadsOptions() WithMaxSize(10)(opts) if opts.maxsize != 10 { t.Errorf("got maxsize %d, expected 10", opts.maxsize) @@ -33,13 +56,53 @@ func TestWithMaxSize(t *testing.T) { } func TestWithClient(t *testing.T) { - opts := defaultOptions() + opts := defaultLogPayloadsOptions() WithClient()(opts) if !opts.client { t.Errorf("got client %v, expected true", opts.client) } } +func TestWithPrefix(t *testing.T) { + opts := defaultDebugLogEnablerOptions() + WithPath("foo")(opts) + if opts.path != "foo" { + t.Errorf("got prefix %q, expected %q", opts.path, "foo") + } +} + +func TestWithQuery(t *testing.T) { + opts := defaultDebugLogEnablerOptions() + WithQuery("foo")(opts) + if opts.query != "foo" { + t.Errorf("got query %q, expected %q", opts.query, "foo") + } +} + +func TestWithOnValue(t *testing.T) { + opts := defaultDebugLogEnablerOptions() + WithOnValue("foo")(opts) + if opts.onval != "foo" { + t.Errorf("got onval %q, expected %q", opts.onval, "foo") + } +} + +func TestWithOffValue(t *testing.T) { + opts := defaultDebugLogEnablerOptions() + WithOffValue("foo")(opts) + if opts.offval != "foo" { + t.Errorf("got offval %q, expected %q", opts.offval, "foo") + } +} + +func TestWithPprofPrefix(t *testing.T) { + opts := defaultPprofOptions() + WithPrefix("foo")(opts) + if opts.prefix != "foo" { + t.Errorf("got pprofPrefix %q, expected %q", opts.prefix, "foo") + } +} + func TestFormatJSON(t *testing.T) { ctx := context.Background() js := FormatJSON(ctx, map[string]string{"foo": "bar"}) diff --git a/example/weather/services/forecaster/cmd/forecaster/main.go b/example/weather/services/forecaster/cmd/forecaster/main.go index 22d92497..28ae6d45 100644 --- a/example/weather/services/forecaster/cmd/forecaster/main.go +++ b/example/weather/services/forecaster/cmd/forecaster/main.go @@ -102,12 +102,12 @@ func main() { // 7. Setup health check, metrics and debug endpoints check := log.HTTP(ctx)(health.Handler(health.NewChecker(wc))) mux := http.NewServeMux() + debug.MountDebugLogEnabler(mux) debug.MountPprofHandlers(mux) mux.Handle("/healthz", check) mux.Handle("/livez", check) mux.Handle("/metrics", metrics.Handler(ctx)) - handler := debug.MountDebugLogEnabler("/debug", mux)(mux) - httpsvr := &http.Server{Addr: *httpaddr, Handler: handler} + httpsvr := &http.Server{Addr: *httpaddr, Handler: mux} // 8. Start gRPC and HTTP servers errc := make(chan error) diff --git a/example/weather/services/front/cmd/front/main.go b/example/weather/services/front/cmd/front/main.go index bbc46b11..d29caa1b 100644 --- a/example/weather/services/front/cmd/front/main.go +++ b/example/weather/services/front/cmd/front/main.go @@ -107,9 +107,9 @@ func main() { // 5. Create transport mux := goahttp.NewMuxer() + debug.MountDebugLogEnabler(debug.Adapt(mux)) mux.Use(metrics.HTTP(ctx)) - handler := trace.HTTP(ctx)(mux) // 5. Trace request - handler = debug.MountDebugLogEnabler("/debug", debug.Adapt(mux))(handler) // 4. Enable debug logs toggling + handler := trace.HTTP(ctx)(mux) // 4. Trace request handler = goahttpmiddleware.LogContext(log.AsGoaMiddlewareLogger)(handler) // 3. Log request and response handler = log.HTTP(ctx)(handler) // 2. Add logger to request context (with request ID key) handler = goahttpmiddleware.RequestID()(handler) // 1. Add request ID to context diff --git a/example/weather/services/locator/cmd/locator/main.go b/example/weather/services/locator/cmd/locator/main.go index 95325fcc..401a0fb9 100644 --- a/example/weather/services/locator/cmd/locator/main.go +++ b/example/weather/services/locator/cmd/locator/main.go @@ -102,12 +102,12 @@ func main() { // 7. Setup health check, metrics and debug endpoints check := log.HTTP(ctx)(health.Handler(health.NewChecker(ipc))) mux := http.NewServeMux() + debug.MountDebugLogEnabler(mux) debug.MountPprofHandlers(mux) mux.Handle("/healthz", check) mux.Handle("/livez", check) mux.Handle("/metrics", metrics.Handler(ctx)) - handler := debug.MountDebugLogEnabler("/debug", mux)(mux) - httpsvr := &http.Server{Addr: *httpaddr, Handler: handler} + httpsvr := &http.Server{Addr: *httpaddr, Handler: mux} // 8. Start gRPC and HTTP servers errc := make(chan error)