mirror of
https://github.com/fullstorydev/grpcurl.git
synced 2026-05-24 20:51:45 +03:00
initial commit, functioning grpcurl command-line util
This commit is contained in:
456
cmd/grpcurl/grpcurl.go
Normal file
456
cmd/grpcurl/grpcurl.go
Normal file
@@ -0,0 +1,456 @@
|
||||
// Command grpcurl makes GRPC requests (a la cURL, but HTTP/2). It can use a supplied descriptor file or
|
||||
// service reflection to translate JSON request data into the appropriate protobuf request data and vice
|
||||
// versa for presenting the response contents.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jhump/protoreflect/desc"
|
||||
"github.com/jhump/protoreflect/grpcreflect"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/fullstorydev/grpcurl"
|
||||
)
|
||||
|
||||
var (
|
||||
exit = os.Exit
|
||||
|
||||
help = flag.Bool("help", false,
|
||||
`Print usage instructions and exit.`)
|
||||
plaintext = flag.Bool("plaintext", false,
|
||||
`Use plain-text HTTP/2 when connecting to server (no TLS).`)
|
||||
insecure = flag.Bool("insecure", false,
|
||||
`Skip server certificate and domain verification. (NOT SECURE!). Not
|
||||
valid with -plaintext option.`)
|
||||
cacert = flag.String("cacert", "",
|
||||
`File containing trusted root certificates for verifying the server.
|
||||
Ignored if -insecure is specified.`)
|
||||
cert = flag.String("cert", "",
|
||||
`File containing client certificate (public key), to present to the
|
||||
server. Not valid with -plaintext option. Must also provide -key option.`)
|
||||
key = flag.String("key", "",
|
||||
`File containing client private key, to present to the server. Not valid
|
||||
with -plaintext option. Must also provide -cert option.`)
|
||||
protoset multiString
|
||||
addlHeaders multiString
|
||||
data = flag.String("d", "",
|
||||
`JSON request contents. If the value is '@' then the request contents are
|
||||
read from stdin. For calls that accept a stream of requests, the
|
||||
contents should include all such request messages concatenated together
|
||||
(optionally separated by whitespace).`)
|
||||
connectTimeout = flag.String("connect-timeout", "",
|
||||
`The maximum time, in seconds, to wait for connection to be established.
|
||||
Defaults to 10 seconds.`)
|
||||
keepaliveTime = flag.String("keepalive-time", "",
|
||||
`If present, the maximum idle time in seconds, after which a keepalive
|
||||
probe is sent. If the connection remains idle and no keepalive response
|
||||
is received for this same period then the connection is closed and the
|
||||
operation fails.`)
|
||||
maxTime = flag.String("max-time", "",
|
||||
`The maximum total time the operation can take. This is useful for
|
||||
preventing batch jobs that use grpcurl from hanging due to slow or bad
|
||||
network links or due to incorrect stream method usage.`)
|
||||
verbose = flag.Bool("v", false,
|
||||
`Enable verbose output.`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Var(&addlHeaders, "H",
|
||||
`Additional request headers in 'name: value' format. May specify more
|
||||
than one via multiple -H flags.`)
|
||||
flag.Var(&protoset, "protoset",
|
||||
`The name of a file containing an encoded FileDescriptorSet. This file's
|
||||
contents will be used to determine the RPC schema instead of querying
|
||||
for it from the remote server via the GRPC reflection API. When set: the
|
||||
'list' action lists the services found in the given descriptors (vs.
|
||||
those exposed by the remote server), and the 'describe' action describes
|
||||
symbols found in the given descriptors. May specify more than one via
|
||||
multiple -protoset flags.`)
|
||||
}
|
||||
|
||||
type multiString []string
|
||||
|
||||
func (s *multiString) String() string {
|
||||
return strings.Join(*s, ",")
|
||||
}
|
||||
|
||||
func (s *multiString) Set(value string) error {
|
||||
*s = append(*s, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.CommandLine.Usage = usage
|
||||
flag.Parse()
|
||||
if *help {
|
||||
usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Do extra validation on arguments and figure out what user asked us to do.
|
||||
if *plaintext && *insecure {
|
||||
fail(nil, "The -plaintext and -insecure arguments are mutually exclusive.")
|
||||
}
|
||||
if *plaintext && *cert != "" {
|
||||
fail(nil, "The -plaintext and -cert arguments are mutually exclusive.")
|
||||
}
|
||||
if *plaintext && *key != "" {
|
||||
fail(nil, "The -plaintext and -key arguments are mutually exclusive.")
|
||||
}
|
||||
if (*key == "") != (*cert == "") {
|
||||
fail(nil, "The -cert and -key arguments must be used together and both be present.")
|
||||
}
|
||||
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) == 0 {
|
||||
fail(nil, "Too few arguments.")
|
||||
}
|
||||
var target string
|
||||
if args[0] != "list" && args[0] != "describe" {
|
||||
target = args[0]
|
||||
args = args[1:]
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
fail(nil, "Too few arguments.")
|
||||
}
|
||||
var list, describe, invoke bool
|
||||
if args[0] == "list" {
|
||||
list = true
|
||||
args = args[1:]
|
||||
} else if args[0] == "describe" {
|
||||
describe = true
|
||||
args = args[1:]
|
||||
} else {
|
||||
invoke = true
|
||||
}
|
||||
|
||||
var symbol string
|
||||
if invoke {
|
||||
if len(args) == 0 {
|
||||
fail(nil, "Too few arguments.")
|
||||
}
|
||||
symbol = args[0]
|
||||
args = args[1:]
|
||||
} else {
|
||||
if *data != "" {
|
||||
fail(nil, "The -d argument is not used with 'list' or 'describe' verb.")
|
||||
}
|
||||
if len(addlHeaders) > 0 {
|
||||
fail(nil, "The -H argument is not used with 'list' or 'describe' verb.")
|
||||
}
|
||||
if len(args) > 0 {
|
||||
symbol = args[0]
|
||||
args = args[1:]
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
fail(nil, "Too many arguments.")
|
||||
}
|
||||
if invoke && target == "" {
|
||||
fail(nil, "No host:port specified.")
|
||||
}
|
||||
if len(protoset) == 0 && target == "" {
|
||||
fail(nil, "No host:port specified and no protoset specified.")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if *maxTime != "" {
|
||||
t, err := strconv.ParseFloat(*maxTime, 64)
|
||||
if err != nil {
|
||||
fail(nil, "The -max-time argument must be a valid number.")
|
||||
}
|
||||
timeout := time.Duration(t * float64(time.Second))
|
||||
ctx, _ = context.WithTimeout(ctx, timeout)
|
||||
}
|
||||
|
||||
dial := func() *grpc.ClientConn {
|
||||
dialTime := 10 * time.Second
|
||||
if *connectTimeout != "" {
|
||||
t, err := strconv.ParseFloat(*connectTimeout, 64)
|
||||
if err != nil {
|
||||
fail(nil, "The -connect-timeout argument must be a valid number.")
|
||||
}
|
||||
dialTime = time.Duration(t * float64(time.Second))
|
||||
}
|
||||
opts := []grpc.DialOption{grpc.WithTimeout(dialTime), grpc.WithBlock()}
|
||||
if *keepaliveTime != "" {
|
||||
t, err := strconv.ParseFloat(*keepaliveTime, 64)
|
||||
if err != nil {
|
||||
fail(nil, "The -keepalive-time argument must be a valid number.")
|
||||
}
|
||||
timeout := time.Duration(t * float64(time.Second))
|
||||
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: timeout,
|
||||
Timeout: timeout,
|
||||
}))
|
||||
}
|
||||
if *plaintext {
|
||||
opts = append(opts, grpc.WithInsecure())
|
||||
} else {
|
||||
creds, err := grpcurl.ClientTransportCredentials(*insecure, *cacert, *cert, *key)
|
||||
if err != nil {
|
||||
fail(err, "Failed to configure transport credentials")
|
||||
}
|
||||
opts = append(opts, grpc.WithTransportCredentials(creds))
|
||||
}
|
||||
cc, err := grpc.Dial(target, opts...)
|
||||
if err != nil {
|
||||
fail(err, "Failed to dial target host %q", target)
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
var cc *grpc.ClientConn
|
||||
var descSource grpcurl.DescriptorSource
|
||||
var refClient *grpcreflect.Client
|
||||
if len(protoset) > 0 {
|
||||
var err error
|
||||
descSource, err = grpcurl.DescriptorSourceFromProtoSets(protoset...)
|
||||
if err != nil {
|
||||
fail(err, "Failed to process proto descriptor sets")
|
||||
}
|
||||
} else {
|
||||
cc = dial()
|
||||
refClient = grpcreflect.NewClient(ctx, reflectpb.NewServerReflectionClient(cc))
|
||||
descSource = grpcurl.DescriptorSourceFromServer(ctx, refClient)
|
||||
}
|
||||
|
||||
// arrange for the RPCs to be cleanly shutdown
|
||||
reset := func() {
|
||||
if refClient != nil {
|
||||
refClient.Reset()
|
||||
refClient = nil
|
||||
}
|
||||
if cc != nil {
|
||||
cc.Close()
|
||||
cc = nil
|
||||
}
|
||||
}
|
||||
defer reset()
|
||||
exit = func(code int) {
|
||||
// since defers aren't run by os.Exit...
|
||||
reset()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
if list {
|
||||
if symbol == "" {
|
||||
svcs, err := grpcurl.ListServices(descSource)
|
||||
if err != nil {
|
||||
fail(err, "Failed to list services")
|
||||
}
|
||||
if len(svcs) == 0 {
|
||||
fmt.Println("(No services)")
|
||||
} else {
|
||||
for _, svc := range svcs {
|
||||
fmt.Printf("%s\n", svc)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
methods, err := grpcurl.ListMethods(descSource, symbol)
|
||||
if err != nil {
|
||||
fail(err, "Failed to list methods for service %q", symbol)
|
||||
}
|
||||
if len(methods) == 0 {
|
||||
fmt.Println("(No methods)") // probably unlikely
|
||||
} else {
|
||||
for _, m := range methods {
|
||||
fmt.Printf("%s\n", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if describe {
|
||||
var symbols []string
|
||||
if symbol != "" {
|
||||
symbols = []string{symbol}
|
||||
} else {
|
||||
// if no symbol given, describe all exposed services
|
||||
svcs, err := descSource.ListServices()
|
||||
if err != nil {
|
||||
fail(err, "Failed to list services")
|
||||
}
|
||||
if len(svcs) == 0 {
|
||||
fmt.Println("Server returned an empty list of exposed services")
|
||||
}
|
||||
symbols = svcs
|
||||
}
|
||||
for _, s := range symbols {
|
||||
dsc, err := descSource.FindSymbol(s)
|
||||
if err != nil {
|
||||
fail(err, "Failed to resolve symbol %q", s)
|
||||
}
|
||||
|
||||
txt, err := grpcurl.GetDescriptorText(dsc, descSource)
|
||||
if err != nil {
|
||||
fail(err, "Failed to describe symbol %q", s)
|
||||
}
|
||||
|
||||
switch dsc.(type) {
|
||||
case *desc.MessageDescriptor:
|
||||
fmt.Printf("%s is a message:\n", dsc.GetFullyQualifiedName())
|
||||
case *desc.FieldDescriptor:
|
||||
fmt.Printf("%s is a field:\n", dsc.GetFullyQualifiedName())
|
||||
case *desc.OneOfDescriptor:
|
||||
fmt.Printf("%s is a one-of:\n", dsc.GetFullyQualifiedName())
|
||||
case *desc.EnumDescriptor:
|
||||
fmt.Printf("%s is an enum:\n", dsc.GetFullyQualifiedName())
|
||||
case *desc.EnumValueDescriptor:
|
||||
fmt.Printf("%s is an enum value:\n", dsc.GetFullyQualifiedName())
|
||||
case *desc.ServiceDescriptor:
|
||||
fmt.Printf("%s is a service:\n", dsc.GetFullyQualifiedName())
|
||||
case *desc.MethodDescriptor:
|
||||
fmt.Printf("%s is a method:\n", dsc.GetFullyQualifiedName())
|
||||
default:
|
||||
err = fmt.Errorf("descriptor has unrecognized type %T", dsc)
|
||||
fail(err, "Failed to describe symbol %q", s)
|
||||
}
|
||||
fmt.Println(txt)
|
||||
}
|
||||
|
||||
} else {
|
||||
// Invoke an RPC
|
||||
if cc == nil {
|
||||
cc = dial()
|
||||
}
|
||||
var dec *json.Decoder
|
||||
if *data == "@" {
|
||||
dec = json.NewDecoder(os.Stdin)
|
||||
} else {
|
||||
dec = json.NewDecoder(strings.NewReader(*data))
|
||||
}
|
||||
|
||||
h := &handler{dec: dec, descSource: descSource}
|
||||
err := grpcurl.InvokeRpc(ctx, descSource, cc, symbol, addlHeaders, h, h.getRequestData)
|
||||
if err != nil {
|
||||
fail(err, "Error invoking method %q", symbol)
|
||||
}
|
||||
reqSuffix := ""
|
||||
respSuffix := ""
|
||||
if h.reqCount != 1 {
|
||||
reqSuffix = "s"
|
||||
}
|
||||
if h.respCount != 1 {
|
||||
respSuffix = "s"
|
||||
}
|
||||
fmt.Printf("Sent %d request%s and received %d response%s\n", h.reqCount, reqSuffix, h.respCount, respSuffix)
|
||||
if h.stat.Code() != codes.OK {
|
||||
fmt.Fprintf(os.Stderr, "ERROR:\n Code: %s\n Message: %s\n", h.stat.Code().String(), h.stat.Message())
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, `Usage:
|
||||
%s [flags] [host:port] [list|describe] [symbol]
|
||||
|
||||
The 'host:port' is only optional when used with 'list' or 'describe' and a
|
||||
protoset flag is provided.
|
||||
|
||||
If 'list' is indicated, the symbol (if present) should be a fully-qualified
|
||||
service name. If present, all methods of that service are listed. If not
|
||||
present, all exposed services are listed, or all services defined in protosets.
|
||||
|
||||
If 'describe' is indicated, the descriptor for the given symbol is shown. The
|
||||
symbol should be a fully-qualified service, enum, or message name. If no symbol
|
||||
is given then the descriptors for all exposed or known services are shown.
|
||||
|
||||
If neither verb is present, the symbol must be a fully-qualified method name in
|
||||
'service/method' or 'service.method' format. In this case, the request body will
|
||||
be used to invoke the named method. If no body is given, an empty instance of
|
||||
the method's request type will be sent.
|
||||
|
||||
`, os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
|
||||
}
|
||||
|
||||
func fail(err error, msg string, args ...interface{}) {
|
||||
if err != nil {
|
||||
msg += ": %v"
|
||||
args = append(args, err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, msg, args...)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
if err != nil {
|
||||
exit(1)
|
||||
} else {
|
||||
// nil error means it was CLI usage issue
|
||||
fmt.Fprintf(os.Stderr, "Try '%s -help' for more details.\n", os.Args[0])
|
||||
exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
dec *json.Decoder
|
||||
descSource grpcurl.DescriptorSource
|
||||
reqCount int
|
||||
respCount int
|
||||
stat *status.Status
|
||||
}
|
||||
|
||||
func (h *handler) OnResolveMethod(md *desc.MethodDescriptor) {
|
||||
if *verbose {
|
||||
txt, err := grpcurl.GetDescriptorText(md, h.descSource)
|
||||
if err == nil {
|
||||
fmt.Printf("\nResolved method descriptor:\n%s\n", txt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (*handler) OnSendHeaders(md metadata.MD) {
|
||||
if *verbose {
|
||||
fmt.Printf("\nRequest metadata to send:\n%s\n", grpcurl.MetadataToString(md))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) getRequestData() (json.RawMessage, error) {
|
||||
// we don't use a mutex, though this methods will be called from different goroutine
|
||||
// than other methods for bidi calls, because this method does not share any state
|
||||
// with the other methods.
|
||||
var msg json.RawMessage
|
||||
if err := h.dec.Decode(&msg); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
h.reqCount++
|
||||
return msg, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (*handler) OnReceiveHeaders(md metadata.MD) {
|
||||
if *verbose {
|
||||
fmt.Printf("\nResponse headers received:\n%s\n", grpcurl.MetadataToString(md))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) OnReceiveResponse(rsp json.RawMessage) {
|
||||
h.respCount++
|
||||
if *verbose {
|
||||
fmt.Print("\nResponse contents:\n")
|
||||
}
|
||||
fmt.Println(string(rsp))
|
||||
}
|
||||
|
||||
func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
|
||||
h.stat = stat
|
||||
if *verbose {
|
||||
fmt.Printf("\nResponse trailers received:\n%s\n", grpcurl.MetadataToString(md))
|
||||
}
|
||||
}
|
||||
168
cmd/testserver/testserver.go
Normal file
168
cmd/testserver/testserver.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// Command testserver spins up a test GRPC server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/interop/grpc_testing"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/fullstorydev/grpcurl"
|
||||
grpcurl_testing "github.com/fullstorydev/grpcurl/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
help = flag.Bool("help", false, "Print usage instructions and exit.")
|
||||
cacert = flag.String("cacert", "",
|
||||
"File containing trusted root certificates for verifying client certs. Ignored if\n"+
|
||||
" TLS is not in use (e.g. no -cert or -key specified).")
|
||||
cert = flag.String("cert", "",
|
||||
"File containing server certificate (public key). Must also provide -key option.\n"+
|
||||
" Server uses plain-text if no -cert and -key options are given.")
|
||||
key = flag.String("key", "",
|
||||
"File containing server private key. Must also provide -cert option. Server uses\n"+
|
||||
" plain-text if no -cert and -key options are given.")
|
||||
requirecert = flag.Bool("requirecert", false,
|
||||
"Require clients to authenticate via client certs. Must be using TLS (e.g. must also\n"+
|
||||
" provide -cert and -key options).")
|
||||
port = flag.Int("p", 0, "Port on which to listen. Ephemeral port used if not specified.")
|
||||
noreflect = flag.Bool("noreflect", false, "Indicates that server should not support server reflection.")
|
||||
quiet = flag.Bool("q", false, "Suppresses server request and stream logging.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stdout, os.Stdout, os.Stderr))
|
||||
|
||||
if len(flag.Args()) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "No arguments expected.")
|
||||
os.Exit(2)
|
||||
}
|
||||
if (*cert == "") != (*key == "") {
|
||||
fmt.Fprintln(os.Stderr, "The -cert and -key arguments must be used together and both be present.")
|
||||
os.Exit(2)
|
||||
}
|
||||
if *requirecert && *cert == "" {
|
||||
fmt.Fprintln(os.Stderr, "The -requirecert arg cannot be used without -cert and -key arguments.")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
var opts []grpc.ServerOption
|
||||
if *cert != "" {
|
||||
creds, err := grpcurl.ServerTransportCredentials(*cacert, *cert, *key, *requirecert)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to configure transport credentials: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
opts = []grpc.ServerOption{grpc.Creds(creds)}
|
||||
}
|
||||
if !*quiet {
|
||||
opts = append(opts, grpc.UnaryInterceptor(unaryLogger), grpc.StreamInterceptor(streamLogger))
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to listen on socket: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
p := l.Addr().(*net.TCPAddr).Port
|
||||
fmt.Printf("Listening on 127.0.0.1:%d\n", p)
|
||||
|
||||
svr := grpc.NewServer(opts...)
|
||||
|
||||
grpc_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||
if !*noreflect {
|
||||
reflection.Register(svr)
|
||||
}
|
||||
|
||||
if err := svr.Serve(l); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "GRPC server returned error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var id int32
|
||||
|
||||
func unaryLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
i := atomic.AddInt32(&id, 1) - 1
|
||||
grpclog.Printf("start <%d>: %s\n", i, info.FullMethod)
|
||||
start := time.Now()
|
||||
rsp, err := handler(ctx, req)
|
||||
var code codes.Code
|
||||
if stat, ok := status.FromError(err); ok {
|
||||
code = stat.Code()
|
||||
} else {
|
||||
code = codes.Unknown
|
||||
}
|
||||
grpclog.Printf("completed <%d>: %v (%d) %v\n", i, code, code, time.Now().Sub(start))
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func streamLogger(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
i := atomic.AddInt32(&id, 1) - 1
|
||||
start := time.Now()
|
||||
grpclog.Printf("start <%d>: %s\n", i, info.FullMethod)
|
||||
err := handler(srv, loggingStream{ss: ss, id: i})
|
||||
var code codes.Code
|
||||
if stat, ok := status.FromError(err); ok {
|
||||
code = stat.Code()
|
||||
} else {
|
||||
code = codes.Unknown
|
||||
}
|
||||
grpclog.Printf("completed <%d>: %v(%d) %v\n", i, code, code, time.Now().Sub(start))
|
||||
return err
|
||||
}
|
||||
|
||||
type loggingStream struct {
|
||||
ss grpc.ServerStream
|
||||
id int32
|
||||
}
|
||||
|
||||
func (l loggingStream) SetHeader(md metadata.MD) error {
|
||||
return l.ss.SetHeader(md)
|
||||
}
|
||||
|
||||
func (l loggingStream) SendHeader(md metadata.MD) error {
|
||||
return l.ss.SendHeader(md)
|
||||
}
|
||||
|
||||
func (l loggingStream) SetTrailer(md metadata.MD) {
|
||||
l.ss.SetTrailer(md)
|
||||
}
|
||||
|
||||
func (l loggingStream) Context() context.Context {
|
||||
return l.ss.Context()
|
||||
}
|
||||
|
||||
func (l loggingStream) SendMsg(m interface{}) error {
|
||||
err := l.ss.SendMsg(m)
|
||||
if err == nil {
|
||||
grpclog.Printf("stream <%d>: sent message\n", l.id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l loggingStream) RecvMsg(m interface{}) error {
|
||||
err := l.ss.RecvMsg(m)
|
||||
if err == nil {
|
||||
grpclog.Printf("stream <%d>: received message\n", l.id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user