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 factory based zap payload logging interceptor #469

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions logging/zap/payload_interceptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ var (
JsonPbMarshaller grpc_logging.JsonPbMarshaler = &jsonpb.Marshaler{}
)

// LoggerFactory Helps instantiate a logger
type LoggerFactory interface {

// GetLogger fetches a context based logger
GetLogger(ctx context.Context) *zap.Logger
}

// PayloadUnaryServerInterceptor returns a new unary server interceptors that logs the payloads of requests.
//
// This *only* works when placed *after* the `grpc_zap.UnaryServerInterceptor`. However, the logging can be done to a
Expand All @@ -29,14 +36,7 @@ func PayloadUnaryServerInterceptor(logger *zap.Logger, decider grpc_logging.Serv
if !decider(ctx, info.FullMethod, info.Server) {
return handler(ctx, req)
}
// Use the provided zap.Logger for logging but use the fields from context.
logEntry := logger.With(append(serverCallFields(info.FullMethod), ctxzap.TagsToFields(ctx)...)...)
logProtoMessageAsJson(logEntry, req, "grpc.request.content", "server request payload logged as grpc.request.content field")
resp, err := handler(ctx, req)
if err == nil {
logProtoMessageAsJson(logEntry, resp, "grpc.response.content", "server response payload logged as grpc.response.content field")
}
return resp, err
return logAndHandleUnaryCall(ctx, logger, req, info, handler)
}
}

Expand Down Expand Up @@ -84,6 +84,46 @@ func PayloadStreamClientInterceptor(logger *zap.Logger, decider grpc_logging.Cli
}
}

// FactoryBasedPayloadUnaryServerInterceptor returns a new unary server interceptors that logs the payloads of requests.
//
// This *only* works when placed *after* the `grpc_zap.UnaryServerInterceptor`. However, the logging can be done to a
// separate instance of the logger.
func FactoryBasedPayloadUnaryServerInterceptor(loggerFactory LoggerFactory, decider grpc_logging.ServerPayloadLoggingDecider) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if !decider(ctx, info.FullMethod, info.Server) {
return handler(ctx, req)
}
return logAndHandleUnaryCall(ctx, loggerFactory.GetLogger(ctx), req, info, handler)
}
}

func logAndHandleUnaryCall(ctx context.Context, logger *zap.Logger, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// Use the provided zap.Logger for logging but use the fields from context.
logEntry := logger.With(append(serverCallFields(info.FullMethod), ctxzap.TagsToFields(ctx)...)...)
logProtoMessageAsJson(logEntry, req, "grpc.request.content", "server request payload logged as grpc.request.content field")
resp, err := handler(ctx, req)
if err == nil {
logProtoMessageAsJson(logEntry, resp, "grpc.response.content", "server response payload logged as grpc.response.content field")
}
return resp, err
}

// FactoryBasedPayloadUnaryClientInterceptor returns a new unary client interceptor that logs the payloads of requests and responses.
func FactoryBasedPayloadUnaryClientInterceptor(loggerFactory LoggerFactory, decider grpc_logging.ClientPayloadLoggingDecider) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
if !decider(ctx, method) {
return invoker(ctx, method, req, reply, cc, opts...)
}
logEntry := loggerFactory.GetLogger(ctx).With(newClientLoggerFields(ctx, method)...)
logProtoMessageAsJson(logEntry, req, "grpc.request.content", "client request payload logged as grpc.request.content")
err := invoker(ctx, method, req, reply, cc, opts...)
if err == nil {
logProtoMessageAsJson(logEntry, reply, "grpc.response.content", "client response payload logged as grpc.response.content")
}
return err
}
}

type loggingClientStream struct {
grpc.ClientStream
logger *zap.Logger
Expand Down