istox_gruf 2.7.1

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +246 -0
  3. data/CODE_OF_CONDUCT.md +46 -0
  4. data/README.md +544 -0
  5. data/bin/gruf +29 -0
  6. data/lib/gruf.rb +50 -0
  7. data/lib/gruf/cli/executor.rb +99 -0
  8. data/lib/gruf/client.rb +217 -0
  9. data/lib/gruf/client/error.rb +66 -0
  10. data/lib/gruf/client/error_factory.rb +105 -0
  11. data/lib/gruf/configuration.rb +137 -0
  12. data/lib/gruf/controllers/base.rb +102 -0
  13. data/lib/gruf/controllers/request.rb +121 -0
  14. data/lib/gruf/controllers/service_binder.rb +117 -0
  15. data/lib/gruf/error.rb +230 -0
  16. data/lib/gruf/errors/debug_info.rb +56 -0
  17. data/lib/gruf/errors/field.rb +56 -0
  18. data/lib/gruf/errors/helpers.rb +44 -0
  19. data/lib/gruf/hooks/base.rb +34 -0
  20. data/lib/gruf/hooks/executor.rb +47 -0
  21. data/lib/gruf/hooks/registry.rb +159 -0
  22. data/lib/gruf/instrumentable_grpc_server.rb +64 -0
  23. data/lib/gruf/integrations/rails/railtie.rb +10 -0
  24. data/lib/gruf/interceptors/active_record/connection_reset.rb +48 -0
  25. data/lib/gruf/interceptors/authentication/basic.rb +87 -0
  26. data/lib/gruf/interceptors/base.rb +53 -0
  27. data/lib/gruf/interceptors/client_interceptor.rb +125 -0
  28. data/lib/gruf/interceptors/context.rb +56 -0
  29. data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +61 -0
  30. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/base.rb +41 -0
  31. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/logstash.rb +43 -0
  32. data/lib/gruf/interceptors/instrumentation/request_logging/formatters/plain.rb +48 -0
  33. data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +225 -0
  34. data/lib/gruf/interceptors/instrumentation/statsd.rb +82 -0
  35. data/lib/gruf/interceptors/registry.rb +161 -0
  36. data/lib/gruf/interceptors/server_interceptor.rb +34 -0
  37. data/lib/gruf/interceptors/timer.rb +85 -0
  38. data/lib/gruf/loggable.rb +30 -0
  39. data/lib/gruf/logging.rb +53 -0
  40. data/lib/gruf/outbound/request_context.rb +71 -0
  41. data/lib/gruf/response.rb +71 -0
  42. data/lib/gruf/serializers/errors/base.rb +57 -0
  43. data/lib/gruf/serializers/errors/json.rb +43 -0
  44. data/lib/gruf/server.rb +294 -0
  45. data/lib/gruf/synchronized_client.rb +97 -0
  46. data/lib/gruf/timer.rb +78 -0
  47. data/lib/gruf/version.rb +20 -0
  48. metadata +203 -0
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Controllers
20
+ ##
21
+ # Binds gRPC services to a gruf controller
22
+ #
23
+ class ServiceBinder
24
+ ##
25
+ # Represents a bound RPC descriptor for future-proofing internal helpers
26
+ #
27
+ class BoundDesc < SimpleDelegator; end
28
+
29
+ ##
30
+ # Initialize a service binder instance with the given service
31
+ #
32
+ # @param [GRPC::GenericService] service The gRPC service stub to bind
33
+ #
34
+ def initialize(service)
35
+ @service = service
36
+ end
37
+
38
+ ##
39
+ # Bind all methods on the service to the passed controller
40
+ #
41
+ # @param [Gruf::Controllers::Base] controller
42
+ #
43
+ def bind!(controller)
44
+ rpc_methods.each { |name, desc| bind_method(controller, name, desc) }
45
+ end
46
+
47
+ private
48
+
49
+ ##
50
+ # Bind the grpc methods to the service, allowing for server interception and execution control
51
+ #
52
+ # @param [Gruf::Controllers::Base] controller
53
+ # @param [Symbol] method_name
54
+ # @param [BoundDesc] desc
55
+ #
56
+ def bind_method(controller, method_name, desc)
57
+ method_key = method_name.to_s.underscore.to_sym
58
+ service_ref = @service
59
+
60
+ @service.class_eval do
61
+ if desc.request_response?
62
+ define_method(method_key) do |message, active_call|
63
+ c = controller.new(
64
+ method_key: method_key,
65
+ service: service_ref,
66
+ message: message,
67
+ active_call: active_call,
68
+ rpc_desc: desc
69
+ )
70
+ c.call(method_key)
71
+ end
72
+ elsif desc.client_streamer?
73
+ define_method(method_key) do |active_call|
74
+ c = controller.new(
75
+ method_key: method_key,
76
+ service: service_ref,
77
+ message: proc { |&block| active_call.each_remote_read(&block) },
78
+ active_call: active_call,
79
+ rpc_desc: desc
80
+ )
81
+ c.call(method_key)
82
+ end
83
+ elsif desc.server_streamer?
84
+ define_method(method_key) do |message, active_call, &block|
85
+ c = controller.new(
86
+ method_key: method_key,
87
+ service: service_ref,
88
+ message: message,
89
+ active_call: active_call,
90
+ rpc_desc: desc
91
+ )
92
+ c.call(method_key, &block)
93
+ end
94
+ else # bidi
95
+ define_method(method_key) do |messages, active_call, &block|
96
+ c = controller.new(
97
+ method_key: method_key,
98
+ service: service_ref,
99
+ message: messages,
100
+ active_call: active_call,
101
+ rpc_desc: desc
102
+ )
103
+ c.call(method_key, &block)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ ##
110
+ # @return Array<Gruf::Controllers::ServiceBinder::BoundDesc>
111
+ #
112
+ def rpc_methods
113
+ @service.rpc_descs.map { |rd| BoundDesc.new(rd) }
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ require_relative 'errors/field'
19
+ require_relative 'errors/debug_info'
20
+ require_relative 'serializers/errors/base'
21
+ require_relative 'serializers/errors/json'
22
+
23
+ module Gruf
24
+ ##
25
+ # Represents a error that can be transformed into a gRPC error and have metadata attached to the trailing headers.
26
+ # This layer acts as an middle layer that can have metadata injection, tracing support, and other functionality
27
+ # not present in the gRPC core.
28
+ #
29
+ class Error
30
+ include Gruf::Loggable
31
+
32
+ # @return [Hash<GRPC::BadStatus>] A hash mapping of gRPC BadStatus codes to error symbols
33
+ TYPES = {
34
+ ok: GRPC::Ok,
35
+ cancelled: GRPC::Cancelled,
36
+ unknown: GRPC::Unknown,
37
+ invalid_argument: GRPC::InvalidArgument,
38
+ bad_request: GRPC::InvalidArgument,
39
+ deadline_exceeded: GRPC::DeadlineExceeded,
40
+ not_found: GRPC::NotFound,
41
+ already_exists: GRPC::AlreadyExists,
42
+ unauthorized: GRPC::PermissionDenied,
43
+ permission_denied: GRPC::PermissionDenied,
44
+ unauthenticated: GRPC::Unauthenticated,
45
+ resource_exhausted: GRPC::ResourceExhausted,
46
+ failed_precondition: GRPC::FailedPrecondition,
47
+ aborted: GRPC::Aborted,
48
+ out_of_range: GRPC::OutOfRange,
49
+ unimplemented: GRPC::Unimplemented,
50
+ internal: GRPC::Internal,
51
+ unavailable: GRPC::Unavailable,
52
+ data_loss: GRPC::DataLoss
53
+ }.freeze
54
+
55
+ # Default limit on trailing metadata is 8KB. We need to be careful
56
+ # not to overflow this limit, or the response message will never
57
+ # be sent. Instead, resource_exhausted will be thrown.
58
+ MAX_METADATA_SIZE = 7.5 * 1_024
59
+ METADATA_SIZE_EXCEEDED_CODE = 'metadata_size_exceeded'
60
+ METADATA_SIZE_EXCEEDED_MSG = 'Metadata too long, risks exceeding http2 trailing metadata limit.'
61
+
62
+ # @return [Symbol] The given internal gRPC code for the error
63
+ attr_accessor :code
64
+ # @return [Symbol] An arbitrary application code that can be used for logical processing of the error by the client
65
+ attr_accessor :app_code
66
+ # @return [String] The error message returned by the server
67
+ attr_accessor :message
68
+ # @return [Array] An array of field errors that can be returned by the server
69
+ attr_accessor :field_errors
70
+ # @return [Errors::DebugInfo] A object containing debugging information, such as a stack trace and exception name,
71
+ # that can be used to debug an given error response. This is sent by the server over the trailing metadata.
72
+ attr_accessor :debug_info
73
+ # @return [GRPC::BadStatus] The gRPC BadStatus error object that was generated
74
+ attr_writer :grpc_error
75
+ # @return [Hash] The trailing metadata that was attached to the error
76
+ attr_reader :metadata
77
+
78
+ ##
79
+ # Initialize the error, setting default values
80
+ #
81
+ # @param [Hash] args (Optional) An optional hash of arguments that will set fields on the error object
82
+ #
83
+ def initialize(args = {})
84
+ @field_errors = []
85
+ @metadata = {}
86
+ args.each do |k, v|
87
+ send("#{k}=", v) if respond_to?(k)
88
+ end
89
+ end
90
+
91
+ ##
92
+ # Add a field error to this error package
93
+ #
94
+ # @param [Symbol] field_name The field name for the error
95
+ # @param [Symbol] error_code The application error code for the error; e.g. :job_not_found
96
+ # @param [String] message The application error message for the error; e.g. "Job not found with ID 123"
97
+ #
98
+ def add_field_error(field_name, error_code, message = '')
99
+ @field_errors << Errors::Field.new(field_name, error_code, message)
100
+ end
101
+
102
+ ##
103
+ # Return true if there are any present field errors
104
+ #
105
+ # @return [Boolean] True if the service has any field errors
106
+ #
107
+ def has_field_errors?
108
+ @field_errors.any?
109
+ end
110
+
111
+ ##
112
+ # Set the debugging information for the error message
113
+ #
114
+ # @param [String] detail The detailed message generated by the exception
115
+ # @param [Array<String>] stack_trace An array of strings that represents the exception backtrace generated by the
116
+ # service
117
+ #
118
+ def set_debug_info(detail, stack_trace = [])
119
+ @debug_info = Errors::DebugInfo.new(detail, stack_trace)
120
+ end
121
+
122
+ ##
123
+ # Ensure all metadata values are strings as HTTP/2 requires string values for transport
124
+ #
125
+ # @param [Hash] metadata The existing metadata hash
126
+ # @return [Hash] The newly set metadata
127
+ #
128
+ def metadata=(metadata)
129
+ @metadata = metadata.map { |k, str| [k, str.to_s] }.to_h
130
+ end
131
+
132
+ ##
133
+ # Serialize the error for transport
134
+ #
135
+ # @return [String] The serialized error message
136
+ #
137
+ def serialize
138
+ serializer = serializer_class.new(self)
139
+ serializer.serialize.to_s
140
+ end
141
+
142
+ ##
143
+ # Update the trailing metadata on the given gRPC call, including the error payload if configured
144
+ # to do so.
145
+ #
146
+ # @param [GRPC::ActiveCall] active_call The marshalled gRPC call
147
+ # @return [Error] Return the error itself after updating metadata on the given gRPC call.
148
+ # In the case of a metadata overflow error, we replace the current error with
149
+ # a new one that won't cause a low-level http2 error.
150
+ #
151
+ def attach_to_call(active_call)
152
+ metadata[Gruf.error_metadata_key.to_sym] = serialize if Gruf.append_server_errors_to_trailing_metadata
153
+ return self if metadata.empty? || !active_call || !active_call.respond_to?(:output_metadata)
154
+
155
+ # Check if we've overflown the maximum size of output metadata. If so,
156
+ # log a warning and replace the metadata with something smaller to avoid
157
+ # resource exhausted errors.
158
+ if metadata.inspect.size > MAX_METADATA_SIZE
159
+ code = METADATA_SIZE_EXCEEDED_CODE
160
+ msg = METADATA_SIZE_EXCEEDED_MSG
161
+ logger.warn "#{code}: #{msg} Original error: #{to_h.inspect}"
162
+ err = Gruf::Error.new(code: :internal, app_code: code, message: msg)
163
+ return err.attach_to_call(active_call)
164
+ end
165
+
166
+ active_call.output_metadata.update(metadata)
167
+ self
168
+ end
169
+
170
+ ##
171
+ # Fail the current gRPC call with the given error, properly attaching it to the call and raising the appropriate
172
+ # gRPC BadStatus code.
173
+ #
174
+ # @param [GRPC::ActiveCall] active_call The marshalled gRPC call
175
+ # @return [GRPC::BadStatus] The gRPC BadStatus code this error is mapped to
176
+ #
177
+ def fail!(active_call)
178
+ raise attach_to_call(active_call).grpc_error
179
+ end
180
+
181
+ ##
182
+ # Return the error represented in Hash form
183
+ #
184
+ # @return [Hash] The error as a hash
185
+ #
186
+ def to_h
187
+ {
188
+ code: code,
189
+ app_code: app_code,
190
+ message: message,
191
+ field_errors: field_errors.map(&:to_h),
192
+ debug_info: debug_info.to_h
193
+ }
194
+ end
195
+
196
+ ##
197
+ # Return the appropriately mapped GRPC::BadStatus error object for this error
198
+ #
199
+ # @return [GRPC::BadStatus]
200
+ #
201
+ def grpc_error
202
+ md = @metadata || {}
203
+ @grpc_error = grpc_class.new(message, **md)
204
+ end
205
+
206
+ private
207
+
208
+ ##
209
+ # Return the error serializer being used for gruf
210
+ #
211
+ # @return [Gruf::Serializers::Errors::Base]
212
+ #
213
+ def serializer_class
214
+ if Gruf.error_serializer
215
+ Gruf.error_serializer.is_a?(Class) ? Gruf.error_serializer : Gruf.error_serializer.to_s.constantize
216
+ else
217
+ Gruf::Serializers::Errors::Json
218
+ end
219
+ end
220
+
221
+ ##
222
+ # Return the appropriate gRPC class for the given error code
223
+ #
224
+ # @return [Class] The gRPC error class
225
+ #
226
+ def grpc_class
227
+ TYPES[code]
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Errors
20
+ ##
21
+ # Represents debugging information for an exception that occurred in a gRPC service
22
+ #
23
+ class DebugInfo
24
+ # @return [String] The detail message of the exception
25
+ attr_reader :detail
26
+ # @return [Array<String>] The stack trace generated by the exception as an array of strings
27
+ attr_reader :stack_trace
28
+
29
+ ##
30
+ # @param [String] detail The detail message of the exception
31
+ # @param [Array<String>] stack_trace The stack trace generated by the exception as an array of strings
32
+ #
33
+ def initialize(detail, stack_trace = [])
34
+ @detail = detail
35
+ @stack_trace = (stack_trace.is_a?(String) ? stack_trace.split("\n") : stack_trace)
36
+
37
+ # Limit the size of the stack trace to reduce risk of overflowing metadata
38
+ stack_trace_limit = Gruf.backtrace_limit.to_i
39
+ stack_trace_limit = 10 if stack_trace_limit.negative?
40
+ @stack_trace = @stack_trace[0..stack_trace_limit] if stack_trace_limit.positive?
41
+ end
42
+
43
+ ##
44
+ # Return this object marshalled into a hash
45
+ #
46
+ # @return [Hash] The debug info represented as a hash
47
+ #
48
+ def to_h
49
+ {
50
+ detail: detail,
51
+ stack_trace: stack_trace
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ # Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ #
18
+ module Gruf
19
+ module Errors
20
+ ##
21
+ # Represents a field-specific error
22
+ #
23
+ class Field
24
+ # @return [Symbol] The name of the field as a Symbol
25
+ attr_reader :field_name
26
+ # @return [Symbol] The application error code for the field, e.g. :job_not_found
27
+ attr_reader :error_code
28
+ # @return [String] The error message for the field, e.g. "Job with ID 123 not found"
29
+ attr_reader :message
30
+
31
+ ##
32
+ # @param [Symbol] field_name The name of the field as a Symbol
33
+ # @param [Symbol] error_code The application error code for the field, e.g. :job_not_found
34
+ # @param [String] message (Optional) The error message for the field, e.g. "Job with ID 123 not found"
35
+ #
36
+ def initialize(field_name, error_code, message = '')
37
+ @field_name = field_name
38
+ @error_code = error_code
39
+ @message = message
40
+ end
41
+
42
+ ##
43
+ # Return the field error represented as a hash
44
+ #
45
+ # @return [Hash] The error represented as a hash
46
+ #
47
+ def to_h
48
+ {
49
+ field_name: field_name,
50
+ error_code: error_code,
51
+ message: message
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end