package logging import ( "context" "fmt" "time" "github.com/google/uuid" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) func InterceptorLogger() logging.Logger { return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) { l := zerolog.Ctx(ctx).With().Fields(fields).Logger() switch lvl { case logging.LevelDebug: l.Debug().Msg(msg) case logging.LevelInfo: l.Info().Msg(msg) case logging.LevelWarn: l.Warn().Msg(msg) case logging.LevelError: l.Error().Msg(msg) default: l.Info().Msg(msg) } }) } func LoggingOpts() []logging.Option { return []logging.Option{ logging.WithLogOnEvents(logging.StartCall, logging.FinishCall), logging.WithLevels(levelFunc), logging.WithDurationField(func(d time.Duration) logging.Fields { return logging.Fields{"grpc.duration_ms", d.Milliseconds()} }), } } func levelFunc(code codes.Code) logging.Level { switch code { case codes.OK, codes.NotFound, codes.Canceled: return logging.LevelInfo case codes.InvalidArgument, codes.AlreadyExists, codes.Unauthenticated: return logging.LevelWarn default: return logging.LevelError } } func RequestIDInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { requestID := uuid.New().String() logger := log.Logger.With(). Str("request_id", requestID). Str("grpc.method", info.FullMethod). Logger() ctx = logger.WithContext(ctx) resp, err := handler(ctx, req) if err != nil { st, _ := status.FromError(err) zerolog.Ctx(ctx).Debug(). Str("grpc.code", st.Code().String()). Str("grpc.error", st.Message()). Msg("request failed") } return resp, err } } func PeerInfoInterceptor() grpc.UnaryServerInterceptor { return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { md, ok := metadata.FromIncomingContext(ctx) if ok { evt := zerolog.Ctx(ctx).With() if ua := md.Get("user-agent"); len(ua) > 0 { evt = evt.Str("peer.user_agent", ua[0]) } if auth := md.Get("authorization"); len(auth) > 0 { evt = evt.Bool("peer.authenticated", true) } logger := evt.Logger() ctx = logger.WithContext(ctx) } return handler(ctx, req) } } func StreamRequestIDInterceptor() grpc.StreamServerInterceptor { return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { requestID := uuid.New().String() logger := log.Logger.With(). Str("request_id", requestID). Str("grpc.method", info.FullMethod). Logger() ctx := logger.WithContext(ss.Context()) wrapped := &wrappedStream{ServerStream: ss, ctx: ctx} return handler(srv, wrapped) } } type wrappedStream struct { grpc.ServerStream ctx context.Context } func (w *wrappedStream) Context() context.Context { return w.ctx } func (w *wrappedStream) SendMsg(m any) error { zerolog.Ctx(w.ctx).Trace().Str("direction", "send").Msg(fmt.Sprintf("stream message: %T", m)) return w.ServerStream.SendMsg(m) } func (w *wrappedStream) RecvMsg(m any) error { err := w.ServerStream.RecvMsg(m) if err == nil { zerolog.Ctx(w.ctx).Trace().Str("direction", "recv").Msg(fmt.Sprintf("stream message: %T", m)) } return err }