grpc-xds/greeter-go/pkg/interceptors/logging.go (90 lines of code) (raw):
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package interceptors
import (
"context"
"fmt"
"github.com/go-logr/logr"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector"
"google.golang.org/grpc"
"google.golang.org/grpc/channelz/grpc_channelz_v1"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection/grpc_reflection_v1"
"google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// verbosity https://github.com/kubernetes/community/blob/1a09f121536ddb84c1429c88fbb3978d6c5e2dd0/contributors/devel/sig-instrumentation/logging.md#what-method-to-use
const (
debugVerbosity = 4
infoVerbosity = 2
warnVerbosity = 1
errorVerbosity = 0
interceptorLoggerCallDepth = 3
)
var (
// gRPC services to exclude from logging.
excludedServices = map[string]bool{
grpc_channelz_v1.Channelz_ServiceDesc.ServiceName: true,
grpc_health_v1.Health_ServiceDesc.ServiceName: true,
grpc_reflection_v1.ServerReflection_ServiceDesc.ServiceName: true,
grpc_reflection_v1alpha.ServerReflection_ServiceDesc.ServiceName: true,
"envoy.service.status.v2.ClientStatusDiscoveryService": true, // not exported
"envoy.service.status.v3.ClientStatusDiscoveryService": true, // not exported
}
loggingOpts = []logging.Option{
logging.WithLogOnEvents(
logging.PayloadReceived,
logging.PayloadSent),
}
)
func StreamClientLogging(logger logr.Logger) grpc.StreamClientInterceptor {
loggingInterceptor := logging.StreamClientInterceptor(interceptorLogger(logger), loggingOpts...)
return selector.StreamClientInterceptor(loggingInterceptor, selector.MatchFunc(selectorFunc))
}
func StreamServerLogging(logger logr.Logger) grpc.StreamServerInterceptor {
loggingInterceptor := logging.StreamServerInterceptor(interceptorLogger(logger), loggingOpts...)
return selector.StreamServerInterceptor(loggingInterceptor, selector.MatchFunc(selectorFunc))
}
func UnaryClientLogging(logger logr.Logger) grpc.UnaryClientInterceptor {
loggingInterceptor := logging.UnaryClientInterceptor(interceptorLogger(logger), loggingOpts...)
return selector.UnaryClientInterceptor(loggingInterceptor, selector.MatchFunc(selectorFunc))
}
func UnaryServerLogging(logger logr.Logger) grpc.UnaryServerInterceptor {
loggingInterceptor := logging.UnaryServerInterceptor(interceptorLogger(logger), loggingOpts...)
return selector.UnaryServerInterceptor(loggingInterceptor, selector.MatchFunc(selectorFunc))
}
func selectorFunc(_ context.Context, callMeta interceptors.CallMeta) bool {
return !excludedServices[callMeta.Service]
}
// interceptorLogger adapts logr logger to interceptor logger.
//
// This function also marshals any `fields` of type `proto.Message` into
// pretty-printed multi-line JSON strings, to make log tailing easier during
// development. This approach is not recommended for production environments.
func interceptorLogger(l logr.Logger) logging.Logger {
return logging.LoggerFunc(func(_ context.Context, lvl logging.Level, msg string, fields ...any) {
if fields == nil {
fields = []any{}
}
protoMarshalOptions := protojson.MarshalOptions{
Multiline: true,
Indent: " ",
AllowPartial: true,
}
for i, field := range fields {
if message, ok := field.(proto.Message); ok {
messageJSONBytes, err := protoMarshalOptions.Marshal(message)
if err == nil {
fields[i] = string(messageJSONBytes)
}
}
}
l := l.WithCallDepth(interceptorLoggerCallDepth).WithValues(fields...)
switch lvl {
case logging.LevelDebug:
l.V(debugVerbosity).Info(msg)
case logging.LevelInfo:
l.V(infoVerbosity).Info(msg)
case logging.LevelWarn:
l.V(warnVerbosity).Info(msg)
case logging.LevelError:
l.V(errorVerbosity).Error(nil, msg)
default:
panic(fmt.Sprintf("unknown level %v", lvl))
}
})
}