diff --git a/cmd/grpcurl/grpcurl.go b/cmd/grpcurl/grpcurl.go index fd00511..54e39fc 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,7 @@ 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.NewDefaultEventHandler(os.Stdout, descSource, formatter, verbosityLevel) err = grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), h, rf.Next) if err != nil { @@ -676,7 +686,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 3e47789..c75d3fc 100644 --- a/format.go +++ b/format.go @@ -412,7 +412,10 @@ type DefaultEventHandler struct { out io.Writer descSource DescriptorSource formatter func(proto.Message) (string, error) - verbose bool + // 0 = default + // 1 = verbose + // 2 = very verbose + verbosityLevel int // NumResponses is the number of responses that have been received. NumResponses int @@ -424,19 +427,19 @@ 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. -func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbose bool) *DefaultEventHandler { +func NewDefaultEventHandler(out io.Writer, descSource DescriptorSource, formatter Formatter, verbosityLevel int) *DefaultEventHandler { return &DefaultEventHandler{ - out: out, - descSource: descSource, - formatter: formatter, - verbose: verbose, + out: out, + descSource: descSource, + formatter: formatter, + verbosityLevel: verbosityLevel, } } var _ InvocationEventHandler = (*DefaultEventHandler)(nil) func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) { - if h.verbose { + if h.verbosityLevel > 0 { txt, err := GetDescriptorText(md, h.descSource) if err == nil { fmt.Fprintf(h.out, "\nResolved method descriptor:\n%s\n", txt) @@ -445,21 +448,23 @@ func (h *DefaultEventHandler) OnResolveMethod(md *desc.MethodDescriptor) { } func (h *DefaultEventHandler) OnSendHeaders(md metadata.MD) { - if h.verbose { + 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 { + 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.Fprintf(h.out, "\nResponse size (bytes): %d\n", proto.Size(resp)) + if h.verbosityLevel > 1 { + fmt.Fprintf(h.out, "\nEstimated response size: %d bytes\n", proto.Size(resp)) + } + if h.verbosityLevel > 0 { fmt.Fprint(h.out, "\nResponse contents:\n") } if respStr, err := h.formatter(resp); err != nil { @@ -471,7 +476,7 @@ func (h *DefaultEventHandler) OnReceiveResponse(resp proto.Message) { func (h *DefaultEventHandler) OnReceiveTrailers(stat *status.Status, md metadata.MD) { h.Status = stat - if h.verbose { + 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..28e2be5 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,7 @@ func TestHandler(t *testing.T) { } var buf bytes.Buffer - h := NewDefaultEventHandler(&buf, source, formatter, verbose) + h := NewDefaultEventHandler(&buf, source, formatter, verbosityLevel) h.OnResolveMethod(md) h.OnSendHeaders(reqHeaders) @@ -148,6 +150,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 +231,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 db7617f..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=