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.
- 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
|