diff --git a/cmd/grpcurl/grpcurl.go b/cmd/grpcurl/grpcurl.go index fd00511..66b2990 100644 --- a/cmd/grpcurl/grpcurl.go +++ b/cmd/grpcurl/grpcurl.go @@ -135,6 +135,8 @@ var ( When describing messages, show a template of input data.`)) verbose = flags.Bool("v", false, prettify(` Enable verbose output.`)) + veryVerbose = flags.Bool("vv", false, prettify(` + Enable very verbose output.`)) serverName = flags.String("servername", "", prettify(` Override server name when validating TLS certificate. This flag is ignored if -plaintext or -insecure is used. @@ -316,6 +318,14 @@ func main() { invoke = true } + verbosityLevel := 0 + if *verbose { + verbosityLevel = 1 + } + if *veryVerbose { + verbosityLevel = 2 + } + var symbol string if invoke { if len(args) == 0 { @@ -651,7 +661,7 @@ func main() { // if not verbose output, then also include record delimiters // between each message, so output could potentially be piped // to another grpcurl process - includeSeparators := !*verbose + includeSeparators := verbosityLevel == 0 options := grpcurl.FormatOptions{ EmitJSONDefaultFields: *emitDefaults, IncludeTextSeparator: includeSeparators, @@ -661,7 +671,11 @@ func main() { if err != nil { fail(err, "Failed to construct request parser and formatter for %q", *format) } - h := grpcurl.NewDefaultEventHandler(os.Stdout, descSource, formatter, *verbose) + h := &grpcurl.DefaultEventHandler{ + Out: os.Stdout, + Formatter: formatter, + VerbosityLevel: verbosityLevel, + } err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next) if err != nil { @@ -676,7 +690,7 @@ func main() { if h.NumResponses != 1 { respSuffix = "s" } - if *verbose { + if verbosityLevel > 0 { fmt.Printf("Sent %d request%s and received %d response%s\n", reqCount, reqSuffix, h.NumResponses, respSuffix) } if h.Status.Code() != codes.OK { diff --git a/format.go b/format.go index 54cd5f8..98d6a24 100644 --- a/format.go +++ b/format.go @@ -409,10 +409,12 @@ func RequestParserAndFormatterFor(format Format, descSource DescriptorSource, em // safe for use with InvokeRPC as long as NumResponses and Status are not read // until the call to InvokeRPC completes. type DefaultEventHandler struct { - out io.Writer - descSource DescriptorSource - formatter func(proto.Message) (string, error) - verbose bool + Out io.Writer + Formatter Formatter + // 0 = default + // 1 = verbose + // 2 = very verbose + VerbosityLevel int // NumResponses is the number of responses that have been received. NumResponses int @@ -424,54 +426,65 @@ type DefaultEventHandler struct { // NewDefaultEventHandler returns an InvocationEventHandler that logs events to // the given output. If verbose is true, all events are logged. Otherwise, only // response messages are logged. +// +// Deprecated: NewDefaultEventHandler exists for compatability. +// It doesn't allow fine control over the `VerbosityLevel` +// and provides only 0 and 1 options (which corresponds to the `verbose` argument). +// Use DefaultEventHandler{} initializer directly. func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbose bool) *DefaultEventHandler { + verbosityLevel := 0 + if verbose { + verbosityLevel = 1 + } return &DefaultEventHandler{ - out: out, - descSource: descSource, - formatter: formatter, - verbose: verbose, + Out: out, + Formatter: formatter, + VerbosityLevel: verbosityLevel, } } var _ InvocationEventHandler = (*DefaultEventHandler)(nil) func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) { - if h.verbose { - txt, err := GetDescriptorText(md, h.descSource) + if h.VerbosityLevel > 0 { + txt, err := GetDescriptorText(md, nil) if err == nil { - fmt.Fprintf(h.out, "\nResolved method descriptor:\n%s\n", txt) + fmt.Fprintf(h.Out, "\nResolved method descriptor:\n%s\n", txt) } } } func (h *DefaultEventHandler) OnSendHeaders(md metadata.MD) { - if h.verbose { - fmt.Fprintf(h.out, "\nRequest metadata to send:\n%s\n", MetadataToString(md)) + if h.VerbosityLevel > 0 { + fmt.Fprintf(h.Out, "\nRequest metadata to send:\n%s\n", MetadataToString(md)) } } func (h *DefaultEventHandler) OnReceiveHeaders(md metadata.MD) { - if h.verbose { - fmt.Fprintf(h.out, "\nResponse headers received:\n%s\n", MetadataToString(md)) + if h.VerbosityLevel > 0 { + fmt.Fprintf(h.Out, "\nResponse headers received:\n%s\n", MetadataToString(md)) } } func (h *DefaultEventHandler) OnReceiveResponse(resp proto.Message) { h.NumResponses++ - if h.verbose { - fmt.Fprint(h.out, "\nResponse contents:\n") + if h.VerbosityLevel > 1 { + fmt.Fprintf(h.Out, "\nEstimated response size: %d bytes\n", proto.Size(resp)) } - if respStr, err := h.formatter(resp); err != nil { - fmt.Fprintf(h.out, "Failed to format response message %d: %v\n", h.NumResponses, err) + if h.VerbosityLevel > 0 { + fmt.Fprint(h.Out, "\nResponse contents:\n") + } + if respStr, err := h.Formatter(resp); err != nil { + fmt.Fprintf(h.Out, "Failed to format response message %d: %v\n", h.NumResponses, err) } else { - fmt.Fprintln(h.out, respStr) + fmt.Fprintln(h.Out, respStr) } } func (h *DefaultEventHandler) OnReceiveTrailers(stat *status.Status, md metadata.MD) { h.Status = stat - if h.verbose { - fmt.Fprintf(h.out, "\nResponse trailers received:\n%s\n", MetadataToString(md)) + if h.VerbosityLevel > 0 { + fmt.Fprintf(h.Out, "\nResponse trailers received:\n%s\n", MetadataToString(md)) } } diff --git a/format_test.go b/format_test.go index 3e517e3..1a36059 100644 --- a/format_test.go +++ b/format_test.go @@ -120,12 +120,14 @@ func TestHandler(t *testing.T) { for _, format := range []Format{FormatJSON, FormatText} { for _, numMessages := range []int{1, 3} { - for _, verbose := range []bool{true, false} { + for verbosityLevel := 0; verbosityLevel <= 2; verbosityLevel++ { name := fmt.Sprintf("%s, %d message(s)", format, numMessages) - if verbose { - name += ", verbose" + if verbosityLevel > 0 { + name += fmt.Sprintf(", verbosityLevel=%d", verbosityLevel) } + verbose := verbosityLevel > 0 + _, formatter, err := RequestParserAndFormatter(format, source, nil, FormatOptions{IncludeTextSeparator: !verbose}) if err != nil { t.Errorf("Failed to create parser and formatter: %v", err) @@ -133,7 +135,11 @@ func TestHandler(t *testing.T) { } var buf bytes.Buffer - h := NewDefaultEventHandler(&buf, source, formatter, verbose) + h := &DefaultEventHandler{ + Out: &buf, + Formatter: formatter, + VerbosityLevel: verbosityLevel, + } h.OnResolveMethod(md) h.OnSendHeaders(reqHeaders) @@ -148,6 +154,9 @@ func TestHandler(t *testing.T) { expectedOutput += verbosePrefix } for i := 0; i < numMessages; i++ { + if verbosityLevel > 1 { + expectedOutput += verboseResponseSize + } if verbose { expectedOutput += verboseResponseHeader } @@ -226,6 +235,9 @@ Response trailers received: a: 1 b: 2 c: 3 +` + verboseResponseSize = ` +Estimated response size: 100 bytes ` verboseResponseHeader = ` Response contents: diff --git a/go.sum b/go.sum index 6e6a698..8173527 100644 --- a/go.sum +++ b/go.sum @@ -365,6 +365,7 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -444,6 +445,7 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=