Tips for gRPC error handling in Ruby

Summarize error handling tips when implementing a gRPC client in Ruby

Premise

tl;dr --The error response to a gRPC call is an exception of type GRPC :: BadStatus, so be sure to resuce when making a gRPC call. --If you want to get more information about the captured Exception, cast it to the Google :: Rpc :: Status type with the to_rpc_status method. --If error details are returned from the gRPC server, they will be returned as Any type, so you need to unpack them to the appropriate type. --gem error class https://github.com/grpc/grpc/blob/master/src/ruby/lib/grpc/errors.rb

First catch the error

The gRPC call response is basically one of the following Codes:

enum Code {
  OK = 0;
  CANCELLED = 1;
  UNKNOWN = 2;
  INVALID_ARGUMENT = 3;
  DEADLINE_EXCEEDED = 4;
  NOT_FOUND = 5;
  ALREADY_EXISTS = 6;
  PERMISSION_DENIED = 7;
  UNAUTHENTICATED = 16;
  RESOURCE_EXHAUSTED = 8;
  FAILED_PRECONDITION = 9;
  ABORTED = 10;
  OUT_OF_RANGE = 11;
  UNIMPLEMENTED = 12;
  INTERNAL = 13;
  UNAVAILABLE = 14;
  DATA_LOSS = 15;
}

https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto As an implementation of gRPC server, I think that the normal system returns Code OK (0), and if there is an error, it returns Code (1 ~ 15) other than OK.

If an error is returned, it seems that the grpc gem side will generate an exception without permission, and if you do not rescue, the program will stop there. Therefore, it is necessary to pick up the exception with rescue as follows.

    begin
      stub = HogeProto::HogeService::Stub.new("localhost:50051", :this_channel_is_insecure)
      res = stub.get_hoge
    rescue GRPC::BadStatus => ex
      res = "error"
    rescue ex
      res = "unexpected error"
    end

Looking at the error class defined in gem, all classes are BadStatus You can see that it inherits class.

In fact, if you look at the inheritance tree of the Exception that occurred

p ex.class.ancestors
# =>[GRPC::InvalidArgument, GRPC::BadStatus, GRPC::Core::StatusCodes, StandardError, Exception...

Basically, you can catch all Exceptions with GRPC :: BadStatus.

If you want to process in more detail, you can pick it up in units like GRPC :: InvalidArgument.

Extract information from the captured Exception

Let's extract the information from the captured Exception. The BadStatus instance has four fields. https://github.com/grpc/grpc/blob/d48d39c4324f06a6da24bb4f67e8ef21166ba65b/src/ruby/lib/grpc/errors.rb#L49-L52

For example, create an error from the gRPC server implemented in Go as follows and return it to ruby.

server.go


  func (s *Server) set_name(name string) {
    st := status.New(codes.InvalidArgument, "invalid username")
    return nil, st.Err()
  }

client.rb


    begin
      stub = HogeProto::HogeService::Stub.new("localhost:50051", :this_channel_is_insecure)
      res = stub.set_name("hoge1")
    rescue GRPC::BadStatus => ex
      p ex.code # => 3
      p ex.message # => "3:invalid username"
      p ex.details # => "invalid username"
      res = "error"
    rescue ex
      res = "unexpected error"
    end

I got the messages Status Code 3 and"invalid username"

If you want to get error details, use the to_rpc_status method

Up to the above, the Status Code and error message have been obtained. However, when you actually build it, you often use error details to pass in the error details. https://christina04.hatenablog.com/entry/grpc-error-details https://grpc.io/docs/guides/error/#richer-error-model

If you want more detailed information, including error details, you can get more detailed information by using the to_rpc_status method. The implementation of to_rpc_status is as follows, which allows you to cast to the Google :: Rpc :: Status type and retrieve detailed information including trailer metadata. https://github.com/grpc/grpc/blob/d48d39c4324f06a6da24bb4f67e8ef21166ba65b/src/ruby/lib/grpc/errors.rb#L63-L75

I added error details to the previous implementation example.

server.go


  func (s *Server) set_name(name string) {
    st := status.New(codes.InvalidArgument, "invalid username")

    //Create and set error details information
    desc := "The username must only contain alphanumeric characters"
    v := &errdetails.BadRequest_FieldViolation{
		Field:       "username",
		Description: desc,
    }
    br := &errdetails.BadRequest{}
    br.FieldViolations = append(br.FieldViolations, v)
    st, _ = st.WithDetails(br)
    
    return nil, st.Err()
  }

client.rb


require 'google/rpc/error_details_pb'

    begin
      stub = HogeProto::HogeService::Stub.new("localhost:50051", :this_channel_is_insecure)
      res = stub.set_name("hoge1")
    rescue GRPC::BadStatus => ex
      p ex.class # => GRPC::InvalidArgument
      p ex.to_rpc_status.class # => Google::Rpc::Status
      p ex.to_rpc_status # => <Google::Rpc::Status: code: 3, message: "invalid username", details: [<Google::Protobuf::Any: type_url: "type.googleapis.com/google.rpc.BadRequest", value: "\nC\n\tusername\x126The username must only contain alphanumeric characters">]>

      ex.to_rpc_status.details.each do |detail|
        p detail.type_url # => "type.googleapis.com/google.rpc.BadRequest"
        p detail.unpack(Google::Rpc::BadRequest) # => <Google::Rpc::BadRequest: field_violations: [<Google::Rpc::BadRequest::FieldViolation: field: "username", description: "The username must only contain alphanumeric characters">]>
      end
      res = "error"
    rescue ex
      res = "unexpected error"
    end

Notable details details: [<Google :: Protobuf :: Any: type_url:" type.googleapis.com/google.rpc.BadRequest ", value:" \ nC \ n \ tusername2 \ x126The username must only contain alphanumeric The characters ">] part. This is the error details data sent as a trailer, and there is some data of type Google :: Protobuf :: Any and data of value. You can set multiple error details and they will be returned as an array, so you need to turn each to retrieve them. type_url is where the type definition location for error details is stored, and if you are using the type provided by Google by default, it will be "type.googleapis.com/google.rpc.BadRequest" .. Of course, you can also set your own defined type as error details, in which case the location you defined in the proto file will be stored.

Also, the returned instance is of type Google :: Protobuf :: Any, and if it is left as it is, the data cannot be retrieved properly in the serialized state. Therefore, we use the ʻunpackmethod. By determining the type withtype_url and casting it to the type you want to return with ʻunpack, I was finally able to retrieve error details. By the way, note that you need to import google / rpc / error_details_pb to see the type definition of Google :: Rpc :: BadRequest.

Impressions

It took a lot of time to retrieve the error details. The sample code at https://grpc.io/docs/languages/ruby/quickstart/ doesn't cover this in detail, so it's a bit painful to end up having to go through the contents of the gem one by one.

Recommended Posts

Tips for gRPC error handling in Ruby
Tips for handling enums in thymeleaf
Tips for handling pseudo-elements in Selenium
Tips for gRPC error handling in Ruby
Implement a gRPC client in Ruby
How to implement Pagination in GraphQL (for ruby)
Tips for generating files for eclipse projects in Gradle
[For beginners] ○○. △△ in Ruby (ActiveRecord method, instance method, data acquisition)
How to resolve SSL_connect error in PayPal Ruby SDK
Handling of date and time in Ruby. Use Date and Time properly.
Variable type in ruby
Fast popcount in Ruby
Tips for using Salesforce SOAP and Bulk API in Java
Microbenchmark for integer power of floating point numbers in Ruby