gruf 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -19
- data/README.md +71 -21
- data/lib/gruf.rb +3 -0
- data/lib/gruf/authentication.rb +9 -1
- data/lib/gruf/authentication/base.rb +16 -8
- data/lib/gruf/authentication/basic.rb +6 -6
- data/lib/gruf/authentication/none.rb +2 -2
- data/lib/gruf/authentication/strategies.rb +20 -7
- data/lib/gruf/client.rb +73 -11
- data/lib/gruf/configuration.rb +9 -6
- data/lib/gruf/error.rb +49 -15
- data/lib/gruf/errors/debug_info.rb +10 -5
- data/lib/gruf/errors/field.rb +12 -5
- data/lib/gruf/hooks/active_record/connection_reset.rb +15 -1
- data/lib/gruf/hooks/base.rb +26 -4
- data/lib/gruf/hooks/registry.rb +15 -9
- data/lib/gruf/instrumentation/base.rb +66 -11
- data/lib/gruf/instrumentation/output_metadata_timer.rb +6 -3
- data/lib/gruf/instrumentation/registry.rb +22 -13
- data/lib/gruf/instrumentation/request_context.rb +66 -0
- data/lib/gruf/instrumentation/request_logging/formatters/base.rb +38 -0
- data/lib/gruf/instrumentation/request_logging/formatters/logstash.rb +40 -0
- data/lib/gruf/instrumentation/request_logging/formatters/plain.rb +45 -0
- data/lib/gruf/instrumentation/request_logging/hook.rb +145 -0
- data/lib/gruf/instrumentation/statsd.rb +19 -13
- data/lib/gruf/loggable.rb +4 -1
- data/lib/gruf/logging.rb +19 -0
- data/lib/gruf/response.rb +19 -4
- data/lib/gruf/serializers/errors/base.rb +10 -3
- data/lib/gruf/serializers/errors/json.rb +5 -2
- data/lib/gruf/server.rb +10 -2
- data/lib/gruf/service.rb +32 -22
- data/lib/gruf/timer.rb +26 -5
- data/lib/gruf/version.rb +1 -1
- metadata +7 -2
data/lib/gruf/client.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
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
|
133
|
+
service_klass.rpc_descs[request_method]
|
85
134
|
end
|
86
135
|
|
87
136
|
##
|
88
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
data/lib/gruf/configuration.rb
CHANGED
@@ -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 + '=')
|
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)
|
data/lib/gruf/error.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
#
|
79
|
-
#
|
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
|
-
#
|
104
|
-
#
|
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
|
-
#
|
116
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
{
|
data/lib/gruf/errors/field.rb
CHANGED
@@ -20,12 +20,17 @@ module Gruf
|
|
20
20
|
# Represents a field-specific error
|
21
21
|
#
|
22
22
|
class Field
|
23
|
-
|
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
|
-
#
|
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
|
-
|
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
|