lhc 12.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rubocop.localch.yml +325 -0
- data/.rubocop.yml +61 -0
- data/.ruby-version +1 -0
- data/Gemfile +13 -0
- data/Gemfile.activesupport4 +4 -0
- data/Gemfile.activesupport5 +4 -0
- data/Gemfile.activesupport6 +4 -0
- data/LICENSE +674 -0
- data/README.md +984 -0
- data/Rakefile +25 -0
- data/cider-ci.yml +6 -0
- data/cider-ci/bin/bundle +51 -0
- data/cider-ci/bin/ruby_install +8 -0
- data/cider-ci/bin/ruby_version +25 -0
- data/cider-ci/jobs/rspec-activesupport-4.yml +28 -0
- data/cider-ci/jobs/rspec-activesupport-5.yml +27 -0
- data/cider-ci/jobs/rspec-activesupport-6.yml +28 -0
- data/cider-ci/jobs/rubocop.yml +18 -0
- data/cider-ci/task_components/bundle.yml +22 -0
- data/cider-ci/task_components/rspec.yml +36 -0
- data/cider-ci/task_components/rubocop.yml +29 -0
- data/cider-ci/task_components/ruby.yml +15 -0
- data/friday.yml +3 -0
- data/lhc.gemspec +39 -0
- data/lib/core_ext/hash/deep_transform_values.rb +48 -0
- data/lib/lhc.rb +136 -0
- data/lib/lhc/concerns/lhc/basic_methods_concern.rb +42 -0
- data/lib/lhc/concerns/lhc/configuration_concern.rb +20 -0
- data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +42 -0
- data/lib/lhc/concerns/lhc/formats_concern.rb +25 -0
- data/lib/lhc/concerns/lhc/request/user_agent_concern.rb +25 -0
- data/lib/lhc/config.rb +47 -0
- data/lib/lhc/endpoint.rb +119 -0
- data/lib/lhc/error.rb +80 -0
- data/lib/lhc/errors/client_error.rb +73 -0
- data/lib/lhc/errors/parser_error.rb +4 -0
- data/lib/lhc/errors/server_error.rb +28 -0
- data/lib/lhc/errors/timeout.rb +4 -0
- data/lib/lhc/errors/unknown_error.rb +4 -0
- data/lib/lhc/format.rb +18 -0
- data/lib/lhc/formats.rb +8 -0
- data/lib/lhc/formats/form.rb +45 -0
- data/lib/lhc/formats/json.rb +55 -0
- data/lib/lhc/formats/multipart.rb +45 -0
- data/lib/lhc/formats/plain.rb +42 -0
- data/lib/lhc/interceptor.rb +32 -0
- data/lib/lhc/interceptors.rb +26 -0
- data/lib/lhc/interceptors/auth.rb +98 -0
- data/lib/lhc/interceptors/caching.rb +127 -0
- data/lib/lhc/interceptors/default_timeout.rb +16 -0
- data/lib/lhc/interceptors/logging.rb +37 -0
- data/lib/lhc/interceptors/monitoring.rb +63 -0
- data/lib/lhc/interceptors/prometheus.rb +51 -0
- data/lib/lhc/interceptors/retry.rb +41 -0
- data/lib/lhc/interceptors/rollbar.rb +36 -0
- data/lib/lhc/interceptors/throttle.rb +81 -0
- data/lib/lhc/interceptors/zipkin.rb +110 -0
- data/lib/lhc/railtie.rb +10 -0
- data/lib/lhc/request.rb +157 -0
- data/lib/lhc/response.rb +60 -0
- data/lib/lhc/response/data.rb +28 -0
- data/lib/lhc/response/data/base.rb +22 -0
- data/lib/lhc/response/data/collection.rb +16 -0
- data/lib/lhc/response/data/item.rb +29 -0
- data/lib/lhc/rspec.rb +12 -0
- data/lib/lhc/test/cache_helper.rb +3 -0
- data/lib/lhc/version.rb +5 -0
- data/script/ci/build.sh +19 -0
- data/spec/basic_methods/delete_spec.rb +34 -0
- data/spec/basic_methods/get_spec.rb +49 -0
- data/spec/basic_methods/post_spec.rb +42 -0
- data/spec/basic_methods/put_spec.rb +48 -0
- data/spec/basic_methods/request_spec.rb +19 -0
- data/spec/basic_methods/request_without_rails_spec.rb +29 -0
- data/spec/config/endpoints_spec.rb +63 -0
- data/spec/config/placeholders_spec.rb +32 -0
- data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +7 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +4 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +5 -0
- data/spec/dummy/bin/rails +6 -0
- data/spec/dummy/bin/rake +6 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/config/application.rb +16 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config/environments/development.rb +36 -0
- data/spec/dummy/config/environments/production.rb +77 -0
- data/spec/dummy/config/environments/test.rb +41 -0
- data/spec/dummy/config/initializers/assets.rb +10 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/dummy/config/initializers/inflections.rb +18 -0
- data/spec/dummy/config/initializers/mime_types.rb +6 -0
- data/spec/dummy/config/initializers/session_store.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/endpoint/compile_spec.rb +35 -0
- data/spec/endpoint/match_spec.rb +41 -0
- data/spec/endpoint/placeholders_spec.rb +30 -0
- data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
- data/spec/endpoint/values_as_params_spec.rb +31 -0
- data/spec/error/dup_spec.rb +12 -0
- data/spec/error/find_spec.rb +57 -0
- data/spec/error/response_spec.rb +17 -0
- data/spec/error/timeout_spec.rb +14 -0
- data/spec/error/to_s_spec.rb +80 -0
- data/spec/formats/form_spec.rb +27 -0
- data/spec/formats/json_spec.rb +66 -0
- data/spec/formats/multipart_spec.rb +26 -0
- data/spec/formats/plain_spec.rb +29 -0
- data/spec/interceptors/after_request_spec.rb +20 -0
- data/spec/interceptors/after_response_spec.rb +39 -0
- data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
- data/spec/interceptors/auth/bearer_spec.rb +19 -0
- data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
- data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
- data/spec/interceptors/before_request_spec.rb +21 -0
- data/spec/interceptors/before_response_spec.rb +20 -0
- data/spec/interceptors/caching/hydra_spec.rb +26 -0
- data/spec/interceptors/caching/main_spec.rb +73 -0
- data/spec/interceptors/caching/methods_spec.rb +42 -0
- data/spec/interceptors/caching/options_spec.rb +89 -0
- data/spec/interceptors/caching/parameters_spec.rb +24 -0
- data/spec/interceptors/caching/response_status_spec.rb +29 -0
- data/spec/interceptors/caching/to_cache_spec.rb +16 -0
- data/spec/interceptors/default_interceptors_spec.rb +15 -0
- data/spec/interceptors/default_timeout/main_spec.rb +34 -0
- data/spec/interceptors/define_spec.rb +29 -0
- data/spec/interceptors/dup_spec.rb +19 -0
- data/spec/interceptors/logging/main_spec.rb +37 -0
- data/spec/interceptors/monitoring/main_spec.rb +97 -0
- data/spec/interceptors/prometheus_spec.rb +54 -0
- data/spec/interceptors/response_competition_spec.rb +41 -0
- data/spec/interceptors/retry/main_spec.rb +73 -0
- data/spec/interceptors/return_response_spec.rb +38 -0
- data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
- data/spec/interceptors/rollbar/main_spec.rb +57 -0
- data/spec/interceptors/throttle/main_spec.rb +106 -0
- data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
- data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
- data/spec/rails_helper.rb +6 -0
- data/spec/request/body_spec.rb +39 -0
- data/spec/request/encoding_spec.rb +37 -0
- data/spec/request/error_handling_spec.rb +88 -0
- data/spec/request/headers_spec.rb +12 -0
- data/spec/request/ignore_errors_spec.rb +73 -0
- data/spec/request/option_dup_spec.rb +13 -0
- data/spec/request/parallel_requests_spec.rb +59 -0
- data/spec/request/params_encoding_spec.rb +26 -0
- data/spec/request/request_without_rails_spec.rb +15 -0
- data/spec/request/url_patterns_spec.rb +54 -0
- data/spec/request/user_agent_spec.rb +26 -0
- data/spec/request/user_agent_without_rails_spec.rb +27 -0
- data/spec/response/body_spec.rb +16 -0
- data/spec/response/code_spec.rb +16 -0
- data/spec/response/data_accessor_spec.rb +29 -0
- data/spec/response/data_spec.rb +61 -0
- data/spec/response/effective_url_spec.rb +16 -0
- data/spec/response/headers_spec.rb +18 -0
- data/spec/response/options_spec.rb +18 -0
- data/spec/response/success_spec.rb +13 -0
- data/spec/response/time_spec.rb +21 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/fixtures/json/feedback.json +11 -0
- data/spec/support/fixtures/json/feedbacks.json +164 -0
- data/spec/support/fixtures/json/localina_content_ad.json +23 -0
- data/spec/support/load_json.rb +5 -0
- data/spec/support/reset_config.rb +7 -0
- data/spec/support/zipkin_mock.rb +113 -0
- data/spec/timeouts/no_signal_spec.rb +13 -0
- data/spec/timeouts/timings_spec.rb +55 -0
- metadata +534 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::DefaultTimeout < LHC::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 LHC::Logging < LHC::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 LHC 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 LHC 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,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Monitoring < LHC::Interceptor
|
4
|
+
|
5
|
+
# Options forwarded to the monitoring
|
6
|
+
FORWARDED_OPTIONS = {
|
7
|
+
monitoring_key: :key
|
8
|
+
}
|
9
|
+
|
10
|
+
include ActiveSupport::Configurable
|
11
|
+
|
12
|
+
config_accessor :statsd, :env
|
13
|
+
|
14
|
+
def before_request
|
15
|
+
return unless statsd
|
16
|
+
LHC::Monitoring.statsd.count("#{key(request)}.before_request", 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
def after_request
|
20
|
+
return unless statsd
|
21
|
+
LHC::Monitoring.statsd.count("#{key(request)}.count", 1)
|
22
|
+
LHC::Monitoring.statsd.count("#{key(request)}.after_request", 1)
|
23
|
+
end
|
24
|
+
|
25
|
+
def after_response
|
26
|
+
return unless statsd
|
27
|
+
key = key(response)
|
28
|
+
LHC::Monitoring.statsd.timing("#{key}.time", response.time) if response.success?
|
29
|
+
key += response.timeout? ? '.timeout' : ".#{response.code}"
|
30
|
+
LHC::Monitoring.statsd.count(key, 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def key(target)
|
36
|
+
request = target.is_a?(LHC::Request) ? target : target.request
|
37
|
+
key = options(request.options)[:key]
|
38
|
+
return key if key.present?
|
39
|
+
|
40
|
+
url = sanitize_url(request.url)
|
41
|
+
key = [
|
42
|
+
'lhc',
|
43
|
+
Rails.application.class.parent_name.underscore,
|
44
|
+
LHC::Monitoring.env || Rails.env,
|
45
|
+
URI.parse(url).host.gsub(/\./, '_'),
|
46
|
+
request.method
|
47
|
+
]
|
48
|
+
key.join('.')
|
49
|
+
end
|
50
|
+
|
51
|
+
def sanitize_url(url)
|
52
|
+
return url if url.match(%r{https?://})
|
53
|
+
"http://#{url}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def options(input = {})
|
57
|
+
options = {}
|
58
|
+
FORWARDED_OPTIONS.each do |k, v|
|
59
|
+
options[v] = input[k] if input.key?(k)
|
60
|
+
end
|
61
|
+
options
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Prometheus < LHC::Interceptor
|
4
|
+
include ActiveSupport::Configurable
|
5
|
+
|
6
|
+
config_accessor :client, :namespace
|
7
|
+
|
8
|
+
REQUEST_COUNTER_KEY = :lhc_requests
|
9
|
+
REQUEST_HISTOGRAM_KEY = :lhc_request_seconds
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :registered
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(request)
|
16
|
+
super(request)
|
17
|
+
return if LHC::Prometheus.registered || LHC::Prometheus.client.blank?
|
18
|
+
|
19
|
+
begin
|
20
|
+
LHC::Prometheus.client.registry.counter(LHC::Prometheus::REQUEST_COUNTER_KEY, 'Counter of all LHC requests.')
|
21
|
+
LHC::Prometheus.client.registry.histogram(LHC::Prometheus::REQUEST_HISTOGRAM_KEY, 'Request timings for all LHC requests in seconds.')
|
22
|
+
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
|
23
|
+
Rails.logger.error(e) if defined?(Rails)
|
24
|
+
ensure
|
25
|
+
LHC::Prometheus.registered = true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def after_response
|
30
|
+
return if !LHC::Prometheus.registered || LHC::Prometheus.client.blank?
|
31
|
+
|
32
|
+
host = URI.parse(request.url).host
|
33
|
+
|
34
|
+
LHC::Prometheus.client.registry
|
35
|
+
.get(LHC::Prometheus::REQUEST_COUNTER_KEY)
|
36
|
+
.increment(
|
37
|
+
code: response.code,
|
38
|
+
success: response.success?,
|
39
|
+
timeout: response.timeout?,
|
40
|
+
host: host,
|
41
|
+
app: LHC::Prometheus.namespace
|
42
|
+
)
|
43
|
+
|
44
|
+
LHC::Prometheus.client.registry
|
45
|
+
.get(LHC::Prometheus::REQUEST_HISTOGRAM_KEY)
|
46
|
+
.observe({
|
47
|
+
host: host,
|
48
|
+
app: LHC::Prometheus.namespace
|
49
|
+
}, response.time)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Retry < LHC::Interceptor
|
4
|
+
attr_accessor :retries, :current_retry
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :max, :all
|
8
|
+
end
|
9
|
+
|
10
|
+
def after_response
|
11
|
+
response.request.options[:retries] ||= 0
|
12
|
+
return unless retry?(response.request)
|
13
|
+
response.request.options[:retries] += 1
|
14
|
+
current_retry = response.request.options[:retries]
|
15
|
+
begin
|
16
|
+
response.request.run!
|
17
|
+
rescue LHC::Error
|
18
|
+
return
|
19
|
+
end
|
20
|
+
response.request.response if current_retry == response.request.options[:retries]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def retry?(request)
|
26
|
+
return false if request.response.success?
|
27
|
+
return false if request.error_ignored?
|
28
|
+
return false if !request.options.dig(:retry) && !LHC::Retry.all
|
29
|
+
request.options[:retries] < max(request)
|
30
|
+
end
|
31
|
+
|
32
|
+
def max(request)
|
33
|
+
options(request).is_a?(Hash) ? options(request).fetch(:max, LHC::Retry.max) : LHC::Retry.max
|
34
|
+
end
|
35
|
+
|
36
|
+
def options(request)
|
37
|
+
@options ||= request.options.dig(:retry)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
LHC::Retry.max = 3
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'core_ext/hash/deep_transform_values'
|
4
|
+
|
5
|
+
class LHC::Rollbar < LHC::Interceptor
|
6
|
+
include ActiveSupport::Configurable
|
7
|
+
include LHC::FixInvalidEncodingConcern
|
8
|
+
|
9
|
+
def after_response
|
10
|
+
return unless Object.const_defined?('Rollbar')
|
11
|
+
return if response.success?
|
12
|
+
request = response.request
|
13
|
+
additional_params = request.options.fetch(:rollbar, {})
|
14
|
+
data = {
|
15
|
+
response: {
|
16
|
+
body: response.body,
|
17
|
+
code: response.code,
|
18
|
+
headers: response.headers,
|
19
|
+
time: response.time,
|
20
|
+
timeout?: response.timeout?
|
21
|
+
},
|
22
|
+
request: {
|
23
|
+
url: request.url,
|
24
|
+
method: request.method,
|
25
|
+
headers: request.headers,
|
26
|
+
params: request.params
|
27
|
+
}
|
28
|
+
}.merge additional_params
|
29
|
+
begin
|
30
|
+
Rollbar.warning("Status: #{response.code} URL: #{request.url}", data)
|
31
|
+
rescue Encoding::UndefinedConversionError
|
32
|
+
sanitized_data = data.deep_transform_values { |value| self.class.fix_invalid_encoding(value) }
|
33
|
+
Rollbar.warning("Status: #{response.code} URL: #{request.url}", sanitized_data)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/duration'
|
4
|
+
|
5
|
+
class LHC::Throttle < LHC::Interceptor
|
6
|
+
|
7
|
+
class OutOfQuota < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :track
|
12
|
+
end
|
13
|
+
|
14
|
+
def before_request
|
15
|
+
options = request.options.dig(:throttle)
|
16
|
+
return unless options
|
17
|
+
break_options = options.dig(:break)
|
18
|
+
return unless break_options
|
19
|
+
break_when_quota_reached! if break_options.match('%')
|
20
|
+
end
|
21
|
+
|
22
|
+
def after_response
|
23
|
+
options = response.request.options.dig(:throttle)
|
24
|
+
return unless options
|
25
|
+
return unless options.dig(:track)
|
26
|
+
self.class.track ||= {}
|
27
|
+
self.class.track[options.dig(:provider)] = {
|
28
|
+
limit: limit(options: options[:limit], response: response),
|
29
|
+
remaining: remaining(options: options[:remaining], response: response),
|
30
|
+
expires: expires(options: options[:expires], response: response)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def break_when_quota_reached!
|
37
|
+
options = request.options.dig(:throttle)
|
38
|
+
track = (self.class.track || {}).dig(options[:provider])
|
39
|
+
return if track.blank? || track[:remaining].blank? || track[:limit].blank? || track[:expires].blank?
|
40
|
+
return if Time.zone.now > track[:expires]
|
41
|
+
# avoid floats by multiplying with 100
|
42
|
+
remaining = track[:remaining] * 100
|
43
|
+
limit = track[:limit]
|
44
|
+
quota = 100 - options[:break].to_i
|
45
|
+
raise(OutOfQuota, "Reached predefined quota for #{options[:provider]}") if remaining < quota * limit
|
46
|
+
end
|
47
|
+
|
48
|
+
def limit(options:, response:)
|
49
|
+
@limit ||= begin
|
50
|
+
if options.is_a?(Integer)
|
51
|
+
options
|
52
|
+
elsif options.is_a?(Hash) && options[:header] && response.headers.present?
|
53
|
+
response.headers[options[:header]]&.to_i
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def remaining(options:, response:)
|
59
|
+
@remaining ||= begin
|
60
|
+
if options.is_a?(Hash) && options[:header] && response.headers.present?
|
61
|
+
response.headers[options[:header]]&.to_i
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def expires(options:, response:)
|
67
|
+
@expires ||= begin
|
68
|
+
if options.is_a?(Hash) && options[:header] && response.headers.present?
|
69
|
+
convert_expires(response.headers[options[:header]]&.to_i)
|
70
|
+
else
|
71
|
+
convert_expires(options)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def convert_expires(value)
|
77
|
+
if value.is_a?(Integer)
|
78
|
+
Time.zone.at(value).to_datetime
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Zipkin < LHC::Interceptor
|
4
|
+
B3_HEADERS = {
|
5
|
+
trace_id: 'X-B3-TraceId',
|
6
|
+
parent_id: 'X-B3-ParentSpanId',
|
7
|
+
span_id: 'X-B3-SpanId',
|
8
|
+
sampled: 'X-B3-Sampled',
|
9
|
+
flags: 'X-B3-Flags'
|
10
|
+
}.freeze
|
11
|
+
TRUE = '1' # true in binary annotation
|
12
|
+
|
13
|
+
def before_request
|
14
|
+
return if !dependencies? || !tracing?
|
15
|
+
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
|
16
|
+
# add headers even if the current trace_id should not be sampled
|
17
|
+
B3_HEADERS.each { |method, header| request.headers[header] = trace_id.send(method).to_s }
|
18
|
+
# only sample the current call if we're instructed to do so
|
19
|
+
start_trace! if ::Trace.tracer && trace_id.sampled?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def after_response
|
24
|
+
# only sample the current call if we're instructed to do so
|
25
|
+
return unless dependencies? && trace_id.sampled?
|
26
|
+
end_trace!
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def tracing?
|
32
|
+
ZipkinTracer::TraceContainer.current
|
33
|
+
end
|
34
|
+
|
35
|
+
def start_trace!
|
36
|
+
record_path
|
37
|
+
record_remote_endpoint
|
38
|
+
record_local_endpoint
|
39
|
+
end
|
40
|
+
|
41
|
+
def end_trace!
|
42
|
+
record_status
|
43
|
+
record_error if !response.success?
|
44
|
+
record_end
|
45
|
+
end
|
46
|
+
|
47
|
+
def trace_id
|
48
|
+
@trace_id ||= ZipkinTracer::TraceGenerator.new.next_trace_id
|
49
|
+
end
|
50
|
+
|
51
|
+
# register a new span with zipkin tracer
|
52
|
+
def span
|
53
|
+
@span ||= ::Trace.tracer.start_span(trace_id, url.path)
|
54
|
+
end
|
55
|
+
|
56
|
+
def url
|
57
|
+
@url ||= URI(request.raw.url)
|
58
|
+
end
|
59
|
+
|
60
|
+
def status
|
61
|
+
@status ||= response.code.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def service_name
|
65
|
+
@service_name ||= url.host
|
66
|
+
end
|
67
|
+
|
68
|
+
def record_local_endpoint
|
69
|
+
span.record(::Trace::Annotation::CLIENT_SEND, local_endpoint)
|
70
|
+
end
|
71
|
+
|
72
|
+
def record_remote_endpoint
|
73
|
+
span.record_tag(::Trace::BinaryAnnotation::SERVER_ADDRESS, TRUE, ::Trace::BinaryAnnotation::Type::BOOL, remote_endpoint)
|
74
|
+
end
|
75
|
+
|
76
|
+
def record_path
|
77
|
+
span.record_tag(::Trace::BinaryAnnotation::PATH, url.path, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
|
78
|
+
end
|
79
|
+
|
80
|
+
def record_end
|
81
|
+
span.record(::Trace::Annotation::CLIENT_RECV, local_endpoint)
|
82
|
+
::Trace.tracer.end_span(span)
|
83
|
+
end
|
84
|
+
|
85
|
+
def record_error
|
86
|
+
span.record_tag(::Trace::BinaryAnnotation::ERROR, status, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
|
87
|
+
end
|
88
|
+
|
89
|
+
def record_status
|
90
|
+
span.record_tag(::Trace::BinaryAnnotation::STATUS, status, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
|
91
|
+
end
|
92
|
+
|
93
|
+
def local_endpoint
|
94
|
+
@local_endpoint ||= ::Trace.default_endpoint
|
95
|
+
end
|
96
|
+
|
97
|
+
def remote_endpoint
|
98
|
+
@remote_endpoint ||= ::Trace::Endpoint.remote_endpoint(url, service_name, local_endpoint.ip_format)
|
99
|
+
end
|
100
|
+
|
101
|
+
def dependencies?
|
102
|
+
(
|
103
|
+
defined?(ZipkinTracer::TraceContainer) &&
|
104
|
+
defined?(::Trace) &&
|
105
|
+
defined?(::Trace::Annotation) &&
|
106
|
+
defined?(::Trace::BinaryAnnotation) &&
|
107
|
+
defined?(::Trace::Endpoint)
|
108
|
+
) || warn('[WARNING] Zipkin interceptor is enabled but dependencies are not found. See: https://github.com/local-ch/lhc#zipkin')
|
109
|
+
end
|
110
|
+
end
|