Skip to content

Commit dd9e68f

Browse files
authored
Make gRPC logging optional via a custom interface (weaveworks#299)
* middleware: add OptionalLogging interface with method ShouldLog E.g. if the error is caused by overload, then we don't want to log it because that uses more resource. * Add test for gRPC logging, patterned after the one for http logging. Signed-off-by: Bryan Boreham <[email protected]>
1 parent 22cda1c commit dd9e68f

File tree

2 files changed

+61
-0
lines changed

2 files changed

+61
-0
lines changed

middleware/grpc_logging.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package middleware
22

33
import (
4+
"errors"
45
"time"
56

67
"golang.org/x/net/context"
@@ -16,6 +17,11 @@ const (
1617
errorKey = "err"
1718
)
1819

20+
// An error can implement ShouldLog() to control whether GRPCServerLog will log.
21+
type OptionalLogging interface {
22+
ShouldLog(ctx context.Context, duration time.Duration) bool
23+
}
24+
1925
// GRPCServerLog logs grpc requests, errors, and latency.
2026
type GRPCServerLog struct {
2127
Log logging.Interface
@@ -31,6 +37,10 @@ func (s GRPCServerLog) UnaryServerInterceptor(ctx context.Context, req interface
3137
if err == nil && s.DisableRequestSuccessLog {
3238
return resp, nil
3339
}
40+
var optional OptionalLogging
41+
if errors.As(err, &optional) && !optional.ShouldLog(ctx, time.Since(begin)) {
42+
return resp, err
43+
}
3444

3545
entry := user.LogWith(ctx, s.Log).WithFields(logging.Fields{"method": info.FullMethod, "duration": time.Since(begin)})
3646
if err != nil {

middleware/grpc_logging_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package middleware
22

33
import (
4+
"bytes"
45
"context"
6+
"errors"
57
"testing"
8+
"time"
69

710
"github.com/go-kit/log"
811
"github.com/go-kit/log/level"
12+
"github.com/stretchr/testify/require"
913
"google.golang.org/grpc"
1014

1115
"github.com/weaveworks/common/logging"
@@ -28,3 +32,50 @@ func BenchmarkGRPCServerLog_UnaryServerInterceptor_NoError(b *testing.B) {
2832
_, _ = l.UnaryServerInterceptor(ctx, nil, info, handler)
2933
}
3034
}
35+
36+
type doNotLogError struct{ Err error }
37+
38+
func (i doNotLogError) Error() string { return i.Err.Error() }
39+
func (i doNotLogError) Unwrap() error { return i.Err }
40+
func (i doNotLogError) ShouldLog(_ context.Context, _ time.Duration) bool { return false }
41+
42+
func TestGrpcLogging(t *testing.T) {
43+
ctx := context.Background()
44+
info := &grpc.UnaryServerInfo{FullMethod: "Test"}
45+
for _, tc := range []struct {
46+
err error
47+
logContains []string
48+
}{{
49+
err: context.Canceled,
50+
logContains: []string{"level=debug", "context canceled"},
51+
}, {
52+
err: errors.New("yolo"),
53+
logContains: []string{"level=warn", "err=yolo"},
54+
}, {
55+
err: nil,
56+
logContains: []string{"level=debug", "method=Test"},
57+
}, {
58+
err: doNotLogError{Err: errors.New("yolo")},
59+
logContains: nil,
60+
}} {
61+
t.Run("", func(t *testing.T) {
62+
buf := bytes.NewBuffer(nil)
63+
logger := logging.GoKit(log.NewLogfmtLogger(buf))
64+
l := GRPCServerLog{Log: logger, WithRequest: true, DisableRequestSuccessLog: false}
65+
66+
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
67+
return nil, tc.err
68+
}
69+
70+
_, err := l.UnaryServerInterceptor(ctx, nil, info, handler)
71+
require.ErrorIs(t, tc.err, err)
72+
73+
if len(tc.logContains) == 0 {
74+
require.Empty(t, buf)
75+
}
76+
for _, content := range tc.logContains {
77+
require.Contains(t, buf.String(), content)
78+
}
79+
})
80+
}
81+
}

0 commit comments

Comments
 (0)