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