Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logging option to disable fields in log entry #631

Merged
merged 12 commits into from
Sep 19, 2023
11 changes: 9 additions & 2 deletions interceptors/logging/interceptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,15 @@ func reportable(logger Logger, opts *options) interceptors.CommonReportableFunc
kind = KindClientFieldValue
}

// Field dups from context override the common fields.
fields := newCommonFields(kind, c).WithUnique(ExtractFields(ctx))
fields := Fields{}
if opts.grpcLogFields != nil {
fields = fields.WithUnique(customCommonFields(kind, c, opts.grpcLogFields()))
} else {
// Field dups from context override the common fields.
fields = newCommonFields(kind, c)
}
fields = fields.WithUnique(ExtractFields(ctx))

if !c.IsClient {
if peer, ok := peer.FromContext(ctx); ok {
fields = append(fields, "peer.address", peer.Addr.String())
Expand Down
149 changes: 149 additions & 0 deletions interceptors/logging/interceptors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,155 @@ func (s *loggingPayloadSuite) assertPayloadLogLinesForMessage(lines LogLines, me
}
}

func addGrpcLogFields() []string {
return []string{
logging.MethodFieldKey, logging.ServiceFieldKey, "should-not-be-added",
}
}

type loggingCustomGrpcLogFieldsSuite struct {
*baseLoggingSuite
}

func TestCustomGrpcLogFieldsSuite(t *testing.T) {
if strings.HasPrefix(runtime.Version(), "go1.7") {
t.Skipf("Skipping due to json.RawMessage incompatibility with go1.7")
return
}
s := &loggingCustomGrpcLogFieldsSuite{
baseLoggingSuite: &baseLoggingSuite{
logger: newMockLogger(),
InterceptorTestSuite: &testpb.InterceptorTestSuite{
TestService: &testpb.TestPingService{},
},
},
}
s.InterceptorTestSuite.ClientOpts = []grpc.DialOption{
grpc.WithUnaryInterceptor(logging.UnaryClientInterceptor(s.logger, logging.WithGrpcLogFields(addGrpcLogFields))),
grpc.WithStreamInterceptor(logging.StreamClientInterceptor(s.logger, logging.WithGrpcLogFields(addGrpcLogFields))),
}
s.InterceptorTestSuite.ServerOpts = []grpc.ServerOption{
grpc.StreamInterceptor(logging.StreamServerInterceptor(s.logger, logging.WithGrpcLogFields(addGrpcLogFields))),
grpc.UnaryInterceptor(logging.UnaryServerInterceptor(s.logger, logging.WithGrpcLogFields(addGrpcLogFields))),
}
suite.Run(t, s)
}

// Test that fields are added to logs using withGrpcLogFields.
func (s *loggingCustomGrpcLogFieldsSuite) TestCustomGrpcLogFieldsWithPing() {
_, err := s.Client.Ping(s.SimpleCtx(), testpb.GoodPing)
assert.NoError(s.T(), err, "there must be not be an on a successful call")

lines := s.logger.o.Lines()
sort.Sort(lines)
require.Len(s.T(), lines, 4)

clientStartCallLogLine := lines[2]
assert.Equal(s.T(), logging.LevelDebug, clientStartCallLogLine.lvl)
assert.Equal(s.T(), "started call", clientStartCallLogLine.msg)
clientStartCallFields := clientStartCallLogLine.fields
clientStartCallFields.AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "Ping").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())

serverStartCallLogLine := lines[3]
assert.Equal(s.T(), logging.LevelInfo, serverStartCallLogLine.lvl)
assert.Equal(s.T(), "started call", serverStartCallLogLine.msg)
serverStartCallFields := serverStartCallLogLine.fields
serverStartCallFields.AssertFieldNotEmpty(s.T(), "peer.address").
AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "Ping").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())

serverFinishCallLogLine := lines[0]
assert.Equal(s.T(), logging.LevelInfo, serverFinishCallLogLine.lvl)
assert.Equal(s.T(), "finished call", serverFinishCallLogLine.msg)
serverFinishCallFields := serverFinishCallLogLine.fields
serverFinishCallFields.AssertFieldNotEmpty(s.T(), "peer.address").
AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertField(s.T(), "grpc.code", "OK").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "Ping").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())

clientFinishCallLogLine := lines[1]
assert.Equal(s.T(), logging.LevelDebug, clientFinishCallLogLine.lvl)
assert.Equal(s.T(), "finished call", clientFinishCallLogLine.msg)

clientFinishCallFields := clientFinishCallLogLine.fields
clientFinishCallFields.AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertField(s.T(), "grpc.code", "OK").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "Ping").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())
}

func (s *loggingCustomGrpcLogFieldsSuite) TestCustomGrpcLogFieldsWithPingList() {
stream, err := s.Client.PingList(s.SimpleCtx(), testpb.GoodPingList)
require.NoError(s.T(), err, "should not fail on establishing the stream")
for {
_, err := stream.Recv()
if err == io.EOF {
break
}
require.NoError(s.T(), err, "reading stream should not fail")
}
lines := s.logger.o.Lines()
sort.Sort(lines)
require.Len(s.T(), lines, 4)

clientStartCallLogLine := lines[2]
assert.Equal(s.T(), logging.LevelDebug, clientStartCallLogLine.lvl)
assert.Equal(s.T(), "started call", clientStartCallLogLine.msg)
clientStartCallFields := clientStartCallLogLine.fields
clientStartCallFields.AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "PingList").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())

serverStartCallLogLine := lines[3]
assert.Equal(s.T(), logging.LevelInfo, serverStartCallLogLine.lvl)
assert.Equal(s.T(), "started call", serverStartCallLogLine.msg)
serverStartCallFields := serverStartCallLogLine.fields
serverStartCallFields.AssertFieldNotEmpty(s.T(), "peer.address").
AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "PingList").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())

serverFinishCallLogLine := lines[0]
assert.Equal(s.T(), logging.LevelInfo, serverFinishCallLogLine.lvl)
assert.Equal(s.T(), "finished call", serverFinishCallLogLine.msg)
serverFinishCallFields := serverFinishCallLogLine.fields
serverFinishCallFields.AssertFieldNotEmpty(s.T(), "peer.address").
AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertField(s.T(), "grpc.code", "OK").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "PingList").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())

clientFinishCallLogLine := lines[1]
assert.Equal(s.T(), logging.LevelDebug, clientFinishCallLogLine.lvl)
assert.Equal(s.T(), "finished call", clientFinishCallLogLine.msg)

clientFinishCallFields := clientFinishCallLogLine.fields
clientFinishCallFields.AssertFieldNotEmpty(s.T(), "grpc.start_time").
AssertFieldNotEmpty(s.T(), "grpc.request.deadline").
AssertField(s.T(), "grpc.code", "OK").
AssertFieldNotEmpty(s.T(), "grpc.time_ms").
AssertField(s.T(), logging.MethodFieldKey, "PingList").
AssertField(s.T(), logging.ServiceFieldKey, testpb.TestServiceFullName).AssertNoMoreTags(s.T())
}

// waitUntil executes f every interval seconds until timeout or no error is returned from f.
func waitUntil(interval time.Duration, stopc <-chan struct{}, f func() error) error {
tick := time.NewTicker(interval)
Expand Down
19 changes: 19 additions & 0 deletions interceptors/logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ func newCommonFields(kind string, c interceptors.CallMeta) Fields {
}
}

func customCommonFields(kind string, c interceptors.CallMeta, customFields []string) Fields {
commonFields := newCommonFields(kind, c)

existing := map[any]any{}
coleenquadros marked this conversation as resolved.
Show resolved Hide resolved
i := commonFields.Iterator()
for i.Next() {
k, v := i.At()
existing[k] = v
}

newFields := Fields{}
for _, key := range customFields {
if _, ok := existing[key]; ok {
newFields = append(newFields, key, existing[key])
}
coleenquadros marked this conversation as resolved.
Show resolved Hide resolved
}
return newFields
}

// Fields loosely represents key value pairs that adds context to log lines. The key has to be type of string, whereas
// value can be an arbitrary object.
type Fields []any
Expand Down
20 changes: 20 additions & 0 deletions interceptors/logging/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var (
// levelFunc depends if it's client or server.
levelFunc: nil,
timestampFormat: time.RFC3339,
grpcLogFields: nil,
}
)

Expand All @@ -60,6 +61,7 @@ type options struct {
durationFieldFunc DurationToFields
timestampFormat string
fieldsFromCtxCallMetaFn fieldsFromCtxCallMetaFn
grpcLogFields AddGrpcLogFields
}

type Option func(*options)
Expand Down Expand Up @@ -204,3 +206,21 @@ func WithTimestampFormat(format string) Option {
o.timestampFormat = format
}
}

// AddGrpcLogFields customizes the function for adding gRPC fields to the log entry from the below list.
//
// -SystemTag[0],
// -ComponentFieldKey,
// -ServiceFieldKey,
// -MethodFieldKey,
// -MethodTypeFieldKey,
//
// If not provided default fields will be used.
type AddGrpcLogFields func() []string

// WithAddGrpcLogFields customizes the function for adding gRPC fields to the log entry.
func WithGrpcLogFields(f AddGrpcLogFields) Option {
return func(o *options) {
o.grpcLogFields = f
}
}
coleenquadros marked this conversation as resolved.
Show resolved Hide resolved