zerolog
zerolog copied to clipboard
gRPC interceptor?
I have swapped out logrus in my application for zerolog, for performance gain, but I am using gRPC and I am unable to find zerolog for gRPC. I am not talking about the grpclog but gRPC interceptor logger: https://github.com/grpc-ecosystem/go-grpc-middleware/tree/master/logging
Is there some project that provides this implementation for zerolog?
I've built a very simple implementation inspired by the logrus from grpc middleware.
package ctx_zerolog
import (
"context"
"github.com/rs/zerolog"
)
type ctxKey struct{}
var key ctxKey
type wrapper struct {
logger zerolog.Logger
}
func New(ctx context.Context, log zerolog.Logger) context.Context {
return context.WithValue(ctx, key, &wrapper{log})
}
func Set(ctx context.Context, changes zerolog.Context) {
l, ok := ctx.Value(key).(*wrapper)
if !ok || l == nil {
return
}
l.logger = changes.Logger()
}
func Get(ctx context.Context) zerolog.Context {
l, ok := ctx.Value(key).(*wrapper)
if !ok || l == nil {
return zerolog.Nop().With()
}
return l.logger.With()
}
package grpc_zerolog
import (
"fmt"
"github.com/rs/zerolog"
"google.golang.org/grpc/grpclog"
)
func ReplaceGrpcLogger(logger zerolog.Logger) {
grpclog.SetLoggerV2(wrap(logger))
}
func wrap(l zerolog.Logger) *bridge {
return &bridge{l}
}
type bridge struct {
zerolog.Logger
}
func (b *bridge) Info(args ...interface{}) {
b.Logger.Info().Msg(fmt.Sprint(args...))
}
func (b *bridge) Infoln(args ...interface{}) {
b.Logger.Info().Msg(fmt.Sprint(args...) + "\n")
}
func (b *bridge) Infof(format string, args ...interface{}) {
b.Logger.Info().Msgf(format, args...)
}
func (b *bridge) Warning(args ...interface{}) {
b.Logger.Warn().Msg(fmt.Sprint(args...))
}
func (b *bridge) Warningln(args ...interface{}) {
b.Logger.Warn().Msg(fmt.Sprint(args...) + "\n")
}
func (b *bridge) Warningf(format string, args ...interface{}) {
b.Logger.Warn().Msgf(format, args...)
}
func (b *bridge) Error(args ...interface{}) {
b.Logger.Error().Msg(fmt.Sprint(args...))
}
func (b *bridge) Errorln(args ...interface{}) {
b.Logger.Error().Msg(fmt.Sprint(args...) + "\n")
}
func (b *bridge) Errorf(format string, args ...interface{}) {
b.Logger.Error().Msgf(format, args...)
}
func (b *bridge) Fatal(args ...interface{}) {
b.Logger.Fatal().Msg(fmt.Sprint(args...))
}
func (b *bridge) Fatalln(args ...interface{}) {
b.Logger.Fatal().Msg(fmt.Sprint(args...) + "\n")
}
func (b *bridge) Fatalf(format string, args ...interface{}) {
b.Logger.Fatal().Msgf(format, args...)
}
func (b *bridge) V(verbosity int) bool {
// verbosity values:
// 0 = info
// 1 = warning
// 2 = error
// 3 = fatal
switch b.GetLevel() {
case zerolog.PanicLevel:
return verbosity > 3
case zerolog.FatalLevel:
return verbosity == 3
case zerolog.ErrorLevel:
return verbosity == 2
case zerolog.WarnLevel:
return verbosity == 1
case zerolog.InfoLevel:
return verbosity == 0
case zerolog.DebugLevel:
return true
case zerolog.TraceLevel:
return true
default:
return false
}
}
package grpc_zerolog
import (
"context"
"github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/rs/zerolog"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"ctx_zerolog"
"path"
"time"
)
func UnaryServerInterceptor(logger zerolog.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
newCtx := initCtx(ctx, logger, info.FullMethod, start)
resp, err := handler(newCtx, req)
code := status.Code(err)
level := ctl(code)
with := ctx_zerolog.Get(newCtx).Str("grpc.code", code.String()).Dur("grpc.time_ms", time.Since(start))
if err != nil {
with = with.Err(err)
}
doLog(with.Logger(), level, "finished unary call")
return resp, err
}
}
func StreamServerInterceptor(logger zerolog.Logger) grpc.StreamServerInterceptor {
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
start := time.Now()
newCtx := initCtx(stream.Context(), logger, info.FullMethod, start)
wrapped := grpc_middleware.WrapServerStream(stream)
wrapped.WrappedContext = newCtx
err := handler(srv, wrapped)
code := status.Code(err)
level := ctl(code)
with := ctx_zerolog.Get(newCtx).Str("grpc.code", code.String()).Dur("grpc.time_ms", time.Since(start))
if err != nil {
with = with.Err(err)
}
doLog(with.Logger(), level, "finished streaming call")
return err
}
}
func doLog(logger zerolog.Logger, level zerolog.Level, msg string) {
switch level {
case zerolog.DebugLevel:
logger.Debug().Msg(msg)
case zerolog.InfoLevel:
logger.Info().Msg(msg)
case zerolog.WarnLevel:
logger.Warn().Msg(msg)
case zerolog.ErrorLevel:
logger.Error().Msg(msg)
case zerolog.FatalLevel:
logger.Fatal().Msg(msg)
case zerolog.PanicLevel:
logger.Panic().Msg(msg)
}
}
func initCtx(ctx context.Context, logger zerolog.Logger, fullMethodString string, start time.Time) context.Context {
service := path.Dir(fullMethodString)[1:]
method := path.Base(fullMethodString)
with := logger.With().
Str("grpc.service", service).
Str("grpc.method", method).
Str("grpc.start_time", start.Format(time.RFC3339))
if d, ok := ctx.Deadline(); ok {
with = with.Str("grpc.request.deadline", d.Format(time.RFC3339))
}
return ctx_zerolog.New(ctx, with.Logger())
}
func ctl(code codes.Code) zerolog.Level {
switch code {
case codes.OK:
return zerolog.DebugLevel
case codes.Canceled:
return zerolog.DebugLevel
case codes.Unknown:
return zerolog.InfoLevel
case codes.InvalidArgument:
return zerolog.DebugLevel
case codes.DeadlineExceeded:
return zerolog.InfoLevel
case codes.NotFound:
return zerolog.DebugLevel
case codes.AlreadyExists:
return zerolog.DebugLevel
case codes.PermissionDenied:
return zerolog.InfoLevel
case codes.Unauthenticated:
return zerolog.InfoLevel
case codes.ResourceExhausted:
return zerolog.DebugLevel
case codes.FailedPrecondition:
return zerolog.DebugLevel
case codes.Aborted:
return zerolog.DebugLevel
case codes.OutOfRange:
return zerolog.DebugLevel
case codes.Unimplemented:
return zerolog.WarnLevel
case codes.Internal:
return zerolog.WarnLevel
case codes.Unavailable:
return zerolog.WarnLevel
case codes.DataLoss:
return zerolog.WarnLevel
default:
return zerolog.InfoLevel
}
}
Hello! I've written some interceptors for my own use. It independent from go-grpc-miggleware, and it gives possibility to advance it without PRs to go-grpc-middleware, but together with it cannot use the testing framework, I'll will glade to have some ideas how to write the tests for gRPC interceptors. Welcome to use and contribute: https://github.com/pereslava/grpc_zerolog, Documentation at pkg.go.dev
Take a look https://github.com/grpc-ecosystem/go-grpc-middleware/blob/main/interceptors/logging/examples/zerolog/example_test.go