Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.

Commit

Permalink
feat: added error unwrapping
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Chodur <m.chodur@seznam.cz>
  • Loading branch information
FUSAKLA committed Sep 27, 2019
1 parent ae0d866 commit 9c700e5
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 6 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module github.com/grpc-ecosystem/go-grpc-prometheus

require (
github.com/golang/protobuf v1.2.0
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v0.9.2
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
github.com/stretchr/testify v1.3.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
Expand Down
2 changes: 0 additions & 2 deletions makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
SHELL="/bin/bash"

GOFILES_NOVENDOR = $(shell go list ./... | grep -v /vendor/)

all: vet fmt test
Expand Down
50 changes: 47 additions & 3 deletions server_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package grpc_prometheus

import (
"context"

//"errors"
prom "github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

Expand Down Expand Up @@ -100,13 +101,56 @@ func (m *ServerMetrics) Collect(ch chan<- prom.Metric) {
}
}

// Since error can be wrapped and the `FromError` function only checks for `GRPCStatus` function
// and as a fallback uses the `Unknown` gRPC status we need to unwrap the error if possible to get the original status.
// pkg/errors and Go native errors packages have two different approaches so we try to unwrap both types.
// Eventually should be implemented in the go-grpc status function `FromError`. See https://github.com/grpc/grpc-go/issues/2934
func grpcStatusFromError(err error) (s *status.Status, ok bool) {
s, ok = status.FromError(err)
if ok {
return s, true
}

type gRPCStatus interface {
GRPCStatus() *status.Status
}
type causer interface {
Cause() error
}

// Unwrapping the github.com/pkg/errors causer interface, using `Cause` directly could miss some error implementing
// the `GRPCStatus` function so we have to check it on our selves.
unwrappedCauser := err
for unwrappedCauser != nil {
if s, ok := unwrappedCauser.(gRPCStatus); ok {
return s.GRPCStatus(), true
}
cause, ok := unwrappedCauser.(causer)
if !ok {
break
}
unwrappedCauser = cause.Cause()
}

// The un-wrapping is available only since Go 1.13.0

//// Unwrapping the native Go unwrap interface
//var unwrappedStatus gRPCStatus
//if ok := errors.As(err, &unwrappedStatus); ok {
// return unwrappedStatus.GRPCStatus(), true
//}

// We failed to unwrap any GRPSStatus so return default `Unknown`
return status.New(codes.Unknown, err.Error()), false
}

// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs.
func (m *ServerMetrics) UnaryServerInterceptor() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
monitor := newServerReporter(m, Unary, info.FullMethod)
monitor.ReceivedMessage()
resp, err := handler(ctx, req)
st, _ := status.FromError(err)
st, _ := grpcStatusFromError(err)
monitor.Handled(st.Code())
if err == nil {
monitor.SentMessage()
Expand All @@ -120,7 +164,7 @@ func (m *ServerMetrics) StreamServerInterceptor() func(srv interface{}, ss grpc.
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
monitor := newServerReporter(m, streamRPCType(info), info.FullMethod)
err := handler(srv, &monitoredServerStream{ss, monitor})
st, _ := status.FromError(err)
st, _ := grpcStatusFromError(err)
monitor.Handled(st.Code())
return err
}
Expand Down
20 changes: 19 additions & 1 deletion server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"bufio"
"context"
"fmt"
"github.com/pkg/errors"
"io"
"net"
"net/http"
Expand Down Expand Up @@ -144,6 +145,16 @@ func (s *ServerInterceptorTestSuite) TestUnaryIncrementsMetrics() {
requireValueHistCount(s.T(), 1, DefaultServerMetrics.serverHandledHistogram.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError"))
}

func (s *ServerInterceptorTestSuite) TestWrappedErrorsMetrics() {
_, err := s.testClient.PingError(s.ctx, &pb_testproto.PingRequest{Value: "wrapf", ErrorCodeReturned: uint32(codes.FailedPrecondition)}) // should return with code=FailedPrecondition
require.Error(s.T(), err)
requireValue(s.T(), 1, DefaultServerMetrics.serverHandledCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError", "FailedPrecondition"))

//_, err = s.testClient.PingError(s.ctx, &pb_testproto.PingRequest{Value: "errorf", ErrorCodeReturned: uint32(codes.Unavailable)}) // should return with code=Unavailable
//require.Error(s.T(), err)
//requireValue(s.T(), 1, DefaultServerMetrics.serverHandledCounter.WithLabelValues("unary", "mwitkow.testproto.TestService", "PingError", "Unavailable"))
}

func (s *ServerInterceptorTestSuite) TestStartedStreamingIncrementsStarted() {
_, err := s.testClient.PingList(s.ctx, &pb_testproto.PingRequest{})
require.NoError(s.T(), err)
Expand Down Expand Up @@ -243,7 +254,14 @@ func (s *testService) Ping(ctx context.Context, ping *pb_testproto.PingRequest)

func (s *testService) PingError(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.Empty, error) {
code := codes.Code(ping.ErrorCodeReturned)
return nil, status.Errorf(code, "Userspace error.")
err := status.Errorf(code, "Userspace error.")
if ping.Value == "wrapf" {
err = errors.Wrapf(err, "wrapped error")
}
//if ping.Value == "errorf" {
// err = fmt.Errorf("wrapped error: %w", err)
//}
return nil, err
}

func (s *testService) PingList(ping *pb_testproto.PingRequest, stream pb_testproto.TestService_PingListServer) error {
Expand Down

0 comments on commit 9c700e5

Please sign in to comment.