dhc 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.
Files changed (185) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rubocop.yml +27 -0
  3. data/.github/workflows/test.yml +27 -0
  4. data/.gitignore +37 -0
  5. data/.rubocop.yml +105 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +674 -0
  9. data/README.md +999 -0
  10. data/Rakefile +25 -0
  11. data/dhc.gemspec +40 -0
  12. data/lib/core_ext/hash/deep_transform_values.rb +48 -0
  13. data/lib/dhc.rb +77 -0
  14. data/lib/dhc/concerns/dhc/basic_methods_concern.rb +42 -0
  15. data/lib/dhc/concerns/dhc/configuration_concern.rb +20 -0
  16. data/lib/dhc/concerns/dhc/fix_invalid_encoding_concern.rb +42 -0
  17. data/lib/dhc/concerns/dhc/formats_concern.rb +25 -0
  18. data/lib/dhc/concerns/dhc/request/user_agent_concern.rb +25 -0
  19. data/lib/dhc/config.rb +47 -0
  20. data/lib/dhc/endpoint.rb +119 -0
  21. data/lib/dhc/error.rb +82 -0
  22. data/lib/dhc/errors/client_error.rb +73 -0
  23. data/lib/dhc/errors/parser_error.rb +4 -0
  24. data/lib/dhc/errors/server_error.rb +28 -0
  25. data/lib/dhc/errors/timeout.rb +4 -0
  26. data/lib/dhc/errors/unknown_error.rb +4 -0
  27. data/lib/dhc/format.rb +18 -0
  28. data/lib/dhc/formats.rb +8 -0
  29. data/lib/dhc/formats/form.rb +45 -0
  30. data/lib/dhc/formats/json.rb +55 -0
  31. data/lib/dhc/formats/multipart.rb +45 -0
  32. data/lib/dhc/formats/plain.rb +42 -0
  33. data/lib/dhc/interceptor.rb +36 -0
  34. data/lib/dhc/interceptors.rb +26 -0
  35. data/lib/dhc/interceptors/auth.rb +94 -0
  36. data/lib/dhc/interceptors/caching.rb +148 -0
  37. data/lib/dhc/interceptors/default_timeout.rb +16 -0
  38. data/lib/dhc/interceptors/logging.rb +37 -0
  39. data/lib/dhc/interceptors/monitoring.rb +92 -0
  40. data/lib/dhc/interceptors/prometheus.rb +51 -0
  41. data/lib/dhc/interceptors/retry.rb +41 -0
  42. data/lib/dhc/interceptors/rollbar.rb +36 -0
  43. data/lib/dhc/interceptors/throttle.rb +86 -0
  44. data/lib/dhc/interceptors/zipkin.rb +110 -0
  45. data/lib/dhc/railtie.rb +9 -0
  46. data/lib/dhc/request.rb +161 -0
  47. data/lib/dhc/response.rb +60 -0
  48. data/lib/dhc/response/data.rb +28 -0
  49. data/lib/dhc/response/data/base.rb +18 -0
  50. data/lib/dhc/response/data/collection.rb +16 -0
  51. data/lib/dhc/response/data/item.rb +29 -0
  52. data/lib/dhc/rspec.rb +11 -0
  53. data/lib/dhc/test/cache_helper.rb +3 -0
  54. data/lib/dhc/version.rb +5 -0
  55. data/script/ci/build.sh +19 -0
  56. data/spec/basic_methods/delete_spec.rb +34 -0
  57. data/spec/basic_methods/get_spec.rb +49 -0
  58. data/spec/basic_methods/post_spec.rb +42 -0
  59. data/spec/basic_methods/put_spec.rb +48 -0
  60. data/spec/basic_methods/request_spec.rb +19 -0
  61. data/spec/basic_methods/request_without_rails_spec.rb +29 -0
  62. data/spec/config/endpoints_spec.rb +63 -0
  63. data/spec/config/placeholders_spec.rb +32 -0
  64. data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
  65. data/spec/dummy/README.rdoc +28 -0
  66. data/spec/dummy/Rakefile +8 -0
  67. data/spec/dummy/app/assets/config/manifest.js +3 -0
  68. data/spec/dummy/app/assets/images/.keep +0 -0
  69. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  70. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  71. data/spec/dummy/app/controllers/application_controller.rb +7 -0
  72. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  73. data/spec/dummy/app/helpers/application_helper.rb +4 -0
  74. data/spec/dummy/app/mailers/.keep +0 -0
  75. data/spec/dummy/app/models/.keep +0 -0
  76. data/spec/dummy/app/models/concerns/.keep +0 -0
  77. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  78. data/spec/dummy/bin/bundle +5 -0
  79. data/spec/dummy/bin/rails +6 -0
  80. data/spec/dummy/bin/rake +6 -0
  81. data/spec/dummy/config.ru +6 -0
  82. data/spec/dummy/config/application.rb +16 -0
  83. data/spec/dummy/config/boot.rb +7 -0
  84. data/spec/dummy/config/environment.rb +7 -0
  85. data/spec/dummy/config/environments/development.rb +36 -0
  86. data/spec/dummy/config/environments/production.rb +77 -0
  87. data/spec/dummy/config/environments/test.rb +41 -0
  88. data/spec/dummy/config/initializers/assets.rb +10 -0
  89. data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
  90. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  91. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
  92. data/spec/dummy/config/initializers/inflections.rb +18 -0
  93. data/spec/dummy/config/initializers/mime_types.rb +6 -0
  94. data/spec/dummy/config/initializers/session_store.rb +5 -0
  95. data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
  96. data/spec/dummy/config/locales/en.yml +23 -0
  97. data/spec/dummy/config/routes.rb +58 -0
  98. data/spec/dummy/config/secrets.yml +22 -0
  99. data/spec/dummy/lib/assets/.keep +0 -0
  100. data/spec/dummy/log/.keep +0 -0
  101. data/spec/dummy/public/404.html +67 -0
  102. data/spec/dummy/public/422.html +67 -0
  103. data/spec/dummy/public/500.html +66 -0
  104. data/spec/dummy/public/favicon.ico +0 -0
  105. data/spec/endpoint/compile_spec.rb +35 -0
  106. data/spec/endpoint/match_spec.rb +41 -0
  107. data/spec/endpoint/placeholders_spec.rb +30 -0
  108. data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
  109. data/spec/endpoint/values_as_params_spec.rb +31 -0
  110. data/spec/error/dup_spec.rb +12 -0
  111. data/spec/error/find_spec.rb +57 -0
  112. data/spec/error/response_spec.rb +17 -0
  113. data/spec/error/timeout_spec.rb +14 -0
  114. data/spec/error/to_s_spec.rb +85 -0
  115. data/spec/formats/form_spec.rb +27 -0
  116. data/spec/formats/json_spec.rb +66 -0
  117. data/spec/formats/multipart_spec.rb +26 -0
  118. data/spec/formats/plain_spec.rb +29 -0
  119. data/spec/interceptors/after_request_spec.rb +20 -0
  120. data/spec/interceptors/after_response_spec.rb +39 -0
  121. data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
  122. data/spec/interceptors/auth/bearer_spec.rb +19 -0
  123. data/spec/interceptors/auth/body_spec.rb +36 -0
  124. data/spec/interceptors/auth/long_basic_auth_credentials_spec.rb +17 -0
  125. data/spec/interceptors/auth/no_instance_var_for_options_spec.rb +27 -0
  126. data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
  127. data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
  128. data/spec/interceptors/before_request_spec.rb +21 -0
  129. data/spec/interceptors/before_response_spec.rb +20 -0
  130. data/spec/interceptors/caching/hydra_spec.rb +26 -0
  131. data/spec/interceptors/caching/main_spec.rb +73 -0
  132. data/spec/interceptors/caching/methods_spec.rb +42 -0
  133. data/spec/interceptors/caching/multilevel_cache_spec.rb +139 -0
  134. data/spec/interceptors/caching/options_spec.rb +78 -0
  135. data/spec/interceptors/caching/parameters_spec.rb +24 -0
  136. data/spec/interceptors/caching/response_status_spec.rb +29 -0
  137. data/spec/interceptors/caching/to_cache_spec.rb +16 -0
  138. data/spec/interceptors/default_interceptors_spec.rb +15 -0
  139. data/spec/interceptors/default_timeout/main_spec.rb +34 -0
  140. data/spec/interceptors/define_spec.rb +30 -0
  141. data/spec/interceptors/dup_spec.rb +19 -0
  142. data/spec/interceptors/logging/main_spec.rb +37 -0
  143. data/spec/interceptors/monitoring/caching_spec.rb +66 -0
  144. data/spec/interceptors/monitoring/main_spec.rb +97 -0
  145. data/spec/interceptors/prometheus_spec.rb +54 -0
  146. data/spec/interceptors/response_competition_spec.rb +39 -0
  147. data/spec/interceptors/retry/main_spec.rb +73 -0
  148. data/spec/interceptors/return_response_spec.rb +38 -0
  149. data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
  150. data/spec/interceptors/rollbar/main_spec.rb +57 -0
  151. data/spec/interceptors/throttle/main_spec.rb +236 -0
  152. data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
  153. data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
  154. data/spec/rails_helper.rb +6 -0
  155. data/spec/request/body_spec.rb +39 -0
  156. data/spec/request/encoding_spec.rb +38 -0
  157. data/spec/request/error_handling_spec.rb +88 -0
  158. data/spec/request/headers_spec.rb +12 -0
  159. data/spec/request/ignore_errors_spec.rb +73 -0
  160. data/spec/request/option_dup_spec.rb +13 -0
  161. data/spec/request/parallel_requests_spec.rb +59 -0
  162. data/spec/request/params_encoding_spec.rb +26 -0
  163. data/spec/request/request_without_rails_spec.rb +15 -0
  164. data/spec/request/url_patterns_spec.rb +54 -0
  165. data/spec/request/user_agent_spec.rb +26 -0
  166. data/spec/request/user_agent_without_rails_spec.rb +27 -0
  167. data/spec/response/body_spec.rb +16 -0
  168. data/spec/response/code_spec.rb +16 -0
  169. data/spec/response/data_accessor_spec.rb +29 -0
  170. data/spec/response/data_spec.rb +85 -0
  171. data/spec/response/effective_url_spec.rb +16 -0
  172. data/spec/response/headers_spec.rb +18 -0
  173. data/spec/response/options_spec.rb +18 -0
  174. data/spec/response/success_spec.rb +13 -0
  175. data/spec/response/time_spec.rb +21 -0
  176. data/spec/spec_helper.rb +9 -0
  177. data/spec/support/fixtures/json/feedback.json +11 -0
  178. data/spec/support/fixtures/json/feedbacks.json +164 -0
  179. data/spec/support/fixtures/json/localina_content_ad.json +23 -0
  180. data/spec/support/load_json.rb +5 -0
  181. data/spec/support/reset_config.rb +8 -0
  182. data/spec/support/zipkin_mock.rb +114 -0
  183. data/spec/timeouts/no_signal_spec.rb +13 -0
  184. data/spec/timeouts/timings_spec.rb +55 -0
  185. metadata +527 -0
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Handles interceptions during the lifecycle of a request
4
+ # Represents all active interceptors for a request/response.
5
+ class DHC::Interceptors
6
+
7
+ attr_accessor :all
8
+
9
+ # Intitalizes and determines if global or local interceptors are used
10
+ def initialize(request)
11
+ self.all = (request.options[:interceptors] || DHC.config.interceptors).map do |interceptor|
12
+ interceptor.new(request)
13
+ end
14
+ end
15
+
16
+ # Forwards messages to interceptors and handles provided responses.
17
+ def intercept(name)
18
+ all.each do |interceptor|
19
+ result = interceptor.send(name)
20
+ if result.is_a? DHC::Response
21
+ raise 'Response already set from another interceptor' if @response
22
+ @response = interceptor.request.response = result
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::Auth < DHC::Interceptor
4
+ include ActiveSupport::Configurable
5
+ config_accessor :refresh_client_token
6
+
7
+ def before_raw_request
8
+ body_authentication! if auth_options[:body]
9
+ end
10
+
11
+ def before_request
12
+ bearer_authentication! if auth_options[:bearer]
13
+ basic_authentication! if auth_options[:basic]
14
+ end
15
+
16
+ def after_response
17
+ return unless configuration_correct?
18
+ return unless reauthenticate?
19
+ reauthenticate!
20
+ end
21
+
22
+ private
23
+
24
+ def body_authentication!
25
+ auth = auth_options[:body]
26
+ request.options[:body] = (request.options[:body] || {}).merge(auth)
27
+ end
28
+
29
+ def basic_authentication!
30
+ auth = auth_options[:basic]
31
+ credentials = "#{auth[:username]}:#{auth[:password]}"
32
+ set_authorization_header("Basic #{Base64.strict_encode64(credentials).chomp}")
33
+ end
34
+
35
+ def bearer_authentication!
36
+ token = auth_options[:bearer]
37
+ token = token.call if token.is_a?(Proc)
38
+ set_bearer_authorization_header(token)
39
+ end
40
+
41
+ def set_authorization_header(value)
42
+ request.headers['Authorization'] = value
43
+ end
44
+
45
+ def set_bearer_authorization_header(token)
46
+ set_authorization_header("Bearer #{token}")
47
+ end
48
+
49
+ def reauthenticate!
50
+ # refresh token and update header
51
+ token = refresh_client_token_option.call
52
+ set_bearer_authorization_header(token)
53
+ # trigger DHC::Retry and ensure we do not trigger reauthenticate!
54
+ # again should it fail another time
55
+ new_options = request.options.dup
56
+ new_options = new_options.merge(retry: { max: 1 })
57
+ new_options = new_options.merge(auth: { reauthenticated: true })
58
+ request.options = new_options
59
+ end
60
+
61
+ def reauthenticate?
62
+ !response.success? &&
63
+ !auth_options[:reauthenticated] &&
64
+ bearer_header_present? &&
65
+ DHC::Error.find(response) == DHC::Unauthorized
66
+ end
67
+
68
+ def bearer_header_present?
69
+ @has_bearer_header ||= request.headers['Authorization'] =~ /^Bearer .+$/i
70
+ end
71
+
72
+ def refresh_client_token_option
73
+ @refresh_client_token_option ||= auth_options[:refresh_client_token] || refresh_client_token
74
+ end
75
+
76
+ def auth_options
77
+ request.options[:auth] || {}
78
+ end
79
+
80
+ def configuration_correct?
81
+ # warn user about configs, only if refresh_client_token_option is set at all
82
+ refresh_client_token_option && refresh_client_token? && retry_interceptor?
83
+ end
84
+
85
+ def refresh_client_token?
86
+ return true if refresh_client_token_option.is_a?(Proc)
87
+ warn('[WARNING] The given refresh_client_token must be a Proc for reauthentication.')
88
+ end
89
+
90
+ def retry_interceptor?
91
+ return true if all_interceptor_classes.include?(DHC::Retry) && all_interceptor_classes.index(DHC::Retry) > all_interceptor_classes.index(self.class)
92
+ warn('[WARNING] Your interceptors must include DHC::Retry after DHC::Auth.')
93
+ end
94
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::Caching < DHC::Interceptor
4
+ include ActiveSupport::Configurable
5
+
6
+ config_accessor :cache, :central
7
+
8
+ # to control cache invalidation across all applications in case of
9
+ # breaking changes within this inteceptor
10
+ # that do not lead to cache invalidation otherwise
11
+ CACHE_VERSION = '1'
12
+
13
+ # Options forwarded to the cache
14
+ FORWARDED_OPTIONS = %i[expires_in race_condition_ttl].freeze
15
+
16
+ class MultilevelCache
17
+
18
+ def initialize(central: nil, local: nil)
19
+ @central = central
20
+ @local = local
21
+ end
22
+
23
+ def fetch(key)
24
+ central_response = @central[:read].fetch(key) if @central && @central[:read].present?
25
+ if central_response
26
+ puts %([DHC] served from central cache: "#{key}")
27
+ return central_response
28
+ end
29
+ local_response = @local.fetch(key) if @local
30
+ if local_response
31
+ puts %([DHC] served from local cache: "#{key}")
32
+ return local_response
33
+ end
34
+ end
35
+
36
+ def write(key, content, options)
37
+ @central[:write].write(key, content, options) if @central && @central[:write].present?
38
+ @local.write(key, content, options) if @local.present?
39
+ end
40
+ end
41
+
42
+ def before_request
43
+ return unless cache?(request)
44
+ return if response_data.blank?
45
+ from_cache(request, response_data)
46
+ end
47
+
48
+ def after_response
49
+ return unless response.success?
50
+ return unless cache?(request)
51
+ return if response_data.present?
52
+ multilevel_cache.write(
53
+ key(request, options[:key]),
54
+ to_cache(response),
55
+ cache_options
56
+ )
57
+ end
58
+
59
+ private
60
+
61
+ # from cache
62
+ def response_data
63
+ # stop calling multi-level cache if it already returned nil for this interceptor instance
64
+ return @response_data if defined? @response_data
65
+ @response_data ||= multilevel_cache.fetch(key(request, options[:key]))
66
+ end
67
+
68
+ # performs read/write (fetch/write) on all configured cache levels (e.g. local & central)
69
+ def multilevel_cache
70
+ MultilevelCache.new(
71
+ central: central_cache,
72
+ local: local_cache
73
+ )
74
+ end
75
+
76
+ # returns the local cache either configured for entire DHC
77
+ # or configured locally for that particular request
78
+ def local_cache
79
+ options.fetch(:use, cache)
80
+ end
81
+
82
+ def central_cache
83
+ return nil if central.blank? || (central[:read].blank? && central[:write].blank?)
84
+ {}.tap do |options|
85
+ options[:read] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:read]) if central[:read].present?
86
+ options[:write] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:write]) if central[:write].present?
87
+ end
88
+ end
89
+
90
+ # do we even need to bother with this interceptor?
91
+ # based on the options, this method will
92
+ # return false if this interceptor cannot work
93
+ def cache?(request)
94
+ return false unless request.options[:cache]
95
+ (local_cache || central_cache) &&
96
+ cached_method?(request.method, options[:methods])
97
+ end
98
+
99
+ def options
100
+ options = (request.options[:cache] == true) ? {} : request.options[:cache].dup
101
+ options
102
+ end
103
+
104
+ # converts json we read from the cache to an DHC::Response object
105
+ def from_cache(request, data)
106
+ raw = Typhoeus::Response.new(data)
107
+ response = DHC::Response.new(raw, request, from_cache: true)
108
+ request.response = response
109
+ response
110
+ end
111
+
112
+ # converts a DHC::Response object to json, we store in the cache
113
+ def to_cache(response)
114
+ data = {}
115
+ data[:body] = response.body
116
+ data[:code] = response.code
117
+ # convert into a actual hash because the typhoeus headers object breaks marshaling
118
+ data[:headers] = response.headers ? Hash[response.headers] : response.headers
119
+ # return_code is quite important as Typhoeus relies on it in order to determin 'success?'
120
+ data[:return_code] = response.options[:return_code]
121
+ # in a test scenario typhoeus uses mocks and not return_code to determine 'success?'
122
+ data[:mock] = response.mock
123
+ data
124
+ end
125
+
126
+ def key(request, key)
127
+ unless key
128
+ key = "#{request.method.upcase} #{request.url}"
129
+ key += "?#{request.params.to_query}" unless request.params.blank?
130
+ end
131
+ "DHC_CACHE(v#{CACHE_VERSION}): #{key}"
132
+ end
133
+
134
+ # Checks if the provided method should be cached
135
+ # in regards of the provided options.
136
+ def cached_method?(method, cached_methods)
137
+ (cached_methods || [:get]).include?(method)
138
+ end
139
+
140
+ # extracts the options that should be forwarded to
141
+ # the cache
142
+ def cache_options
143
+ options.each_with_object({}) do |(key, value), result|
144
+ result[key] = value if key.in? FORWARDED_OPTIONS
145
+ result
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::DefaultTimeout < DHC::Interceptor
4
+ include ActiveSupport::Configurable
5
+
6
+ config_accessor :timeout, :connecttimeout
7
+
8
+ CONNECTTIMEOUT = 2 # seconds
9
+ TIMEOUT = 15 # seconds
10
+
11
+ def before_raw_request
12
+ request_options = (request.options || {})
13
+ request_options[:timeout] ||= timeout || TIMEOUT
14
+ request_options[:connecttimeout] ||= connecttimeout || CONNECTTIMEOUT
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::Logging < DHC::Interceptor
4
+
5
+ include ActiveSupport::Configurable
6
+ config_accessor :logger
7
+
8
+ def before_request
9
+ return unless logger
10
+ logger.info(
11
+ [
12
+ 'Before DHC request',
13
+ "<#{request.object_id}>",
14
+ request.method.upcase,
15
+ "#{request.url} at #{Time.now.iso8601}",
16
+ "Params=#{request.params}",
17
+ "Headers=#{request.headers}",
18
+ request.source ? "\nCalled from #{request.source}" : nil
19
+ ].compact.join(' ')
20
+ )
21
+ end
22
+
23
+ def after_response
24
+ return unless logger
25
+ logger.info(
26
+ [
27
+ 'After DHC response for request',
28
+ "<#{request.object_id}>",
29
+ request.method.upcase,
30
+ "#{request.url} at #{Time.now.iso8601}",
31
+ "Time=#{response.time_ms}ms",
32
+ "URL=#{response.effective_url}",
33
+ request.source ? "\nCalled from #{request.source}" : nil
34
+ ].compact.join(' ')
35
+ )
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::Monitoring < DHC::Interceptor
4
+
5
+ # Options forwarded to the monitoring
6
+ FORWARDED_OPTIONS = {
7
+ monitoring_key: :key
8
+ }.freeze
9
+
10
+ include ActiveSupport::Configurable
11
+
12
+ config_accessor :statsd, :env
13
+
14
+ def before_request
15
+ return unless statsd
16
+ DHC::Monitoring.statsd.count("#{key}.before_request", 1)
17
+ end
18
+
19
+ def after_request
20
+ return unless statsd
21
+ DHC::Monitoring.statsd.count("#{key}.count", 1)
22
+ DHC::Monitoring.statsd.count("#{key}.after_request", 1)
23
+ end
24
+
25
+ def after_response
26
+ return unless statsd
27
+ monitor_time!
28
+ monitor_cache!
29
+ monitor_response!
30
+ end
31
+
32
+ private
33
+
34
+ def monitor_time!
35
+ DHC::Monitoring.statsd.timing("#{key}.time", response.time) if response.success?
36
+ end
37
+
38
+ def monitor_cache!
39
+ return if request.options[:cache].blank?
40
+ return unless monitor_caching_configuration_check
41
+ if response.from_cache?
42
+ DHC::Monitoring.statsd.count("#{key}.cache.hit", 1)
43
+ else
44
+ DHC::Monitoring.statsd.count("#{key}.cache.miss", 1)
45
+ end
46
+ end
47
+
48
+ def monitor_caching_configuration_check
49
+ return true if all_interceptor_classes.include?(DHC::Caching) && all_interceptor_classes.index(self.class) > all_interceptor_classes.index(DHC::Caching)
50
+ warn('[WARNING] Your interceptors must include DHC::Caching and DHC::Monitoring and also in that order.')
51
+ end
52
+
53
+ def monitor_response!
54
+ if response.timeout?
55
+ DHC::Monitoring.statsd.count("#{key}.timeout", 1)
56
+ else
57
+ DHC::Monitoring.statsd.count("#{key}.#{response.code}", 1)
58
+ end
59
+ end
60
+
61
+ def key
62
+ key = options(request.options)[:key]
63
+ return key if key.present?
64
+
65
+ url = sanitize_url(request.url)
66
+ key = [
67
+ 'dhc',
68
+ module_parent_name.underscore,
69
+ DHC::Monitoring.env || Rails.env,
70
+ URI.parse(url).host.gsub(/\./, '_'),
71
+ request.method
72
+ ]
73
+ key.join('.')
74
+ end
75
+
76
+ def module_parent_name
77
+ (ActiveSupport.gem_version >= Gem::Version.new('6.0.0')) ? Rails.application.class.module_parent_name : Rails.application.class.parent_name
78
+ end
79
+
80
+ def sanitize_url(url)
81
+ return url if url.match(%r{https?://})
82
+ "http://#{url}"
83
+ end
84
+
85
+ def options(input = {})
86
+ options = {}
87
+ FORWARDED_OPTIONS.each do |k, v|
88
+ options[v] = input[k] if input.key?(k)
89
+ end
90
+ options
91
+ end
92
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DHC::Prometheus < DHC::Interceptor
4
+ include ActiveSupport::Configurable
5
+
6
+ config_accessor :client, :namespace
7
+
8
+ REQUEST_COUNTER_KEY = :dhc_requests
9
+ REQUEST_HISTOGRAM_KEY = :dhc_request_seconds
10
+
11
+ class << self
12
+ attr_accessor :registered
13
+ end
14
+
15
+ def initialize(request)
16
+ super(request)
17
+ return if DHC::Prometheus.registered || DHC::Prometheus.client.blank?
18
+
19
+ begin
20
+ DHC::Prometheus.client.registry.counter(DHC::Prometheus::REQUEST_COUNTER_KEY, 'Counter of all DHC requests.')
21
+ DHC::Prometheus.client.registry.histogram(DHC::Prometheus::REQUEST_HISTOGRAM_KEY, 'Request timings for all DHC requests in seconds.')
22
+ rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
23
+ Rails.logger.error(e) if defined?(Rails)
24
+ ensure
25
+ DHC::Prometheus.registered = true
26
+ end
27
+ end
28
+
29
+ def after_response
30
+ return if !DHC::Prometheus.registered || DHC::Prometheus.client.blank?
31
+
32
+ host = URI.parse(request.url).host
33
+
34
+ DHC::Prometheus.client.registry
35
+ .get(DHC::Prometheus::REQUEST_COUNTER_KEY)
36
+ .increment(
37
+ code: response.code,
38
+ success: response.success?,
39
+ timeout: response.timeout?,
40
+ host: host,
41
+ app: DHC::Prometheus.namespace
42
+ )
43
+
44
+ DHC::Prometheus.client.registry
45
+ .get(DHC::Prometheus::REQUEST_HISTOGRAM_KEY)
46
+ .observe({
47
+ host: host,
48
+ app: DHC::Prometheus.namespace
49
+ }, response.time)
50
+ end
51
+ end