gruf 1.2.7 → 2.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +98 -119
  4. data/bin/gruf +9 -3
  5. data/lib/gruf.rb +4 -4
  6. data/lib/gruf/configuration.rb +11 -20
  7. data/lib/gruf/controllers/base.rb +82 -0
  8. data/lib/gruf/controllers/request.rb +96 -0
  9. data/lib/gruf/controllers/service_binder.rb +86 -0
  10. data/lib/gruf/error.rb +9 -0
  11. data/lib/gruf/errors/helpers.rb +40 -0
  12. data/lib/gruf/{hooks → interceptors}/active_record/connection_reset.rb +4 -10
  13. data/lib/gruf/interceptors/authentication/basic.rb +80 -0
  14. data/lib/gruf/interceptors/base.rb +51 -0
  15. data/lib/gruf/{instrumentation/output_metadata_timer.rb → interceptors/context.rb} +25 -15
  16. data/lib/gruf/interceptors/instrumentation/output_metadata_timer.rb +59 -0
  17. data/lib/gruf/{instrumentation → interceptors/instrumentation}/request_logging/formatters/base.rb +15 -13
  18. data/lib/gruf/{instrumentation → interceptors/instrumentation}/request_logging/formatters/logstash.rb +15 -13
  19. data/lib/gruf/{instrumentation → interceptors/instrumentation}/request_logging/formatters/plain.rb +21 -19
  20. data/lib/gruf/interceptors/instrumentation/request_logging/interceptor.rb +191 -0
  21. data/lib/gruf/interceptors/instrumentation/statsd.rb +80 -0
  22. data/lib/gruf/interceptors/registry.rb +131 -0
  23. data/lib/gruf/{authentication/none.rb → interceptors/server_interceptor.rb} +8 -7
  24. data/lib/gruf/interceptors/timer.rb +79 -0
  25. data/lib/gruf/response.rb +1 -2
  26. data/lib/gruf/server.rb +40 -25
  27. data/lib/gruf/version.rb +1 -1
  28. metadata +19 -20
  29. data/lib/gruf/authentication.rb +0 -65
  30. data/lib/gruf/authentication/base.rb +0 -65
  31. data/lib/gruf/authentication/basic.rb +0 -74
  32. data/lib/gruf/authentication/strategies.rb +0 -107
  33. data/lib/gruf/hooks/base.rb +0 -66
  34. data/lib/gruf/hooks/registry.rb +0 -110
  35. data/lib/gruf/instrumentation/base.rb +0 -114
  36. data/lib/gruf/instrumentation/registry.rb +0 -104
  37. data/lib/gruf/instrumentation/request_context.rb +0 -82
  38. data/lib/gruf/instrumentation/request_logging/hook.rb +0 -185
  39. data/lib/gruf/instrumentation/statsd.rb +0 -80
  40. data/lib/gruf/service.rb +0 -333
@@ -1,104 +0,0 @@
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_relative 'base'
18
- require_relative 'statsd'
19
- require_relative 'output_metadata_timer'
20
- require_relative 'request_logging/hook'
21
-
22
- module Gruf
23
- module Instrumentation
24
- ##
25
- # Registry of all hooks added
26
- #
27
- class Registry
28
- ##
29
- # Error class that represents when a instrumentation strategy does not extend the base class
30
- #
31
- class StrategyDescendantError < StandardError; end
32
-
33
- class << self
34
- ##
35
- # Add an authentication strategy, either through a class or a block
36
- #
37
- # Gruf::Instrumentation::Registry.add(:my_instrumentor, MyInstrumentor)
38
- #
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)
45
- base = Gruf::Instrumentation::Base
46
- strategy ||= Class.new(base)
47
- strategy.class_eval(&block) if block_given?
48
-
49
- raise StrategyDescendantError, "Hooks must descend from #{base}" unless strategy.ancestors.include?(base)
50
-
51
- _registry[name.to_sym] = strategy
52
- end
53
-
54
- ##
55
- # Return a strategy type registry via a hash accessor syntax
56
- #
57
- # @return [Gruf::Instrumentation::Base|NilClass] The requested strategy, if exists
58
- #
59
- def [](name)
60
- _registry[name.to_sym]
61
- end
62
-
63
- ##
64
- # Iterate over each hook in the registry
65
- #
66
- def each
67
- _registry.each do |name, s|
68
- yield name, s
69
- end
70
- end
71
-
72
- ##
73
- # @return [Hash<Class>] Return the registry represented as a Hash object
74
- #
75
- def to_h
76
- _registry
77
- end
78
-
79
- ##
80
- # @return [Boolean] Return true if there are any registered instrumentation strategies
81
- #
82
- def any?
83
- to_h.keys.count > 0
84
- end
85
-
86
- ##
87
- # @return [Hash] Clear all existing strategies from the registry
88
- #
89
- def clear
90
- @_registry = {}
91
- end
92
-
93
- private
94
-
95
- ##
96
- # @return [Hash<Class>] Return the current registry
97
- #
98
- def _registry
99
- @_registry ||= {}
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,82 +0,0 @@
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
- ##
20
- # Represents a request context for a given incoming gRPC request. This represents an injection layer that is used
21
- # to pass to instrumentation strategies safely across thread boundaries.
22
- #
23
- class RequestContext
24
- # @return [Gruf::Service] service The service to instrument
25
- attr_reader :service
26
- # @return [Object] request The protobuf request object
27
- attr_reader :request
28
- # @return [Object] response The protobuf response object
29
- attr_reader :response
30
- # @return [Symbol] call_signature The gRPC method on the service that was called
31
- attr_reader :call_signature
32
- # @return [GRPC::ActiveCall] active_call The gRPC core active call object, which represents marshalled data for
33
- # the call itself
34
- attr_reader :active_call
35
- # @return [Time] execution_time The execution time, in ms, of the request
36
- attr_reader :execution_time
37
-
38
- ##
39
- # Initialize the request context given the request and response
40
- #
41
- def initialize(
42
- service:,
43
- request:,
44
- response:,
45
- call_signature:,
46
- active_call:,
47
- execution_time: 0.00
48
- )
49
- @service = service
50
- @request = request
51
- @response = response
52
- @call_signature = call_signature
53
- @active_call = active_call
54
- @execution_time = execution_time
55
- end
56
-
57
- ##
58
- # @return [Boolean] True if the response is successful
59
- #
60
- def success?
61
- !response.is_a?(GRPC::BadStatus)
62
- end
63
-
64
- ##
65
- # @return [String] Return the response class name
66
- #
67
- def response_class_name
68
- response.class.name
69
- end
70
-
71
- ##
72
- # Return the execution time rounded to a specified precision
73
- #
74
- # @param [Integer] precision The amount of decimal places to round to
75
- # @return [Float] The execution time rounded to the appropriate decimal point
76
- #
77
- def execution_time_rounded(precision: 2)
78
- execution_time.to_f.round(precision)
79
- end
80
- end
81
- end
82
- end
@@ -1,185 +0,0 @@
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 'socket'
18
- require_relative 'formatters/base'
19
- require_relative 'formatters/logstash'
20
- require_relative 'formatters/plain'
21
-
22
- module Gruf
23
- module Instrumentation
24
- module RequestLogging
25
- ##
26
- # Represents an error if the formatter does not extend the base formatter
27
- #
28
- class InvalidFormatterError < StandardError; end
29
-
30
- ##
31
- # Handles Rails-style request logging for gruf services.
32
- #
33
- # This is added by default to gruf servers; if you have `Gruf.use_default_hooks = false`, you can add it back
34
- # manually by doing:
35
- #
36
- # Gruf::Instrumentation::Registry.add(:request_logging, Gruf::Instrumentation::RequestLogging::Hook)
37
- #
38
- class Hook < ::Gruf::Instrumentation::Base
39
-
40
- ###
41
- # Log the request, sending it to the appropriate formatter
42
- #
43
- # @param [Gruf::Instrumentation::RequestContext] rc The current request context for the call
44
- # @return [String]
45
- #
46
- def call(rc)
47
- if rc.success?
48
- type = :info
49
- status_name = 'GRPC::Ok'
50
- else
51
- type = :error
52
- status_name = rc.response_class_name
53
- end
54
-
55
- payload = {}
56
- payload[:params] = sanitize(rc.request.to_h) if options.fetch(:log_parameters, false)
57
- payload[:message] = message(rc)
58
- payload[:service] = service_key
59
- payload[:method] = rc.call_signature
60
- payload[:action] = rc.call_signature
61
- payload[:grpc_status] = status_name
62
- payload[:duration] = rc.execution_time_rounded
63
- payload[:status] = status(rc.response, rc.success?)
64
- payload[:thread_id] = Thread.current.object_id
65
- payload[:time] = Time.now.to_s
66
- payload[:host] = Socket.gethostname
67
-
68
- ::Gruf.logger.send(type, formatter.format(payload))
69
- end
70
-
71
- private
72
-
73
- ##
74
- # Return an appropriate log message dependent on the status
75
- #
76
- # @param [RequestContext] rc The current request context
77
- # @return [String] The appropriate message body
78
- #
79
- def message(rc)
80
- if rc.success?
81
- "[GRPC::Ok] (#{service_key}.#{rc.call_signature})"
82
- else
83
- "[#{rc.response_class_name}] (#{service_key}.#{rc.call_signature}) #{rc.response.message}"
84
- end
85
- end
86
-
87
- ##
88
- # Return the proper status code for the response
89
- #
90
- # @param [Object] response The response object
91
- # @param [Boolean] successful If the response was successful
92
- # @return [Boolean] The proper status code
93
- #
94
- def status(response, successful)
95
- successful ? GRPC::Core::StatusCodes::OK : response.code
96
- end
97
-
98
- ##
99
- # Determine the appropriate formatter for the request logging
100
- #
101
- # @return [Gruf::Instrumentation::RequestLogging::Formatters::Base]
102
- #
103
- def formatter
104
- unless @formatter
105
- fmt = options.fetch(:formatter, :plain)
106
- @formatter = case fmt
107
- when Symbol
108
- klass = "Gruf::Instrumentation::RequestLogging::Formatters::#{fmt.to_s.capitalize}"
109
- fmt = klass.constantize.new
110
- when Class
111
- fmt = fmt.new
112
- else
113
- fmt
114
- end
115
- raise Gruf::Instrumentation::RequestLogging::InvalidFormatterError unless fmt.is_a?(Gruf::Instrumentation::RequestLogging::Formatters::Base)
116
- end
117
- @formatter
118
- end
119
-
120
- ##
121
- # Redact any blacklisted params and return an updated hash
122
- #
123
- # @param [Hash] params The hash of parameters to sanitize
124
- # @return [Hash] The sanitized params in hash form
125
- #
126
- def sanitize(params = {})
127
- blacklists = options.fetch(:blacklist, []).map(&:to_s)
128
- redacted_string = options.fetch(:redacted_string, 'REDACTED')
129
- blacklists.each do |blacklist|
130
- parts = blacklist.split('.').map(&:to_sym)
131
- redact!(parts, 0, params, redacted_string)
132
- end
133
- params
134
- end
135
-
136
- ##
137
- # Fetch the options for this hook
138
- #
139
- # @return [Hash] Return a hash of options for this hook
140
- #
141
- def options
142
- super().fetch(:request_logging, {})
143
- end
144
-
145
- ##
146
- # Helper method to recursively redact based on the black list
147
- #
148
- # @param [Array] parts The blacklist. ex. 'data.schema' -> [:data, :schema]
149
- # @param [Integer] idx The current index of the blacklist
150
- # @param [Hash] params The hash of parameters to sanitize
151
- # @param [String] redacted_string The custom redact string
152
- #
153
- def redact!(parts = [], idx = 0, params = {}, redacted_string = 'REDACTED')
154
- return unless parts.is_a?(Array) && params.is_a?(Hash)
155
- return if idx >= parts.size || !params.key?(parts[idx])
156
- if idx == parts.size - 1
157
- if params[parts[idx]].is_a? Hash
158
- hash_deep_redact!(params[parts[idx]], redacted_string)
159
- else
160
- params[parts[idx]] = redacted_string
161
- end
162
- return
163
- end
164
- redact!(parts, idx + 1, params[parts[idx]], redacted_string)
165
- end
166
-
167
- ##
168
- # Helper method to recursively redact the value of all hash keys
169
- #
170
- # @param [Hash] hash Part of the hash of parameters to sanitize
171
- # @param [String] redacted_string The custom redact string
172
- #
173
- def hash_deep_redact!(hash, redacted_string)
174
- hash.keys.each do |key|
175
- if hash[key].is_a? Hash
176
- hash_deep_redact!(hash[key], redacted_string)
177
- else
178
- hash[key] = redacted_string
179
- end
180
- end
181
- end
182
- end
183
- end
184
- end
185
- end
@@ -1,80 +0,0 @@
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
- ##
20
- # Adds increment and timing stats to gRPC routes, pushing data to StatsD
21
- #
22
- class Statsd < Gruf::Instrumentation::Base
23
- ##
24
- # Push data to StatsD, only doing so if a client is set
25
- #
26
- # @param [Gruf::Instrumentation::RequestContext] rc The current request context for the call
27
- #
28
- def call(rc)
29
- if client
30
- rk = route_key(rc.call_signature)
31
- client.increment(rk)
32
- client.increment("#{rk}.#{postfix(rc.success?)}")
33
- client.timing(rk, rc.execution_time)
34
- else
35
- Gruf.logger.error 'Statsd module loaded, but no client configured!'
36
- end
37
- end
38
-
39
- private
40
-
41
- ##
42
- # @param [Boolean] successful Whether or not the request was successful
43
- # @return [String] The appropriate postfix for the key dependent on response status
44
- #
45
- def postfix(successful)
46
- successful ? 'success' : 'failure'
47
- end
48
-
49
- ##
50
- # @param [Symbol] call_signature The method call signature for the handler
51
- # @return [String] Return a composed route key that is used in the statsd metric
52
- #
53
- def route_key(call_signature)
54
- "#{key_prefix}#{method_key(call_signature)}"
55
- end
56
-
57
- ##
58
- # @return [String] Return the sanitized key prefix for the statsd metric key
59
- #
60
- def key_prefix
61
- prefix = options.fetch(:prefix, '').to_s
62
- prefix.empty? ? '' : "#{prefix}."
63
- end
64
-
65
- ##
66
- # @return [::Statsd] Return the given StatsD client
67
- #
68
- def client
69
- @client ||= options.fetch(:client, nil)
70
- end
71
-
72
- ##
73
- # @return [Hash] Return a hash of options for this hook
74
- #
75
- def options
76
- super().fetch(:statsd, {})
77
- end
78
- end
79
- end
80
- end