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/hooks/base.rb
CHANGED
@@ -17,16 +17,21 @@
|
|
17
17
|
module Gruf
|
18
18
|
module Hooks
|
19
19
|
##
|
20
|
-
# Base class for a hook. Define before, around, or after methods to utilize functionality.
|
20
|
+
# Base class for a hook. Define before, around, outer_around, or after methods to utilize functionality.
|
21
21
|
#
|
22
22
|
class Base
|
23
23
|
include Gruf::Loggable
|
24
24
|
|
25
|
-
|
25
|
+
# @return [Gruf::Service] service The service to perform the hook against
|
26
|
+
attr_reader :service
|
27
|
+
# @return [Hash] options Options to use for the hook
|
28
|
+
attr_reader :options
|
26
29
|
|
27
30
|
##
|
28
|
-
#
|
29
|
-
#
|
31
|
+
# Initialize the hook and run setup
|
32
|
+
#
|
33
|
+
# @param [Gruf::Service] service The gruf service that the hook will perform against
|
34
|
+
# @param [Hash] options (Optional) A hash of options for this hook
|
30
35
|
#
|
31
36
|
def initialize(service, options = {})
|
32
37
|
@service = service
|
@@ -36,9 +41,26 @@ module Gruf
|
|
36
41
|
|
37
42
|
##
|
38
43
|
# Method that can be used to setup the hook prior to running it
|
44
|
+
#
|
39
45
|
def setup
|
40
46
|
# noop
|
41
47
|
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# @return [String] Returns the service name as a translated name separated by periods
|
51
|
+
#
|
52
|
+
def service_key
|
53
|
+
service.class.name.underscore.tr('/', '.')
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Parse the method signature into a service.method name format
|
58
|
+
#
|
59
|
+
# @return [String] The parsed service method name
|
60
|
+
#
|
61
|
+
def method_key(call_signature)
|
62
|
+
"#{service_key}.#{call_signature.to_s.gsub('_without_intercept', '')}"
|
63
|
+
end
|
42
64
|
end
|
43
65
|
end
|
44
66
|
end
|
data/lib/gruf/hooks/registry.rb
CHANGED
@@ -23,15 +23,19 @@ module Gruf
|
|
23
23
|
# Registry of all hooks added
|
24
24
|
#
|
25
25
|
class Registry
|
26
|
+
##
|
27
|
+
# Error class that represents when a gruf hook does not extend the base class
|
28
|
+
#
|
26
29
|
class HookDescendantError < StandardError; end
|
27
30
|
|
28
31
|
class << self
|
29
32
|
##
|
30
33
|
# Add an authentication strategy, either through a class or a block
|
31
34
|
#
|
32
|
-
# @param [String] name
|
33
|
-
# @param [Gruf::Hooks::Base|NilClass] hook
|
34
|
-
#
|
35
|
+
# @param [String] name The name to represent the hook as
|
36
|
+
# @param [Gruf::Hooks::Base|NilClass] hook The strategy class to add. If nil, will expect
|
37
|
+
# a block that can be built as a hook instead
|
38
|
+
# @return [Class] The hook that was added
|
35
39
|
#
|
36
40
|
def add(name, hook = nil, &block)
|
37
41
|
base = Gruf::Hooks::Base
|
@@ -47,7 +51,9 @@ module Gruf
|
|
47
51
|
end
|
48
52
|
|
49
53
|
##
|
50
|
-
# Return a
|
54
|
+
# Return a hook via a hash accessor syntax
|
55
|
+
#
|
56
|
+
# @return [Gruf::Instrumentation::Base|NilClass] The requested hook, if exists
|
51
57
|
#
|
52
58
|
def [](name)
|
53
59
|
_registry[name.to_sym]
|
@@ -63,28 +69,28 @@ module Gruf
|
|
63
69
|
end
|
64
70
|
|
65
71
|
##
|
66
|
-
# @return [Hash<Class>]
|
72
|
+
# @return [Hash<Class>] Return the registry represented as a Hash object
|
67
73
|
#
|
68
74
|
def to_h
|
69
75
|
_registry
|
70
76
|
end
|
71
77
|
|
72
78
|
##
|
73
|
-
# @return [Boolean]
|
79
|
+
# @return [Boolean] Return true if there are any registered hooks
|
74
80
|
#
|
75
81
|
def any?
|
76
82
|
count > 0
|
77
83
|
end
|
78
84
|
|
79
85
|
##
|
80
|
-
# @return [Integer]
|
86
|
+
# @return [Integer] Return the number of registered hooks
|
81
87
|
#
|
82
88
|
def count
|
83
89
|
to_h.keys.count
|
84
90
|
end
|
85
91
|
|
86
92
|
##
|
87
|
-
# @return [Hash]
|
93
|
+
# @return [Hash] Clear all existing hooks from the registry
|
88
94
|
#
|
89
95
|
def clear
|
90
96
|
@_registry = {}
|
@@ -93,7 +99,7 @@ module Gruf
|
|
93
99
|
private
|
94
100
|
|
95
101
|
##
|
96
|
-
# @return [Hash<Class>]
|
102
|
+
# @return [Hash<Class>] Return the current registry
|
97
103
|
#
|
98
104
|
def _registry
|
99
105
|
@_registry ||= {}
|
@@ -14,23 +14,27 @@
|
|
14
14
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
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
|
+
require_relative 'request_context'
|
18
|
+
|
17
19
|
module Gruf
|
18
20
|
module Instrumentation
|
19
21
|
##
|
20
|
-
# Base class for
|
22
|
+
# Base class for an instrumentation strategy. Define a call method to utilize functionality.
|
21
23
|
#
|
22
24
|
class Base
|
23
25
|
include Gruf::Loggable
|
24
26
|
|
25
|
-
|
27
|
+
# @return [Gruf::Service] service The service to instrument
|
28
|
+
attr_reader :service
|
29
|
+
# @return [Hash] options Options to use when instrumenting the call
|
30
|
+
attr_reader :options
|
26
31
|
|
27
|
-
|
32
|
+
##
|
33
|
+
# @param [Gruf::Service] service The service to instrument
|
34
|
+
# @param [Hash] options (Optional) Options to use when instrumenting the call
|
35
|
+
#
|
36
|
+
def initialize(service, options = {})
|
28
37
|
@service = service
|
29
|
-
@request = request
|
30
|
-
@response = response
|
31
|
-
@execution_time = execution_time
|
32
|
-
@call_signature = call_signature.to_s.gsub('_without_intercept', '').to_sym
|
33
|
-
@active_call = active_call
|
34
38
|
@options = options
|
35
39
|
setup
|
36
40
|
end
|
@@ -43,17 +47,68 @@ module Gruf
|
|
43
47
|
end
|
44
48
|
|
45
49
|
##
|
46
|
-
#
|
50
|
+
# Was this call a success? If a response is a GRPC::BadStatus object, we assume that it was unsuccessful
|
47
51
|
#
|
48
|
-
|
52
|
+
# @param [Object] response The gRPC response object
|
53
|
+
# @return [Boolean] True if was a successful call
|
54
|
+
#
|
55
|
+
def success?(response)
|
49
56
|
!response.is_a?(GRPC::BadStatus)
|
50
57
|
end
|
51
58
|
|
52
59
|
##
|
60
|
+
# Abstract method that is required for implementing an instrumentation strategy.
|
61
|
+
#
|
62
|
+
# @abstract
|
63
|
+
# @param [Gruf::Instrumentation::RequestContext] _rc The current request context for the call
|
53
64
|
#
|
54
|
-
def call
|
65
|
+
def call(_rc)
|
55
66
|
raise NotImplementedError
|
56
67
|
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Hook into the outer_around call to time the request, and pass that to the call method
|
71
|
+
#
|
72
|
+
# @param [Symbol] call_signature The method being called
|
73
|
+
# @param [Object] request The request object
|
74
|
+
# @param [GRPC::ActiveCall] active_call The gRPC active call object
|
75
|
+
# @param [Proc] &_block The execution block for the call
|
76
|
+
# @return [Object] result The result of the block that was called
|
77
|
+
#
|
78
|
+
def outer_around(call_signature, request, active_call, &_block)
|
79
|
+
timed = Timer.time do
|
80
|
+
yield
|
81
|
+
end
|
82
|
+
rc = RequestContext.new(
|
83
|
+
service: service,
|
84
|
+
request: request,
|
85
|
+
response: timed.result,
|
86
|
+
execution_time: timed.time,
|
87
|
+
call_signature: call_signature,
|
88
|
+
active_call: active_call
|
89
|
+
)
|
90
|
+
call(rc)
|
91
|
+
raise rc.response unless rc.success?
|
92
|
+
rc.response
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# @return [String] Returns the service name as a translated name separated by periods
|
97
|
+
#
|
98
|
+
def service_key
|
99
|
+
service.class.name.underscore.tr('/', '.')
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Parse the method signature into a service.method name format
|
104
|
+
#
|
105
|
+
# @param [Symbol] call_signature The method call signature
|
106
|
+
# @param [String] delimiter The delimiter to separate service and method keys
|
107
|
+
# @return [String] The parsed service method name
|
108
|
+
#
|
109
|
+
def method_key(call_signature, delimiter: '.')
|
110
|
+
"#{service_key}#{delimiter}#{call_signature}"
|
111
|
+
end
|
57
112
|
end
|
58
113
|
end
|
59
114
|
end
|
@@ -23,14 +23,17 @@ module Gruf
|
|
23
23
|
##
|
24
24
|
# Handle the instrumented response. Note: this will only instrument timings of _successful_ responses.
|
25
25
|
#
|
26
|
-
|
27
|
-
|
26
|
+
# @param [Gruf::Instrumentation::RequestContext] rc The current request context for the call
|
27
|
+
# @return [Hash] The resulting output metadata with the timer attached
|
28
|
+
#
|
29
|
+
def call(rc)
|
30
|
+
rc.active_call.output_metadata.update(metadata_key => rc.execution_time.to_s)
|
28
31
|
end
|
29
32
|
|
30
33
|
private
|
31
34
|
|
32
35
|
##
|
33
|
-
# @return [Symbol]
|
36
|
+
# @return [Symbol] The metadata key that the time result should be set to
|
34
37
|
#
|
35
38
|
def metadata_key
|
36
39
|
options.fetch(:output_metadata_timer, {}).fetch(:metadata_key, :timer).to_sym
|
@@ -17,6 +17,7 @@
|
|
17
17
|
require_relative 'base'
|
18
18
|
require_relative 'statsd'
|
19
19
|
require_relative 'output_metadata_timer'
|
20
|
+
require_relative 'request_logging/hook'
|
20
21
|
|
21
22
|
module Gruf
|
22
23
|
module Instrumentation
|
@@ -24,29 +25,37 @@ module Gruf
|
|
24
25
|
# Registry of all hooks added
|
25
26
|
#
|
26
27
|
class Registry
|
27
|
-
|
28
|
+
##
|
29
|
+
# Error class that represents when a instrumentation strategy does not extend the base class
|
30
|
+
#
|
31
|
+
class StrategyDescendantError < StandardError; end
|
28
32
|
|
29
33
|
class << self
|
30
34
|
##
|
31
35
|
# Add an authentication strategy, either through a class or a block
|
32
36
|
#
|
33
|
-
#
|
34
|
-
# @param [Gruf::Hooks::Base|NilClass] hook
|
35
|
-
# @return [Class]
|
37
|
+
# Gruf::Instrumentation::Registry.add(:my_instrumentor, MyInstrumentor)
|
36
38
|
#
|
37
|
-
|
39
|
+
# @param [String] name The name to represent the strategy as
|
40
|
+
# @param [Gruf::Hooks::Base|NilClass] strategy (Optional) The strategy class to add. If nil, will expect
|
41
|
+
# a block that can be built as a strategy instead
|
42
|
+
# @return [Class] The strategy that was added
|
43
|
+
#
|
44
|
+
def add(name, strategy = nil, &block)
|
38
45
|
base = Gruf::Instrumentation::Base
|
39
|
-
|
40
|
-
|
46
|
+
strategy ||= Class.new(base)
|
47
|
+
strategy.class_eval(&block) if block_given?
|
41
48
|
|
42
|
-
raise
|
49
|
+
raise StrategyDescendantError, "Hooks must descend from #{base}" unless strategy.ancestors.include?(base)
|
43
50
|
|
44
|
-
_registry[name.to_sym] =
|
51
|
+
_registry[name.to_sym] = strategy
|
45
52
|
end
|
46
53
|
|
47
54
|
##
|
48
55
|
# Return a strategy type registry via a hash accessor syntax
|
49
56
|
#
|
57
|
+
# @return [Gruf::Instrumentation::Base|NilClass] The requested strategy, if exists
|
58
|
+
#
|
50
59
|
def [](name)
|
51
60
|
_registry[name.to_sym]
|
52
61
|
end
|
@@ -61,21 +70,21 @@ module Gruf
|
|
61
70
|
end
|
62
71
|
|
63
72
|
##
|
64
|
-
# @return [Hash<Class>]
|
73
|
+
# @return [Hash<Class>] Return the registry represented as a Hash object
|
65
74
|
#
|
66
75
|
def to_h
|
67
76
|
_registry
|
68
77
|
end
|
69
78
|
|
70
79
|
##
|
71
|
-
# @return [Boolean]
|
80
|
+
# @return [Boolean] Return true if there are any registered instrumentation strategies
|
72
81
|
#
|
73
82
|
def any?
|
74
83
|
to_h.keys.count > 0
|
75
84
|
end
|
76
85
|
|
77
86
|
##
|
78
|
-
# @return [Hash]
|
87
|
+
# @return [Hash] Clear all existing strategies from the registry
|
79
88
|
#
|
80
89
|
def clear
|
81
90
|
@_registry = {}
|
@@ -84,7 +93,7 @@ module Gruf
|
|
84
93
|
private
|
85
94
|
|
86
95
|
##
|
87
|
-
# @return [Hash<Class>]
|
96
|
+
# @return [Hash<Class>] Return the current registry
|
88
97
|
#
|
89
98
|
def _registry
|
90
99
|
@_registry ||= {}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Gruf
|
2
|
+
module Instrumentation
|
3
|
+
##
|
4
|
+
# Represents a request context for a given incoming gRPC request. This represents an injection layer that is used
|
5
|
+
# to pass to instrumentation strategies safely across thread boundaries.
|
6
|
+
#
|
7
|
+
class RequestContext
|
8
|
+
# @return [Gruf::Service] service The service to instrument
|
9
|
+
attr_reader :service
|
10
|
+
# @return [Object] request The protobuf request object
|
11
|
+
attr_reader :request
|
12
|
+
# @return [Object] response The protobuf response object
|
13
|
+
attr_reader :response
|
14
|
+
# @return [Symbol] call_signature The gRPC method on the service that was called
|
15
|
+
attr_reader :call_signature
|
16
|
+
# @return [GRPC::ActiveCall] active_call The gRPC core active call object, which represents marshalled data for
|
17
|
+
# the call itself
|
18
|
+
attr_reader :active_call
|
19
|
+
# @return [Time] execution_time The execution time, in ms, of the request
|
20
|
+
attr_reader :execution_time
|
21
|
+
|
22
|
+
##
|
23
|
+
# Initialize the request context given the request and response
|
24
|
+
#
|
25
|
+
def initialize(
|
26
|
+
service:,
|
27
|
+
request:,
|
28
|
+
response:,
|
29
|
+
call_signature:,
|
30
|
+
active_call:,
|
31
|
+
execution_time: 0.00
|
32
|
+
)
|
33
|
+
@service = service
|
34
|
+
@request = request
|
35
|
+
@response = response
|
36
|
+
@call_signature = call_signature
|
37
|
+
@active_call = active_call
|
38
|
+
@execution_time = execution_time
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# @return [Boolean] True if the response is successful
|
43
|
+
#
|
44
|
+
def success?
|
45
|
+
!response.is_a?(GRPC::BadStatus)
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# @return [String] Return the response class name
|
50
|
+
#
|
51
|
+
def response_class_name
|
52
|
+
response.class.name
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Return the execution time rounded to a specified precision
|
57
|
+
#
|
58
|
+
# @param [Integer] precision The amount of decimal places to round to
|
59
|
+
# @return [Float] The execution time rounded to the appropriate decimal point
|
60
|
+
#
|
61
|
+
def execution_time_rounded(precision: 2)
|
62
|
+
execution_time.to_f.round(precision)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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 Instrumentation
|
19
|
+
module RequestLogging
|
20
|
+
module Formatters
|
21
|
+
##
|
22
|
+
# Base class for request log formatting
|
23
|
+
#
|
24
|
+
class Base
|
25
|
+
##
|
26
|
+
# Format the parameters into a loggable string. Must be implemented in every derivative class
|
27
|
+
#
|
28
|
+
# @param [Hash] _payload The incoming request payload
|
29
|
+
# @return [String] The formatted string
|
30
|
+
#
|
31
|
+
def format(_payload)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|