lhc 13.0.0 → 14.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yml +15 -0
  3. data/.github/workflows/test.yml +15 -0
  4. data/.rubocop.yml +344 -19
  5. data/.ruby-version +1 -1
  6. data/README.md +44 -0
  7. data/Rakefile +3 -3
  8. data/lhc.gemspec +3 -1
  9. data/lib/lhc.rb +59 -59
  10. data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +1 -0
  11. data/lib/lhc/config.rb +3 -0
  12. data/lib/lhc/endpoint.rb +3 -0
  13. data/lib/lhc/error.rb +5 -1
  14. data/lib/lhc/interceptor.rb +4 -0
  15. data/lib/lhc/interceptors.rb +1 -0
  16. data/lib/lhc/interceptors/auth.rb +3 -4
  17. data/lib/lhc/interceptors/caching.rb +14 -3
  18. data/lib/lhc/interceptors/logging.rb +2 -0
  19. data/lib/lhc/interceptors/monitoring.rb +46 -11
  20. data/lib/lhc/interceptors/retry.rb +2 -0
  21. data/lib/lhc/interceptors/rollbar.rb +1 -0
  22. data/lib/lhc/interceptors/throttle.rb +7 -2
  23. data/lib/lhc/interceptors/zipkin.rb +2 -0
  24. data/lib/lhc/request.rb +13 -3
  25. data/lib/lhc/response.rb +1 -0
  26. data/lib/lhc/response/data.rb +1 -1
  27. data/lib/lhc/version.rb +1 -1
  28. data/spec/error/to_s_spec.rb +7 -2
  29. data/spec/formats/multipart_spec.rb +2 -2
  30. data/spec/formats/plain_spec.rb +1 -1
  31. data/spec/interceptors/after_response_spec.rb +1 -1
  32. data/spec/interceptors/caching/main_spec.rb +2 -2
  33. data/spec/interceptors/caching/multilevel_cache_spec.rb +2 -1
  34. data/spec/interceptors/define_spec.rb +1 -0
  35. data/spec/interceptors/monitoring/caching_spec.rb +66 -0
  36. data/spec/interceptors/response_competition_spec.rb +2 -2
  37. data/spec/interceptors/return_response_spec.rb +2 -2
  38. data/spec/response/data_spec.rb +2 -2
  39. data/spec/support/zipkin_mock.rb +1 -0
  40. metadata +27 -21
  41. data/.rubocop.localch.yml +0 -325
  42. data/cider-ci.yml +0 -5
  43. data/cider-ci/bin/bundle +0 -51
  44. data/cider-ci/bin/ruby_install +0 -8
  45. data/cider-ci/bin/ruby_version +0 -25
  46. data/cider-ci/jobs/rspec-activesupport-5.yml +0 -27
  47. data/cider-ci/jobs/rspec-activesupport-6.yml +0 -28
  48. data/cider-ci/jobs/rubocop.yml +0 -18
  49. data/cider-ci/task_components/bundle.yml +0 -22
  50. data/cider-ci/task_components/rspec.yml +0 -36
  51. data/cider-ci/task_components/rubocop.yml +0 -29
  52. data/cider-ci/task_components/ruby.yml +0 -15
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.6.5
1
+ ruby-2.7.2
data/README.md CHANGED
@@ -73,6 +73,10 @@ use it like:
73
73
  * [Installation](#installation-1)
74
74
  * [Environment](#environment)
75
75
  * [What it tracks](#what-it-tracks)
76
+ * [Before and after request tracking](#before-and-after-request-tracking)
77
+ * [Response tracking](#response-tracking)
78
+ * [Timeout tracking](#timeout-tracking)
79
+ * [Caching tracking](#caching-tracking)
76
80
  * [Configure](#configure-1)
77
81
  * [Prometheus Interceptor](#prometheus-interceptor)
78
82
  * [Retry Interceptor](#retry-interceptor)
@@ -95,6 +99,7 @@ use it like:
95
99
 
96
100
 
97
101
 
102
+
98
103
  ## Basic methods
99
104
 
100
105
  Available are `get`, `post`, `put` & `delete`.
@@ -744,11 +749,15 @@ It tracks request attempts with `before_request` and `after_request` (counts).
744
749
  In case your workers/processes are getting killed due limited time constraints,
745
750
  you are able to detect deltas with relying on "before_request", and "after_request" counts:
746
751
 
752
+ ###### Before and after request tracking
753
+
747
754
  ```ruby
748
755
  "lhc.<app_name>.<env>.<host>.<http_method>.before_request", 1
749
756
  "lhc.<app_name>.<env>.<host>.<http_method>.after_request", 1
750
757
  ```
751
758
 
759
+ ###### Response tracking
760
+
752
761
  In case of a successful response it reports the response code with a count and the response time with a gauge value.
753
762
 
754
763
  ```ruby
@@ -759,6 +768,17 @@ In case of a successful response it reports the response code with a count and t
759
768
  "lhc.<app_name>.<env>.<host>.<http_method>.time", 43
760
769
  ```
761
770
 
771
+ In case of a unsuccessful response it reports the response code with a count but no time:
772
+
773
+ ```ruby
774
+ LHC.get('http://local.ch')
775
+
776
+ "lhc.<app_name>.<env>.<host>.<http_method>.count", 1
777
+ "lhc.<app_name>.<env>.<host>.<http_method>.500", 1
778
+ ```
779
+
780
+ ###### Timeout tracking
781
+
762
782
  Timeouts are also reported:
763
783
 
764
784
  ```ruby
@@ -767,6 +787,30 @@ Timeouts are also reported:
767
787
 
768
788
  All the dots in the host are getting replaced with underscore, because dot is the default separator in graphite.
769
789
 
790
+ ###### Caching tracking
791
+
792
+ When you want to track caching stats please make sure you have enabled the `LHC::Caching` and the `LHC::Monitoring` interceptor.
793
+
794
+ Make sure that the `LHC::Caching` is listed before `LHC::Monitoring` interceptor when configuring interceptors:
795
+
796
+ ```ruby
797
+ LHC.configure do |c|
798
+ c.interceptors = [LHC::Caching, LHC::Monitoring]
799
+ end
800
+ ```
801
+
802
+ If a response was served from cache it tracks:
803
+
804
+ ```ruby
805
+ "lhc.<app_name>.<env>.<host>.<http_method>.cache.hit", 1
806
+ ```
807
+
808
+ If a response was not served from cache it tracks:
809
+
810
+ ```ruby
811
+ "lhc.<app_name>.<env>.<host>.<http_method>.cache.miss", 1
812
+ ```
813
+
770
814
  ##### Configure
771
815
 
772
816
  It is possible to set the key for Monitoring Interceptor on per request basis:
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -17,9 +19,7 @@ end
17
19
  begin
18
20
  require 'rspec/core/rake_task'
19
21
  RSpec::Core::RakeTask.new(:spec)
20
- task :default => :spec
21
- rescue LoadError
22
- # no rspec available
22
+ task default: :spec
23
23
  end
24
24
 
25
25
  Bundler::GemHelper.install_tasks
data/lhc.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.require_paths = ['lib']
21
21
 
22
22
  s.requirements << 'Ruby >= 2.0.0'
23
+ s.required_ruby_version = '>= 2.7' # Needed for rubocop
23
24
 
24
25
  s.add_dependency 'activesupport', '>= 5.2'
25
26
  s.add_dependency 'addressable'
@@ -31,7 +32,8 @@ Gem::Specification.new do |s|
31
32
  s.add_development_dependency 'rails', '>= 5.2'
32
33
  s.add_development_dependency 'redis'
33
34
  s.add_development_dependency 'rspec-rails', '>= 3.0.0'
34
- s.add_development_dependency 'rubocop', '~> 0.57.1'
35
+ s.add_development_dependency 'rubocop', '~> 1.0'
36
+ s.add_development_dependency 'rubocop-performance', '~> 1.0'
35
37
  s.add_development_dependency 'rubocop-rspec', '~> 1.26.0'
36
38
  s.add_development_dependency 'timecop'
37
39
  s.add_development_dependency 'webmock'
data/lib/lhc.rb CHANGED
@@ -6,131 +6,131 @@ require 'active_support/core_ext/hash/keys'
6
6
 
7
7
  module LHC
8
8
  autoload :BasicMethodsConcern,
9
- 'lhc/concerns/lhc/basic_methods_concern'
9
+ 'lhc/concerns/lhc/basic_methods_concern'
10
10
  autoload :ConfigurationConcern,
11
- 'lhc/concerns/lhc/configuration_concern'
11
+ 'lhc/concerns/lhc/configuration_concern'
12
12
  autoload :FixInvalidEncodingConcern,
13
- 'lhc/concerns/lhc/fix_invalid_encoding_concern'
13
+ 'lhc/concerns/lhc/fix_invalid_encoding_concern'
14
14
  autoload :FormatsConcern,
15
- 'lhc/concerns/lhc/formats_concern'
15
+ 'lhc/concerns/lhc/formats_concern'
16
16
 
17
17
  include BasicMethodsConcern
18
18
  include ConfigurationConcern
19
19
  include FormatsConcern
20
20
 
21
21
  autoload :Auth,
22
- 'lhc/interceptors/auth'
22
+ 'lhc/interceptors/auth'
23
23
  autoload :Caching,
24
- 'lhc/interceptors/caching'
24
+ 'lhc/interceptors/caching'
25
25
  autoload :DefaultTimeout,
26
- 'lhc/interceptors/default_timeout'
26
+ 'lhc/interceptors/default_timeout'
27
27
  autoload :Logging,
28
- 'lhc/interceptors/logging'
28
+ 'lhc/interceptors/logging'
29
29
  autoload :Prometheus,
30
- 'lhc/interceptors/prometheus'
30
+ 'lhc/interceptors/prometheus'
31
31
  autoload :Retry,
32
- 'lhc/interceptors/retry'
32
+ 'lhc/interceptors/retry'
33
33
  autoload :Throttle,
34
- 'lhc/interceptors/throttle'
34
+ 'lhc/interceptors/throttle'
35
35
 
36
36
  autoload :Config,
37
- 'lhc/config'
37
+ 'lhc/config'
38
38
  autoload :Endpoint,
39
- 'lhc/endpoint'
39
+ 'lhc/endpoint'
40
40
 
41
41
  autoload :Error,
42
- 'lhc/error'
42
+ 'lhc/error'
43
43
  autoload :ClientError,
44
- 'lhc/errors/client_error'
44
+ 'lhc/errors/client_error'
45
45
  autoload :BadRequest,
46
- 'lhc/errors/client_error'
46
+ 'lhc/errors/client_error'
47
47
  autoload :Unauthorized,
48
- 'lhc/errors/client_error'
48
+ 'lhc/errors/client_error'
49
49
  autoload :PaymentRequired,
50
- 'lhc/errors/client_error'
50
+ 'lhc/errors/client_error'
51
51
  autoload :Forbidden,
52
- 'lhc/errors/client_error'
52
+ 'lhc/errors/client_error'
53
53
  autoload :Forbidden,
54
- 'lhc/errors/client_error'
54
+ 'lhc/errors/client_error'
55
55
  autoload :NotFound,
56
- 'lhc/errors/client_error'
56
+ 'lhc/errors/client_error'
57
57
  autoload :MethodNotAllowed,
58
- 'lhc/errors/client_error'
58
+ 'lhc/errors/client_error'
59
59
  autoload :NotAcceptable,
60
- 'lhc/errors/client_error'
60
+ 'lhc/errors/client_error'
61
61
  autoload :ProxyAuthenticationRequired,
62
- 'lhc/errors/client_error'
62
+ 'lhc/errors/client_error'
63
63
  autoload :RequestTimeout,
64
- 'lhc/errors/client_error'
64
+ 'lhc/errors/client_error'
65
65
  autoload :Conflict,
66
- 'lhc/errors/client_error'
66
+ 'lhc/errors/client_error'
67
67
  autoload :Gone,
68
- 'lhc/errors/client_error'
68
+ 'lhc/errors/client_error'
69
69
  autoload :LengthRequired,
70
- 'lhc/errors/client_error'
70
+ 'lhc/errors/client_error'
71
71
  autoload :PreconditionFailed,
72
- 'lhc/errors/client_error'
72
+ 'lhc/errors/client_error'
73
73
  autoload :RequestEntityTooLarge,
74
- 'lhc/errors/client_error'
74
+ 'lhc/errors/client_error'
75
75
  autoload :RequestUriToLong,
76
- 'lhc/errors/client_error'
76
+ 'lhc/errors/client_error'
77
77
  autoload :UnsupportedMediaType,
78
- 'lhc/errors/client_error'
78
+ 'lhc/errors/client_error'
79
79
  autoload :RequestedRangeNotSatisfiable,
80
- 'lhc/errors/client_error'
80
+ 'lhc/errors/client_error'
81
81
  autoload :ExpectationFailed,
82
- 'lhc/errors/client_error'
82
+ 'lhc/errors/client_error'
83
83
  autoload :UnprocessableEntity,
84
- 'lhc/errors/client_error'
84
+ 'lhc/errors/client_error'
85
85
  autoload :Locked,
86
- 'lhc/errors/client_error'
86
+ 'lhc/errors/client_error'
87
87
  autoload :FailedDependency,
88
- 'lhc/errors/client_error'
88
+ 'lhc/errors/client_error'
89
89
  autoload :UpgradeRequired,
90
- 'lhc/errors/client_error'
90
+ 'lhc/errors/client_error'
91
91
  autoload :ParserError,
92
- 'lhc/errors/parser_error'
92
+ 'lhc/errors/parser_error'
93
93
  autoload :ServerError,
94
- 'lhc/errors/server_error'
94
+ 'lhc/errors/server_error'
95
95
  autoload :InternalServerError,
96
- 'lhc/errors/server_error'
96
+ 'lhc/errors/server_error'
97
97
  autoload :NotImplemented,
98
- 'lhc/errors/server_error'
98
+ 'lhc/errors/server_error'
99
99
  autoload :BadGateway,
100
- 'lhc/errors/server_error'
100
+ 'lhc/errors/server_error'
101
101
  autoload :ServiceUnavailable,
102
- 'lhc/errors/server_error'
102
+ 'lhc/errors/server_error'
103
103
  autoload :GatewayTimeout,
104
- 'lhc/errors/server_error'
104
+ 'lhc/errors/server_error'
105
105
  autoload :HttpVersionNotSupported,
106
- 'lhc/errors/server_error'
106
+ 'lhc/errors/server_error'
107
107
  autoload :InsufficientStorage,
108
- 'lhc/errors/server_error'
108
+ 'lhc/errors/server_error'
109
109
  autoload :NotExtended,
110
- 'lhc/errors/server_error'
110
+ 'lhc/errors/server_error'
111
111
  autoload :Timeout,
112
- 'lhc/errors/timeout'
112
+ 'lhc/errors/timeout'
113
113
  autoload :UnknownError,
114
- 'lhc/errors/unknown_error'
114
+ 'lhc/errors/unknown_error'
115
115
 
116
116
  autoload :Interceptor,
117
- 'lhc/interceptor'
117
+ 'lhc/interceptor'
118
118
  autoload :Interceptors,
119
- 'lhc/interceptors'
119
+ 'lhc/interceptors'
120
120
  autoload :Formats,
121
- 'lhc/formats'
121
+ 'lhc/formats'
122
122
  autoload :Format,
123
- 'lhc/format'
123
+ 'lhc/format'
124
124
  autoload :Monitoring,
125
- 'lhc/interceptors/monitoring'
125
+ 'lhc/interceptors/monitoring'
126
126
  autoload :Request,
127
- 'lhc/request'
127
+ 'lhc/request'
128
128
  autoload :Response,
129
- 'lhc/response'
129
+ 'lhc/response'
130
130
  autoload :Rollbar,
131
- 'lhc/interceptors/rollbar'
131
+ 'lhc/interceptors/rollbar'
132
132
  autoload :Zipkin,
133
- 'lhc/interceptors/zipkin'
133
+ 'lhc/interceptors/zipkin'
134
134
 
135
135
  require 'lhc/railtie' if defined?(Rails)
136
136
  end
@@ -12,6 +12,7 @@ module LHC
12
12
  # an empty string is returned instead
13
13
  def fix_invalid_encoding(string)
14
14
  return string unless string.is_a?(String)
15
+
15
16
  result = string.dup
16
17
 
17
18
  # we assume it's ISO-8859-1 first
data/lib/lhc/config.rb CHANGED
@@ -13,6 +13,7 @@ class LHC::Config
13
13
  def endpoint(name, url, options = {})
14
14
  name = name.to_sym
15
15
  raise 'Endpoint already exists for that name' if @endpoints[name]
16
+
16
17
  @endpoints[name] = LHC::Endpoint.new(url, options)
17
18
  end
18
19
 
@@ -23,6 +24,7 @@ class LHC::Config
23
24
  def placeholder(name, value)
24
25
  name = name.to_sym
25
26
  raise 'Placeholder already exists for that name' if @placeholders[name]
27
+
26
28
  @placeholders[name] = value
27
29
  end
28
30
 
@@ -36,6 +38,7 @@ class LHC::Config
36
38
 
37
39
  def interceptors=(interceptors)
38
40
  raise 'Default interceptors already set and can only be set once' if @interceptors
41
+
39
42
  @interceptors = interceptors
40
43
  end
41
44
 
data/lib/lhc/endpoint.rb CHANGED
@@ -55,6 +55,7 @@ class LHC::Endpoint
55
55
  # Example: {+datastore}/contracts/{id} == http://local.ch/contracts/1
56
56
  def match?(url)
57
57
  return true if url == uri.pattern
58
+
58
59
  match_data = match_data(url)
59
60
  return false if match_data.nil?
60
61
 
@@ -75,6 +76,7 @@ class LHC::Endpoint
75
76
  def values_as_params(url)
76
77
  match_data = match_data(url)
77
78
  return if match_data.nil?
79
+
78
80
  Hash[match_data.variables.map(&:to_sym).zip(match_data.values)]
79
81
  end
80
82
 
@@ -103,6 +105,7 @@ class LHC::Endpoint
103
105
  # creates params according to template
104
106
  def self.values_as_params(template, url)
105
107
  raise("#{url} does not match the template: #{template}") if !match?(url, template)
108
+
106
109
  new(template).values_as_params(url)
107
110
  end
108
111
 
data/lib/lhc/error.rb CHANGED
@@ -43,6 +43,7 @@ class LHC::Error < StandardError
43
43
 
44
44
  def self.find(response)
45
45
  return LHC::Timeout if response.timeout?
46
+
46
47
  status_code = response.code.to_s[0..2].to_i
47
48
  error = map[status_code]
48
49
  error ||= LHC::UnknownError
@@ -64,8 +65,11 @@ class LHC::Error < StandardError
64
65
  end
65
66
 
66
67
  def to_s
67
- return response if response.is_a?(String)
68
+ return response.to_s unless response.is_a?(LHC::Response)
69
+
68
70
  request = response.request
71
+ return unless request.is_a?(LHC::Request)
72
+
69
73
  debug = []
70
74
  debug << [request.method, request.url].map { |str| self.class.fix_invalid_encoding(str) }.join(' ')
71
75
  debug << "Options: #{request.options}"
@@ -29,4 +29,8 @@ class LHC::Interceptor
29
29
  def self.dup
30
30
  self
31
31
  end
32
+
33
+ def all_interceptor_classes
34
+ @all_interceptors ||= LHC::Interceptors.new(request).all.map(&:class)
35
+ end
32
36
  end
@@ -19,6 +19,7 @@ class LHC::Interceptors
19
19
  result = interceptor.send(name)
20
20
  if result.is_a? LHC::Response
21
21
  raise 'Response already set from another interceptor' if @response
22
+
22
23
  @response = interceptor.request.response = result
23
24
  end
24
25
  end
@@ -16,6 +16,7 @@ class LHC::Auth < LHC::Interceptor
16
16
  def after_response
17
17
  return unless configuration_correct?
18
18
  return unless reauthenticate?
19
+
19
20
  reauthenticate!
20
21
  end
21
22
 
@@ -75,10 +76,6 @@ class LHC::Auth < LHC::Interceptor
75
76
  @refresh_client_token_option ||= auth_options[:refresh_client_token] || refresh_client_token
76
77
  end
77
78
 
78
- def all_interceptor_classes
79
- @all_interceptors ||= LHC::Interceptors.new(request).all.map(&:class)
80
- end
81
-
82
79
  def auth_options
83
80
  request.options[:auth] || {}
84
81
  end
@@ -90,11 +87,13 @@ class LHC::Auth < LHC::Interceptor
90
87
 
91
88
  def refresh_client_token?
92
89
  return true if refresh_client_token_option.is_a?(Proc)
90
+
93
91
  warn("[WARNING] The given refresh_client_token must be a Proc for reauthentication.")
94
92
  end
95
93
 
96
94
  def retry_interceptor?
97
95
  return true if all_interceptor_classes.include?(LHC::Retry) && all_interceptor_classes.index(LHC::Retry) > all_interceptor_classes.index(self.class)
96
+
98
97
  warn("[WARNING] Your interceptors must include LHC::Retry after LHC::Auth.")
99
98
  end
100
99
  end
@@ -41,15 +41,16 @@ class LHC::Caching < LHC::Interceptor
41
41
 
42
42
  def before_request
43
43
  return unless cache?(request)
44
- key = key(request, options[:key])
45
- response_data = multilevel_cache.fetch(key)
46
- return unless response_data
44
+ return if response_data.blank?
45
+
47
46
  from_cache(request, response_data)
48
47
  end
49
48
 
50
49
  def after_response
51
50
  return unless response.success?
52
51
  return unless cache?(request)
52
+ return if response_data.present?
53
+
53
54
  multilevel_cache.write(
54
55
  key(request, options[:key]),
55
56
  to_cache(response),
@@ -59,6 +60,14 @@ class LHC::Caching < LHC::Interceptor
59
60
 
60
61
  private
61
62
 
63
+ # from cache
64
+ def response_data
65
+ # stop calling multi-level cache if it already returned nil for this interceptor instance
66
+ return @response_data if defined? @response_data
67
+
68
+ @response_data ||= multilevel_cache.fetch(key(request, options[:key]))
69
+ end
70
+
62
71
  # performs read/write (fetch/write) on all configured cache levels (e.g. local & central)
63
72
  def multilevel_cache
64
73
  MultilevelCache.new(
@@ -75,6 +84,7 @@ class LHC::Caching < LHC::Interceptor
75
84
 
76
85
  def central_cache
77
86
  return nil if central.blank? || (central[:read].blank? && central[:write].blank?)
87
+
78
88
  {}.tap do |options|
79
89
  options[:read] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:read]) if central[:read].present?
80
90
  options[:write] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:write]) if central[:write].present?
@@ -86,6 +96,7 @@ class LHC::Caching < LHC::Interceptor
86
96
  # return false if this interceptor cannot work
87
97
  def cache?(request)
88
98
  return false unless request.options[:cache]
99
+
89
100
  (local_cache || central_cache) &&
90
101
  cached_method?(request.method, options[:methods])
91
102
  end