1 package grpc_zap_test
2
3 import (
4 "context"
5 "io"
6 "runtime"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/suite"
11 "google.golang.org/grpc"
12
13 "github.com/grpc-ecosystem/go-grpc-middleware"
14 "github.com/grpc-ecosystem/go-grpc-middleware/tags"
15 pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18
19 "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
20 "go.uber.org/zap"
21 "go.uber.org/zap/zapcore"
22 )
23
24 func TestZapPayloadSuite(t *testing.T) {
25 if strings.HasPrefix(runtime.Version(), "go1.7") {
26 t.Skipf("Skipping due to json.RawMessage incompatibility with go1.7")
27 return
28 }
29
30 alwaysLoggingDeciderServer := func(ctx context.Context, fullMethodName string, servingObject interface{}) bool { return true }
31 alwaysLoggingDeciderClient := func(ctx context.Context, fullMethodName string) bool { return true }
32
33 b := newBaseZapSuite(t)
34 b.InterceptorTestSuite.ClientOpts = []grpc.DialOption{
35 grpc.WithUnaryInterceptor(grpc_zap.PayloadUnaryClientInterceptor(b.log, alwaysLoggingDeciderClient)),
36 grpc.WithStreamInterceptor(grpc_zap.PayloadStreamClientInterceptor(b.log, alwaysLoggingDeciderClient)),
37 }
38 noOpZap := zap.New(zapcore.NewNopCore())
39 b.InterceptorTestSuite.ServerOpts = []grpc.ServerOption{
40 grpc_middleware.WithStreamServerChain(
41 grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
42 grpc_zap.StreamServerInterceptor(noOpZap),
43 grpc_zap.PayloadStreamServerInterceptor(b.log, alwaysLoggingDeciderServer)),
44 grpc_middleware.WithUnaryServerChain(
45 grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)),
46 grpc_zap.UnaryServerInterceptor(noOpZap),
47 grpc_zap.PayloadUnaryServerInterceptor(b.log, alwaysLoggingDeciderServer)),
48 }
49 suite.Run(t, &zapPayloadSuite{b})
50 }
51
52 type zapPayloadSuite struct {
53 *zapBaseSuite
54 }
55
56 func (s *zapPayloadSuite) getServerAndClientMessages(expectedServer int, expectedClient int) (serverMsgs []map[string]interface{}, clientMsgs []map[string]interface{}) {
57 msgs := s.getOutputJSONs()
58 for _, m := range msgs {
59 if m["span.kind"] == "server" {
60 serverMsgs = append(serverMsgs, m)
61 } else if m["span.kind"] == "client" {
62 clientMsgs = append(clientMsgs, m)
63 }
64 }
65 require.Len(s.T(), serverMsgs, expectedServer, "must match expected number of server log messages")
66 require.Len(s.T(), clientMsgs, expectedClient, "must match expected number of client log messages")
67 return serverMsgs, clientMsgs
68 }
69
70 func (s *zapPayloadSuite) TestPing_LogsBothRequestAndResponse() {
71 _, err := s.Client.Ping(s.SimpleCtx(), goodPing)
72
73 require.NoError(s.T(), err, "there must be not be an error on a successful call")
74 serverMsgs, clientMsgs := s.getServerAndClientMessages(2, 2)
75 for _, m := range append(serverMsgs, clientMsgs...) {
76 assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
77 assert.Equal(s.T(), m["grpc.method"], "Ping", "all lines must contain method name")
78 assert.Equal(s.T(), m["level"], "info", "all payloads must be logged on info level")
79 }
80
81 serverReq, serverResp := serverMsgs[0], serverMsgs[1]
82 clientReq, clientResp := clientMsgs[0], clientMsgs[1]
83 s.T().Log(clientReq)
84 assert.Contains(s.T(), clientReq, "grpc.request.content", "request payload must be logged in a structured way")
85 assert.Contains(s.T(), serverReq, "grpc.request.content", "request payload must be logged in a structured way")
86 assert.Contains(s.T(), clientResp, "grpc.response.content", "response payload must be logged in a structured way")
87 assert.Contains(s.T(), serverResp, "grpc.response.content", "response payload must be logged in a structured way")
88 }
89
90 func (s *zapPayloadSuite) TestPingError_LogsOnlyRequestsOnError() {
91 _, err := s.Client.PingError(s.SimpleCtx(), &pb_testproto.PingRequest{Value: "something", ErrorCodeReturned: uint32(4)})
92
93 require.Error(s.T(), err, "there must be an error on an unsuccessful call")
94 serverMsgs, clientMsgs := s.getServerAndClientMessages(1, 1)
95 for _, m := range append(serverMsgs, clientMsgs...) {
96 assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
97 assert.Equal(s.T(), m["grpc.method"], "PingError", "all lines must contain method name")
98 assert.Equal(s.T(), m["level"], "info", "must be logged at the info level")
99 }
100
101 assert.Contains(s.T(), clientMsgs[0], "grpc.request.content", "request payload must be logged in a structured way")
102 assert.Contains(s.T(), serverMsgs[0], "grpc.request.content", "request payload must be logged in a structured way")
103 }
104
105 func (s *zapPayloadSuite) TestPingStream_LogsAllRequestsAndResponses() {
106 messagesExpected := 20
107 stream, err := s.Client.PingStream(s.SimpleCtx())
108
109 require.NoError(s.T(), err, "no error on stream creation")
110 for i := 0; i < messagesExpected; i++ {
111 require.NoError(s.T(), stream.Send(goodPing), "sending must succeed")
112 }
113 require.NoError(s.T(), stream.CloseSend(), "no error on send stream")
114
115 for {
116 pong := &pb_testproto.PingResponse{}
117 err := stream.RecvMsg(pong)
118 if err == io.EOF {
119 break
120 }
121 require.NoError(s.T(), err, "no error on receive")
122 }
123
124 serverMsgs, clientMsgs := s.getServerAndClientMessages(2*messagesExpected, 2*messagesExpected)
125 for _, m := range append(serverMsgs, clientMsgs...) {
126 assert.Equal(s.T(), m["grpc.service"], "mwitkow.testproto.TestService", "all lines must contain service name")
127 assert.Equal(s.T(), m["grpc.method"], "PingStream", "all lines must contain method name")
128 assert.Equal(s.T(), m["level"], "info", "all lines must logged at info level")
129
130 content := m["grpc.request.content"] != nil || m["grpc.response.content"] != nil
131 assert.True(s.T(), content, "all messages must contain payloads")
132 }
133 }
134
View as plain text