gruf 1.0.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 +7 -0
- data/CHANGELOG.md +81 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/README.md +312 -0
- data/bin/gruf +29 -0
- data/gruf.gemspec +41 -0
- data/lib/gruf.rb +38 -0
- data/lib/gruf/authentication.rb +57 -0
- data/lib/gruf/authentication/base.rb +57 -0
- data/lib/gruf/authentication/basic.rb +74 -0
- data/lib/gruf/authentication/none.rb +32 -0
- data/lib/gruf/authentication/strategies.rb +94 -0
- data/lib/gruf/client.rb +141 -0
- data/lib/gruf/configuration.rb +124 -0
- data/lib/gruf/error.rb +162 -0
- data/lib/gruf/errors/debug_info.rb +45 -0
- data/lib/gruf/errors/field.rb +48 -0
- data/lib/gruf/hooks/active_record/connection_reset.rb +36 -0
- data/lib/gruf/hooks/base.rb +44 -0
- data/lib/gruf/hooks/registry.rb +104 -0
- data/lib/gruf/instrumentation/base.rb +59 -0
- data/lib/gruf/instrumentation/output_metadata_timer.rb +40 -0
- data/lib/gruf/instrumentation/registry.rb +95 -0
- data/lib/gruf/instrumentation/statsd.rb +74 -0
- data/lib/gruf/loggable.rb +26 -0
- data/lib/gruf/logging.rb +33 -0
- data/lib/gruf/response.rb +55 -0
- data/lib/gruf/serializers/errors/base.rb +49 -0
- data/lib/gruf/serializers/errors/json.rb +39 -0
- data/lib/gruf/server.rb +92 -0
- data/lib/gruf/service.rb +271 -0
- data/lib/gruf/timer.rb +56 -0
- data/lib/gruf/version.rb +19 -0
- metadata +160 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
5
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
6
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
7
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
#
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
10
|
+
# Software.
|
11
|
+
#
|
12
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
13
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
14
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
15
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
|
+
#
|
17
|
+
module Gruf
|
18
|
+
module Serializers
|
19
|
+
module Errors
|
20
|
+
##
|
21
|
+
# Base class for serialization of errors for transport across the grpc protocol
|
22
|
+
#
|
23
|
+
class Base
|
24
|
+
attr_reader :error
|
25
|
+
|
26
|
+
##
|
27
|
+
# @param [Gruf::Error|String] err
|
28
|
+
#
|
29
|
+
def initialize(err)
|
30
|
+
@error = err
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# @return [String]
|
35
|
+
#
|
36
|
+
def serialize
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# @return [Object|Hash]
|
42
|
+
#
|
43
|
+
def deserialize
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
5
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
6
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
7
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
#
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
10
|
+
# Software.
|
11
|
+
#
|
12
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
13
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
14
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
15
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
|
+
#
|
17
|
+
require 'json'
|
18
|
+
|
19
|
+
module Gruf
|
20
|
+
module Serializers
|
21
|
+
module Errors
|
22
|
+
class Json < Base
|
23
|
+
##
|
24
|
+
# @return [String]
|
25
|
+
#
|
26
|
+
def serialize
|
27
|
+
@error.to_h.to_json
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# @return [Object|Hash]
|
32
|
+
#
|
33
|
+
def deserialize
|
34
|
+
JSON.parse(@error)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/gruf/server.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
5
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
6
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
7
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
#
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
10
|
+
# Software.
|
11
|
+
#
|
12
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
13
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
14
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
15
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
|
+
#
|
17
|
+
module Gruf
|
18
|
+
##
|
19
|
+
# Represents a gRPC server. Automatically loads and augments gRPC handlers and services
|
20
|
+
# based on configuration values.
|
21
|
+
#
|
22
|
+
class Server
|
23
|
+
include Gruf::Loggable
|
24
|
+
|
25
|
+
attr_accessor :services
|
26
|
+
|
27
|
+
def initialize(services: [])
|
28
|
+
setup!
|
29
|
+
load_services(services)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Start the gRPC server
|
34
|
+
#
|
35
|
+
# :nocov:
|
36
|
+
def start!
|
37
|
+
logger.info { 'Booting gRPC Server...' }
|
38
|
+
server = GRPC::RpcServer.new
|
39
|
+
server.add_http2_port Gruf.server_binding_url, ssl_credentials
|
40
|
+
services.each do |s|
|
41
|
+
server.handle(s)
|
42
|
+
end
|
43
|
+
server.run_till_terminated
|
44
|
+
logger.info { 'Shutting down gRPC server...' }
|
45
|
+
end
|
46
|
+
# :nocov:
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
##
|
51
|
+
# Return all loaded gRPC services
|
52
|
+
#
|
53
|
+
# @param [Array<Class>] svcs
|
54
|
+
# @return [Array<Class>]
|
55
|
+
#
|
56
|
+
def load_services(svcs)
|
57
|
+
unless @services
|
58
|
+
@services = Gruf.services.concat(svcs)
|
59
|
+
@services.uniq!
|
60
|
+
end
|
61
|
+
@services
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Auto-load all gRPC handlers
|
66
|
+
#
|
67
|
+
# :nocov:
|
68
|
+
def setup!
|
69
|
+
Dir["#{Gruf.servers_path}/**/*.rb"].each do |f|
|
70
|
+
logger.info "- Loading gRPC service file: #{f}"
|
71
|
+
require f
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# :nocov:
|
75
|
+
|
76
|
+
##
|
77
|
+
# @return [GRPC::Core::ServerCredentials|Symbol]
|
78
|
+
#
|
79
|
+
# :nocov:
|
80
|
+
def ssl_credentials
|
81
|
+
if Gruf.use_ssl
|
82
|
+
private_key = File.read Gruf.ssl_key_file
|
83
|
+
cert_chain = File.read Gruf.ssl_crt_file
|
84
|
+
certs = [nil, [{ private_key: private_key, cert_chain: cert_chain }], false]
|
85
|
+
GRPC::Core::ServerCredentials.new(*certs)
|
86
|
+
else
|
87
|
+
:this_port_is_insecure
|
88
|
+
end
|
89
|
+
end
|
90
|
+
# :nocov:
|
91
|
+
end
|
92
|
+
end
|
data/lib/gruf/service.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
5
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
6
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
7
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
#
|
9
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
10
|
+
# Software.
|
11
|
+
#
|
12
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
13
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
14
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
15
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
|
+
#
|
17
|
+
module Gruf
|
18
|
+
##
|
19
|
+
# Module for gRPC endpoints
|
20
|
+
#
|
21
|
+
module Service
|
22
|
+
extend ActiveSupport::Concern
|
23
|
+
|
24
|
+
included do
|
25
|
+
include Gruf::Loggable
|
26
|
+
|
27
|
+
##
|
28
|
+
# Hook into method_added to add pre/post interceptors for endpoints
|
29
|
+
#
|
30
|
+
# @param [String] method_name
|
31
|
+
#
|
32
|
+
def self.method_added(method_name)
|
33
|
+
return if @__last_methods_added && @__last_methods_added.include?(method_name)
|
34
|
+
return unless rpc_handler_names.include?(method_name)
|
35
|
+
|
36
|
+
with = :"#{method_name}_with_intercept"
|
37
|
+
without = :"#{method_name}_without_intercept"
|
38
|
+
@__last_methods_added = [method_name, with, without]
|
39
|
+
define_method with do |*args, &block|
|
40
|
+
call_chain(without, args[0], args[1], &block)
|
41
|
+
end
|
42
|
+
alias_method without, method_name
|
43
|
+
alias_method method_name, with
|
44
|
+
@__last_methods_added = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Properly find all RPC handler methods
|
49
|
+
#
|
50
|
+
def self.rpc_handler_names
|
51
|
+
rpc_descs.keys.map { |n| n.to_s.underscore.to_sym }.uniq
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Mount the service into the server automatically
|
56
|
+
#
|
57
|
+
def self.mount
|
58
|
+
Gruf.services << name.constantize
|
59
|
+
end
|
60
|
+
|
61
|
+
mount
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Happens before a call.
|
66
|
+
#
|
67
|
+
# @param [Symbol] call_signature The method being called
|
68
|
+
# @param [Object] req The request object
|
69
|
+
# @param [GRPC::ActiveCall] call The gRPC active call object
|
70
|
+
#
|
71
|
+
def before_call(call_signature, req, call)
|
72
|
+
authenticate(call_signature, req, call)
|
73
|
+
Gruf::Hooks::Registry.each do |_name, h|
|
74
|
+
h.new(self, Gruf.hook_options).before(call_signature, req, call) if h.instance_methods.include?(:before)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Happens around a call.
|
80
|
+
#
|
81
|
+
# @param [Symbol] call_signature The gRPC method being called
|
82
|
+
# @param [Object] req The request object
|
83
|
+
# @param [GRPC::ActiveCall] call The gRPC active call object
|
84
|
+
#
|
85
|
+
def around_call(call_signature, req, call, &block)
|
86
|
+
around_hooks = []
|
87
|
+
Gruf::Hooks::Registry.each do |_name, h|
|
88
|
+
around_hooks << h.new(self, Gruf.hook_options) if h.instance_methods.include?(:around)
|
89
|
+
end
|
90
|
+
if around_hooks.any?
|
91
|
+
run_around_hook(around_hooks, call_signature, req, call, &block)
|
92
|
+
else
|
93
|
+
yield
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Run all around hooks recursively, starting with the last loaded
|
99
|
+
#
|
100
|
+
# @param [Array<Gruf::Hooks::Base>] hooks The current stack of hooks
|
101
|
+
# @param [Symbol] call_signature The gRPC method being called
|
102
|
+
# @param [Object] req The request object
|
103
|
+
# @param [GRPC::ActiveCall] call The gRPC active call object
|
104
|
+
#
|
105
|
+
def run_around_hook(hooks, call_signature, req, call, &_)
|
106
|
+
h = hooks.pop
|
107
|
+
h.around(call_signature, req, call) do
|
108
|
+
if hooks.any?
|
109
|
+
run_around_hook(hooks, call_signature, req, call) { yield }
|
110
|
+
else
|
111
|
+
yield
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
##
|
117
|
+
# Happens around the entire call chain - before, around, the call itself, and after hooks.
|
118
|
+
#
|
119
|
+
# @param [Symbol] call_signature The gRPC method being called
|
120
|
+
# @param [Object] req The request object
|
121
|
+
# @param [GRPC::ActiveCall] call The gRPC active call object
|
122
|
+
#
|
123
|
+
def outer_around_call(call_signature, req, call, &block)
|
124
|
+
outer_around_hooks = []
|
125
|
+
Gruf::Hooks::Registry.each do |_name, h|
|
126
|
+
outer_around_hooks << h.new(self, Gruf.hook_options) if h.instance_methods.include?(:outer_around)
|
127
|
+
end
|
128
|
+
if outer_around_hooks.any?
|
129
|
+
run_outer_around_hook(outer_around_hooks, call_signature, req, call, &block)
|
130
|
+
else
|
131
|
+
yield
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# Run all outer around hooks recursively, starting with the last loaded
|
137
|
+
#
|
138
|
+
# @param [Array<Gruf::Hooks::Base>] hooks The current stack of hooks
|
139
|
+
# @param [Symbol] call_signature The gRPC method being called
|
140
|
+
# @param [Object] req The request object
|
141
|
+
# @param [GRPC::ActiveCall] call The gRPC active call object
|
142
|
+
#
|
143
|
+
def run_outer_around_hook(hooks, call_signature, req, call, &_)
|
144
|
+
h = hooks.pop
|
145
|
+
h.outer_around(call_signature, req, call) do
|
146
|
+
if hooks.any?
|
147
|
+
run_outer_around_hook(hooks, call_signature, req, call) { yield }
|
148
|
+
else
|
149
|
+
yield
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
##
|
155
|
+
# Happens after a call
|
156
|
+
#
|
157
|
+
# @param [Boolean] success Whether or not the result was successful
|
158
|
+
# @param [Object] response The response object returned from the gRPC call
|
159
|
+
# @param [Symbol] call_signature The method being called
|
160
|
+
# @param [Object] req The request object
|
161
|
+
# @param [GRPC::ActiveCall] call The gRPC active call object
|
162
|
+
# @return [Object] If extending this method or using the after_call_hook, you must return the response object
|
163
|
+
#
|
164
|
+
def after_call(success, response, call_signature, req, call)
|
165
|
+
Gruf::Hooks::Registry.each do |_name, h|
|
166
|
+
h.new(self, Gruf.hook_options).after(success, response, call_signature, req, call) if h.instance_methods.include?(:after)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Authenticate the endpoint caller.
|
172
|
+
#
|
173
|
+
# @param [Symbol] _method The method being called
|
174
|
+
# @param [Object] req The request object
|
175
|
+
# @param [GRPC::ActiveCall] call The gRPC active call object
|
176
|
+
#
|
177
|
+
def authenticate(_method, req, call)
|
178
|
+
fail!(req, call, :unauthenticated) unless Authentication.verify(call)
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Will issue a GRPC BadStatus exception, with a code based on the code passed.
|
183
|
+
#
|
184
|
+
# @param [Object] _req The request object being sent
|
185
|
+
# @param [GRPC::ActiveCall] call The gRPC active call
|
186
|
+
# @param [Symbol] error_code The network error code that maps to gRPC status codes
|
187
|
+
# @param [Symbol] app_code The application-specific code for the error
|
188
|
+
# @param [String] message (Optional) A detail message about the error
|
189
|
+
# @param [Hash] metadata (Optional) Any metadata to inject into the trailing metadata for the response
|
190
|
+
# @return [RPC::Error]
|
191
|
+
#
|
192
|
+
def fail!(_req, call, error_code, app_code = nil, message = '', metadata = {})
|
193
|
+
error.code = error_code.to_sym
|
194
|
+
error.app_code = app_code ? app_code.to_sym : error.code
|
195
|
+
error.message = message.to_s
|
196
|
+
error.metadata = metadata
|
197
|
+
error.fail!(call)
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
##
|
203
|
+
# Encapsulate the call chain to provide before/around/after hooks
|
204
|
+
#
|
205
|
+
# @param [Symbol] original_call_sig The original call signature for the service
|
206
|
+
# @param [Object] req The request object
|
207
|
+
# @param [GRPC::ActiveCall] call The ActiveCall object being executed
|
208
|
+
# @return [Object] The response object
|
209
|
+
#
|
210
|
+
def call_chain(original_call_sig, req, call, &block)
|
211
|
+
outer_around_call(original_call_sig, req, call) do
|
212
|
+
before_call(original_call_sig, req, call)
|
213
|
+
timed = Timer.time do
|
214
|
+
around_call(original_call_sig, req, call) do
|
215
|
+
send(original_call_sig, req, call, &block) # send the actual request to gRPC
|
216
|
+
end
|
217
|
+
end
|
218
|
+
after_call(timed.success?, timed.result, original_call_sig, req, call)
|
219
|
+
|
220
|
+
Gruf::Instrumentation::Registry.each do |_name, h|
|
221
|
+
h.new(self, req, timed.result, timed.time, original_call_sig, call, Gruf.instrumentation_options).call
|
222
|
+
end
|
223
|
+
|
224
|
+
raise timed.result unless timed.success?
|
225
|
+
|
226
|
+
timed.result
|
227
|
+
end
|
228
|
+
rescue => e
|
229
|
+
raise e if e.is_a?(GRPC::BadStatus)
|
230
|
+
set_debug_info(e.message, e.backtrace) if Gruf.backtrace_on_error
|
231
|
+
fail!(req, call, :internal, :unknown, e.message)
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Add a field error to this endpoint
|
236
|
+
#
|
237
|
+
# @param [Symbol] field_name
|
238
|
+
# @param [Symbol] error_code
|
239
|
+
# @param [String] message
|
240
|
+
#
|
241
|
+
def add_field_error(field_name, error_code, message = '')
|
242
|
+
error.add_field_error(field_name, error_code, message)
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# Return true if there are any present field errors
|
247
|
+
#
|
248
|
+
# @return [Boolean]
|
249
|
+
#
|
250
|
+
def has_field_errors?
|
251
|
+
error.field_errors.any?
|
252
|
+
end
|
253
|
+
|
254
|
+
##
|
255
|
+
# Set debugging information on the error payload
|
256
|
+
#
|
257
|
+
# @param [String] detail A string message that represents debugging information
|
258
|
+
# @param [Array<String>] stack_trace An array of strings that contain the backtrace
|
259
|
+
#
|
260
|
+
def set_debug_info(detail, stack_trace = [])
|
261
|
+
error.set_debug_info(detail, stack_trace)
|
262
|
+
end
|
263
|
+
|
264
|
+
##
|
265
|
+
# @return [Gruf::Error]
|
266
|
+
#
|
267
|
+
def error
|
268
|
+
@error ||= Gruf::Error.new
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|