gruf 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -19
  3. data/README.md +71 -21
  4. data/lib/gruf.rb +3 -0
  5. data/lib/gruf/authentication.rb +9 -1
  6. data/lib/gruf/authentication/base.rb +16 -8
  7. data/lib/gruf/authentication/basic.rb +6 -6
  8. data/lib/gruf/authentication/none.rb +2 -2
  9. data/lib/gruf/authentication/strategies.rb +20 -7
  10. data/lib/gruf/client.rb +73 -11
  11. data/lib/gruf/configuration.rb +9 -6
  12. data/lib/gruf/error.rb +49 -15
  13. data/lib/gruf/errors/debug_info.rb +10 -5
  14. data/lib/gruf/errors/field.rb +12 -5
  15. data/lib/gruf/hooks/active_record/connection_reset.rb +15 -1
  16. data/lib/gruf/hooks/base.rb +26 -4
  17. data/lib/gruf/hooks/registry.rb +15 -9
  18. data/lib/gruf/instrumentation/base.rb +66 -11
  19. data/lib/gruf/instrumentation/output_metadata_timer.rb +6 -3
  20. data/lib/gruf/instrumentation/registry.rb +22 -13
  21. data/lib/gruf/instrumentation/request_context.rb +66 -0
  22. data/lib/gruf/instrumentation/request_logging/formatters/base.rb +38 -0
  23. data/lib/gruf/instrumentation/request_logging/formatters/logstash.rb +40 -0
  24. data/lib/gruf/instrumentation/request_logging/formatters/plain.rb +45 -0
  25. data/lib/gruf/instrumentation/request_logging/hook.rb +145 -0
  26. data/lib/gruf/instrumentation/statsd.rb +19 -13
  27. data/lib/gruf/loggable.rb +4 -1
  28. data/lib/gruf/logging.rb +19 -0
  29. data/lib/gruf/response.rb +19 -4
  30. data/lib/gruf/serializers/errors/base.rb +10 -3
  31. data/lib/gruf/serializers/errors/json.rb +5 -2
  32. data/lib/gruf/server.rb +10 -2
  33. data/lib/gruf/service.rb +32 -22
  34. data/lib/gruf/timer.rb +26 -5
  35. data/lib/gruf/version.rb +1 -1
  36. metadata +7 -2
@@ -15,22 +15,56 @@
15
15
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
16
  #
17
17
  module Gruf
18
+ ##
19
+ # Abstracts out the calling interface for interacting with gRPC clients. Streamlines calling and provides
20
+ # instrumented response objects that also can contain deserialized error messages via serialized objects transported
21
+ # via the service's trailing metadata.
22
+ #
23
+ # begin
24
+ # client = ::Gruf::Client.new(service: ::Demo::ThingService)
25
+ # response = client.call(:GetMyThing, id: 123)
26
+ # puts response.message.inspect
27
+ # rescue Gruf::Client::Error => e
28
+ # puts e.error.inspect
29
+ # end
30
+ #
31
+ # Utilizes SimpleDelegator to wrap the given service that the client is connecting to, which allows a clean interface
32
+ # to the underlying RPC descriptors and methods.
33
+ #
18
34
  class Client < SimpleDelegator
19
35
  include Gruf::Loggable
20
36
 
37
+ ##
38
+ # Represents an error that was returned from the server's trailing metadata. Used as a custom exception object
39
+ # that is instead raised in the case of the service returning serialized error data, as opposed to the normal
40
+ # GRPC::BadStatus error
41
+ #
21
42
  class Error < StandardError
43
+ # @return [Object] error The deserialized error
22
44
  attr_reader :error
23
45
 
46
+ ##
47
+ # Initialize the client error
48
+ #
49
+ # @param [Object] error The deserialized error
50
+ #
24
51
  def initialize(error)
25
52
  @error = error
26
53
  end
27
54
  end
28
55
 
29
- attr_reader :base_klass, :service_klass, :opts
56
+ # @return [Class] The base, friendly name of the service being requested
57
+ attr_reader :base_klass
58
+ # @return [Class] The class name of the gRPC service being requested
59
+ attr_reader :service_klass
60
+ # @return [Hash] A hash of options for the client
61
+ attr_reader :opts
30
62
 
31
63
  ##
64
+ # Initialize the client and setup the stub
65
+ #
32
66
  # @param [Module] service The namespace of the client Stub that is desired to load
33
- # @param [Hash] options
67
+ # @param [Hash] options A hash of options for the client
34
68
  # @option options [String] :password The password for basic authentication for the service.
35
69
  # @option options [String] :hostname The hostname of the service. Defaults to linkerd.
36
70
  #
@@ -47,7 +81,16 @@ module Gruf
47
81
  ##
48
82
  # Call the client's method with given params
49
83
  #
84
+ # @param [String|Symbol] request_method The method that is being requested on the service
85
+ # @param [Hash] params (Optional) A hash of parameters that will be inserted into the gRPC request message that is required
86
+ # for the given above call
87
+ # @param [Hash] metadata (Optional) A hash of metadata key/values that are transported with the client request
88
+ # @param [Hash] opts (Optional) A hash of options to send to the gRPC request_response method
89
+ # @return [Gruf::Response] The response from the server
90
+ # @raise [Gruf::Client::Error|GRPC::BadStatus] If an error occurs, an exception will be raised according to the
91
+ # error type that was returned
50
92
  def call(request_method, params = {}, metadata = {}, opts = {})
93
+ request_method = request_method.to_sym
51
94
  req = request_object(request_method, params)
52
95
  md = build_metadata(metadata)
53
96
  call_sig = call_signature(request_method)
@@ -64,11 +107,14 @@ module Gruf
64
107
  private
65
108
 
66
109
  ##
110
+ # Execute the given request to the service
111
+ #
67
112
  # @param [Symbol] call_sig The call signature being executed
68
- # @param [Object] req
69
- # @param [Hash] md
113
+ # @param [Object] req (Optional) The protobuf request message to send
114
+ # @param [Hash] md (Optional) A hash of metadata key/values that are transported with the client request
115
+ # @param [Hash] opts (Optional) A hash of options to send to the gRPC request_response method
70
116
  #
71
- def execute(call_sig, req, md, opts)
117
+ def execute(call_sig, req, md, opts = {})
72
118
  timed = Timer.time do
73
119
  opts[:return_op] = true
74
120
  opts[:metadata] = md
@@ -78,14 +124,21 @@ module Gruf
78
124
  end
79
125
 
80
126
  ##
81
- # @return [Struct<GRPC::RpcDesc>]
127
+ # Get the appropriate RPC descriptor given the method on the service being called
128
+ #
129
+ # @param [Symbol] request_method The method name being called on the remote service
130
+ # @return [Struct<GRPC::RpcDesc>] Return the given RPC descriptor given the method on the service being called
82
131
  #
83
132
  def rpc_desc(request_method)
84
- service_klass.rpc_descs[request_method.to_sym]
133
+ service_klass.rpc_descs[request_method]
85
134
  end
86
135
 
87
136
  ##
88
- # @return [Class] The request object
137
+ # Get the appropriate protobuf request message for the given request method on the service being called
138
+ #
139
+ # @param [Symbol] request_method The method name being called on the remote service
140
+ # @param [Hash] params (Optional) A hash of parameters that will populate the request object
141
+ # @return [Class] The request object that corresponds to the method being called
89
142
  #
90
143
  def request_object(request_method, params = {})
91
144
  desc = rpc_desc(request_method)
@@ -93,6 +146,8 @@ module Gruf
93
146
  end
94
147
 
95
148
  ##
149
+ # Properly find the appropriate call signature for the GRPC::GenericService given the request method name
150
+ #
96
151
  # @return [Symbol]
97
152
  #
98
153
  def call_signature(request_method)
@@ -101,7 +156,10 @@ module Gruf
101
156
  end
102
157
 
103
158
  ##
104
- # @return [Hash]
159
+ # Build a sanitized, authenticated metadata hash for the given request
160
+ #
161
+ # @param [Hash] metadata A base metadata hash to build from
162
+ # @return [Hash] The compiled metadata hash that is ready to be transported over the wire
105
163
  #
106
164
  def build_metadata(metadata = {})
107
165
  unless opts[:password].empty?
@@ -114,7 +172,9 @@ module Gruf
114
172
  end
115
173
 
116
174
  ##
117
- # @return [Symbol|GRPC::Core::ChannelCredentials]
175
+ # Build the SSL/TLS credentials for the outbound gRPC request
176
+ #
177
+ # @return [Symbol|GRPC::Core::ChannelCredentials] The generated SSL credentials for the outbound gRPC request
118
178
  #
119
179
  # :nocov:
120
180
  def build_ssl_credentials
@@ -130,7 +190,9 @@ module Gruf
130
190
  # :nocov:
131
191
 
132
192
  ##
133
- # @return [Class]
193
+ # Return the specified error deserializer class by the configuration
194
+ #
195
+ # @return [Class] The proper error deserializer class. Defaults to JSON.
134
196
  #
135
197
  def error_deserializer_class
136
198
  if Gruf.error_serializer
@@ -38,7 +38,9 @@ module Gruf
38
38
  authorization_metadata_key: 'authorization',
39
39
  append_server_errors_to_trailing_metadata: true,
40
40
  use_default_hooks: true,
41
- backtrace_on_error: false
41
+ backtrace_on_error: false,
42
+ use_exception_message: true,
43
+ internal_error_message: 'Internal Server Error'
42
44
  }.freeze
43
45
 
44
46
  attr_accessor *VALID_CONFIG_KEYS.keys
@@ -53,8 +55,8 @@ module Gruf
53
55
  ##
54
56
  # Yield self for ruby-style initialization
55
57
  #
56
- # @yields [Gruf::Configuration]
57
- # @return [Gruf::Configuration]
58
+ # @yields [Gruf::Configuration] The configuration object for gruf
59
+ # @return [Gruf::Configuration] The configuration object for gruf
58
60
  #
59
61
  def configure
60
62
  yield self
@@ -63,7 +65,7 @@ module Gruf
63
65
  ##
64
66
  # Return the current configuration options as a Hash
65
67
  #
66
- # @return [Hash]
68
+ # @return [Hash] The configuration for gruf, represented as a Hash
67
69
  #
68
70
  def options
69
71
  opts = {}
@@ -80,7 +82,7 @@ module Gruf
80
82
  #
81
83
  def reset
82
84
  VALID_CONFIG_KEYS.each do |k, v|
83
- send((k.to_s + '=').to_sym, v)
85
+ send((k.to_s + '='), v)
84
86
  end
85
87
  if defined?(Rails) && Rails.logger
86
88
  self.root_path = Rails.root
@@ -102,6 +104,7 @@ module Gruf
102
104
  if use_default_hooks
103
105
  Gruf::Hooks::Registry.add(:ar_connection_reset, Gruf::Hooks::ActiveRecord::ConnectionReset)
104
106
  Gruf::Instrumentation::Registry.add(:output_metadata_timer, Gruf::Instrumentation::OutputMetadataTimer)
107
+ Gruf::Instrumentation::Registry.add(:request_logging, Gruf::Instrumentation::RequestLogging::Hook)
105
108
  end
106
109
  options
107
110
  end
@@ -111,7 +114,7 @@ module Gruf
111
114
  ##
112
115
  # Automatically determine environment
113
116
  #
114
- # @return [String]
117
+ # @return [String] The current Ruby environment
115
118
  #
116
119
  def environment
117
120
  if defined?(Rails)
@@ -28,6 +28,7 @@ module Gruf
28
28
  class Error
29
29
  include Gruf::Loggable
30
30
 
31
+ # @return [Hash<GRPC::BadStatus>] A hash mapping of gRPC BadStatus codes to error symbols
31
32
  TYPES = {
32
33
  ok: GRPC::Ok,
33
34
  cancelled: GRPC::Cancelled,
@@ -50,15 +51,30 @@ module Gruf
50
51
  data_loss: GRPC::DataLoss
51
52
  }.freeze
52
53
 
53
- attr_accessor :code, :app_code, :message, :field_errors, :debug_info, :grpc_error
54
+ # @return [Symbol] The given internal gRPC code for the error
55
+ attr_accessor :code
56
+ # @return [Symbol] An arbitrary application code that can be used for logical processing of the error by the client
57
+ attr_accessor :app_code
58
+ # @return [String] The error message returned by the server
59
+ attr_accessor :message
60
+ # @return [Array] An array of field errors that can be returned by the server
61
+ attr_accessor :field_errors
62
+ # @return [Object] A hash of debugging information, such as a stack trace and exception name, that can be used to
63
+ # debug an given error response. This is sent by the server over the trailing metadata.
64
+ attr_accessor :debug_info
65
+ # @return [GRPC::BadStatus] The gRPC BadStatus error object that was generated
66
+ attr_accessor :grpc_error
67
+ # @return [Hash] The trailing metadata that was attached to the error
54
68
  attr_reader :metadata
55
69
 
56
70
  ##
57
71
  # Initialize the error, setting default values
58
72
  #
73
+ # @param [Hash] args (Optional) An optional hash of arguments that will set fields on the error object
74
+ #
59
75
  def initialize(args = {})
60
76
  args.each do |k, v|
61
- send("#{k.to_sym}=", v) if respond_to?(k.to_sym)
77
+ send("#{k}=", v) if respond_to?(k)
62
78
  end
63
79
  @field_errors = []
64
80
  end
@@ -66,24 +82,29 @@ module Gruf
66
82
  ##
67
83
  # Add a field error to this error package
68
84
  #
69
- # @param [Symbol] field_name
70
- # @param [Symbol] error_code
71
- # @param [String] message
85
+ # @param [Symbol] field_name The field name for the error
86
+ # @param [Symbol] error_code The application error code for the error; e.g. :job_not_found
87
+ # @param [String] message The application error message for the error; e.g. "Job not found with ID 123"
72
88
  #
73
89
  def add_field_error(field_name, error_code, message = '')
74
90
  @field_errors << Errors::Field.new(field_name, error_code, message)
75
91
  end
76
92
 
77
93
  ##
78
- # @param [String] detail
79
- # @param [Array<String>] stack_trace
94
+ # Set the debugging information for the error message
95
+ #
96
+ # @param [String] detail The detailed message generated by the exception
97
+ # @param [Array<String>] stack_trace An array of strings that represents the exception backtrace generated by the
98
+ # service
80
99
  #
81
100
  def set_debug_info(detail, stack_trace = [])
82
101
  @debug_info = Errors::DebugInfo.new(detail, stack_trace)
83
102
  end
84
103
 
85
104
  ##
86
- # Ensure all metadata values are strings
105
+ # Ensure all metadata values are strings as HTTP/2 requires string values for transport
106
+ #
107
+ # @return [Hash] The newly set metadata
87
108
  #
88
109
  def metadata=(md)
89
110
  @metadata = md.map { |k, str| [k, str.to_s] }.to_h
@@ -92,7 +113,7 @@ module Gruf
92
113
  ##
93
114
  # Serialize the error for transport
94
115
  #
95
- # @return [String]
116
+ # @return [String] The serialized error message
96
117
  #
97
118
  def serialize
98
119
  serializer = serializer_class.new(self)
@@ -100,8 +121,10 @@ module Gruf
100
121
  end
101
122
 
102
123
  ##
103
- # @param [GRPC::ActiveCall]
104
- # @return [Error]
124
+ # Append any appropriate errors to the gRPC call and properly update the output metadata
125
+ #
126
+ # @param [GRPC::ActiveCall] active_call The marshalled gRPC call
127
+ # @return [Error] Return the error itself with the GRPC::ActiveCall attached and error metadata appended
105
128
  #
106
129
  def attach_to_call(active_call)
107
130
  metadata[Gruf.error_metadata_key.to_sym] = serialize if Gruf.append_server_errors_to_trailing_metadata
@@ -112,15 +135,20 @@ module Gruf
112
135
  end
113
136
 
114
137
  ##
115
- # @param [GRPC::ActiveCall]
116
- # @return [GRPC::BadStatus]
138
+ # Fail the current gRPC call with the given error, properly attaching it to the call and raising the appropriate
139
+ # gRPC BadStatus code.
140
+ #
141
+ # @param [GRPC::ActiveCall] active_call The marshalled gRPC call
142
+ # @return [GRPC::BadStatus] The gRPC BadStatus code this error is mapped to
117
143
  #
118
144
  def fail!(active_call)
119
145
  raise attach_to_call(active_call).grpc_error
120
146
  end
121
147
 
122
148
  ##
123
- # @return [Hash]
149
+ # Return the error represented in Hash form
150
+ #
151
+ # @return [Hash] The error as a hash
124
152
  #
125
153
  def to_h
126
154
  {
@@ -133,6 +161,8 @@ module Gruf
133
161
  end
134
162
 
135
163
  ##
164
+ # Return the appropriately mapped GRPC::BadStatus error object for this error
165
+ #
136
166
  # @return [GRPC::BadStatus]
137
167
  #
138
168
  def grpc_error
@@ -142,6 +172,8 @@ module Gruf
142
172
  private
143
173
 
144
174
  ##
175
+ # Return the error serializer being used for gruf
176
+ #
145
177
  # @return [Gruf::Serializers::Errors::Base]
146
178
  #
147
179
  def serializer_class
@@ -153,7 +185,9 @@ module Gruf
153
185
  end
154
186
 
155
187
  ##
156
- # @return [Class]
188
+ # Return the appropriate gRPC class for the given error code
189
+ #
190
+ # @return [Class] The gRPC error class
157
191
  #
158
192
  def grpc_class
159
193
  TYPES[code]
@@ -17,14 +17,17 @@
17
17
  module Gruf
18
18
  module Errors
19
19
  ##
20
- # Represents debugging information
20
+ # Represents debugging information for an exception that occurred in a gRPC service
21
21
  #
22
22
  class DebugInfo
23
- attr_reader :detail, :stack_trace
23
+ # @return [String] The detail message of the exception
24
+ attr_reader :detail
25
+ # @return [Array<String>] The stack trace generated by the exception as an array of strings
26
+ attr_reader :stack_trace
24
27
 
25
28
  ##
26
- # @param [String] detail
27
- # @param [Array<String>] stack_trace
29
+ # @param [String] detail The detail message of the exception
30
+ # @param [Array<String>] stack_trace The stack trace generated by the exception as an array of strings
28
31
  #
29
32
  def initialize(detail, stack_trace = [])
30
33
  @detail = detail
@@ -32,7 +35,9 @@ module Gruf
32
35
  end
33
36
 
34
37
  ##
35
- # @return [Hash]
38
+ # Return this object marshalled into a hash
39
+ #
40
+ # @return [Hash] The debug info represented as a has
36
41
  #
37
42
  def to_h
38
43
  {
@@ -20,12 +20,17 @@ module Gruf
20
20
  # Represents a field-specific error
21
21
  #
22
22
  class Field
23
- attr_reader :field_name, :error_code, :message
23
+ # @return [Symbol] The name of the field as a Symbol
24
+ attr_reader :field_name
25
+ # @return [Symbol] The application error code for the field, e.g. :job_not_found
26
+ attr_reader :error_code
27
+ # @return [String] The error message for the field, e.g. "Job with ID 123 not found"
28
+ attr_reader :message
24
29
 
25
30
  ##
26
- # @param [Symbol] field_name
27
- # @param [Symbol] error_code
28
- # @param [String] message
31
+ # @param [Symbol] field_name The name of the field as a Symbol
32
+ # @param [Symbol] error_code The application error code for the field, e.g. :job_not_found
33
+ # @param [String] message (Optional) The error message for the field, e.g. "Job with ID 123 not found"
29
34
  #
30
35
  def initialize(field_name, error_code, message = '')
31
36
  @field_name = field_name
@@ -34,7 +39,9 @@ module Gruf
34
39
  end
35
40
 
36
41
  ##
37
- # @return [Hash]
42
+ # Return the field error represented as a hash
43
+ #
44
+ # @return [Hash] The error represented as a hash
38
45
  #
39
46
  def to_h
40
47
  {
@@ -21,12 +21,26 @@ module Gruf
21
21
  # Resets the ActiveRecord connection to maintain accurate connected state in the thread pool
22
22
  #
23
23
  class ConnectionReset < Gruf::Hooks::Base
24
- def after(_success, _response, _call_signature, _req, _call)
24
+ ##
25
+ # Reset any ActiveRecord connections after a gRPC service is called. Because of the way gRPC manages its
26
+ # connection pool, we need to ensure that this is done to properly
27
+ #
28
+ # @param [Boolean] _success Whether or not the call was successful
29
+ # @param [Object] _response The protobuf response object
30
+ # @param [Symbol] _call_signature The gRPC method on the service that was called
31
+ # @param [Object] _request The protobuf request object
32
+ # @param [GRPC::ActiveCall] _call the gRPC core active call object, which represents marshalled data for
33
+ # the call itself
34
+ #
35
+ def after(_success, _response, _call_signature, _request, _call)
25
36
  ::ActiveRecord::Base.clear_active_connections! if enabled?
26
37
  end
27
38
 
28
39
  private
29
40
 
41
+ ##
42
+ # @return [Boolean] If AR is loaded, we can enable this hook safely
43
+ #
30
44
  def enabled?
31
45
  defined?(::ActiveRecord::Base)
32
46
  end