liveqa 1.4.6

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +40 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +10 -0
  7. data/CHANGELOG.md +5 -0
  8. data/Gemfile +12 -0
  9. data/README.md +40 -0
  10. data/Rakefile +4 -0
  11. data/lib/liveqa/api_operation/save.rb +57 -0
  12. data/lib/liveqa/api_resource.rb +123 -0
  13. data/lib/liveqa/async_handlers/base.rb +17 -0
  14. data/lib/liveqa/async_handlers/sidekiq.rb +33 -0
  15. data/lib/liveqa/config.rb +116 -0
  16. data/lib/liveqa/errors.rb +41 -0
  17. data/lib/liveqa/event.rb +22 -0
  18. data/lib/liveqa/library_name.rb +3 -0
  19. data/lib/liveqa/liveqa_object.rb +139 -0
  20. data/lib/liveqa/message.rb +50 -0
  21. data/lib/liveqa/plugins/rack/middleware.rb +121 -0
  22. data/lib/liveqa/plugins/rails/middleware_data.rb +44 -0
  23. data/lib/liveqa/plugins/rails/railtie.rb +23 -0
  24. data/lib/liveqa/plugins/sidekiq/client_middleware.rb +19 -0
  25. data/lib/liveqa/plugins/sidekiq/load.rb +14 -0
  26. data/lib/liveqa/plugins/sidekiq/server_middleware.rb +47 -0
  27. data/lib/liveqa/plugins.rb +16 -0
  28. data/lib/liveqa/request.rb +132 -0
  29. data/lib/liveqa/store.rb +49 -0
  30. data/lib/liveqa/util.rb +148 -0
  31. data/lib/liveqa/version.rb +3 -0
  32. data/lib/liveqa.rb +105 -0
  33. data/liveqa.gemspec +26 -0
  34. data/spec/lib/liveqa/async_handlers/base_spec.rb +19 -0
  35. data/spec/lib/liveqa/async_handlers/sidekiq_spec.rb +40 -0
  36. data/spec/lib/liveqa/config_spec.rb +40 -0
  37. data/spec/lib/liveqa/event_spec.rb +36 -0
  38. data/spec/lib/liveqa/liveqa_object_spec.rb +72 -0
  39. data/spec/lib/liveqa/message_spec.rb +101 -0
  40. data/spec/lib/liveqa/plugins/rack/middleware_spec.rb +25 -0
  41. data/spec/lib/liveqa/plugins/rails/middleware_data_spec.rb +67 -0
  42. data/spec/lib/liveqa/plugins/sidekiq/client_middleware_spec.rb +15 -0
  43. data/spec/lib/liveqa/plugins/sidekiq/server_middleware_spec.rb +63 -0
  44. data/spec/lib/liveqa/store_spec.rb +82 -0
  45. data/spec/lib/liveqa/util_spec.rb +123 -0
  46. data/spec/lib/liveqa_spec.rb +78 -0
  47. data/spec/spec_helper.rb +31 -0
  48. data/spec/support/rack_app.rb +12 -0
  49. metadata +176 -0
@@ -0,0 +1,50 @@
1
+ module LiveQA
2
+ ##
3
+ # == LiveQA \Message
4
+ #
5
+ # Return the base message for an event
6
+ class Message
7
+ class << self
8
+
9
+ def to_h
10
+ Util.deep_compact(
11
+ tracker_id: tracker_id,
12
+ message_id: SecureRandom.uuid,
13
+ timestamp: Time.now.utc.iso8601,
14
+ session_tracker_id: LiveQA::Store.get(:tracker_id),
15
+ library: library,
16
+ server: server,
17
+ request: LiveQA::Store.get(:request),
18
+ worker: LiveQA::Store.get(:worker),
19
+ stack: LiveQA::Store.get(:stack),
20
+ environement: LiveQA::Store.get(:environement)
21
+ )
22
+ end
23
+
24
+ private
25
+
26
+ def tracker_id
27
+ return LiveQA::Store.get(:tracker_id) if LiveQA::Store.exist?(:tracker_id)
28
+
29
+ LiveQA::Store.set(:tracker_id, SecureRandom.uuid)
30
+ end
31
+
32
+ def library
33
+ {
34
+ name: LiveQA::LIBRARY_NAME,
35
+ language: 'ruby',
36
+ version: LiveQA::VERSION
37
+ }
38
+ end
39
+
40
+ def server
41
+ {
42
+ host: Socket.gethostname,
43
+ pid: Process.pid,
44
+ software: LiveQA::Store.get(:server_software)
45
+ }
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,121 @@
1
+ require 'uri'
2
+
3
+ module LiveQA
4
+ module Plugins
5
+ module Rack
6
+ ##
7
+ # LiveQA \Plugins \Rack \Middleware
8
+ #
9
+ # Middleware for Rack
10
+ #
11
+ class Middleware
12
+
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def call(env)
18
+ request = ::Rack::Request.new(env)
19
+
20
+ store_tracker(request)
21
+ store_request_data(request)
22
+ store_stack
23
+
24
+ LiveQA::Plugins::Rails::MiddlewareData.store_data(request) if defined?(::Rails)
25
+
26
+ status, headers, body = @app.call(env)
27
+
28
+ write_cookie_tracker_id!(headers)
29
+
30
+ [status, headers, body]
31
+ ensure
32
+ LiveQA::Store.clear!
33
+ end
34
+
35
+ private
36
+
37
+ def tracker_id_name
38
+ 'liveqa_tracker_id'.freeze
39
+ end
40
+
41
+ def store_tracker(request)
42
+ LiveQA::Store.set(
43
+ :tracker_id,
44
+ request.cookies[tracker_id_name] || SecureRandom.uuid
45
+ )
46
+ end
47
+
48
+ # rubocop:disable Metrics/MethodLength
49
+ def store_request_data(request)
50
+ LiveQA::Store.set(
51
+ :request,
52
+ url: obfuscate_uri(request.url),
53
+ ssl: request.ssl?,
54
+ host: request.host,
55
+ port: request.port,
56
+ path: request.path,
57
+ referrer: obfuscate_uri(request.referrer),
58
+ method: request.request_method,
59
+ xhr: request.xhr?,
60
+ user_agent: request.user_agent,
61
+ ip: request.ip,
62
+ get_params: Util.deep_obfuscate_value(
63
+ request.GET,
64
+ LiveQA.configurations.obfuscated_fields
65
+ ),
66
+ post_params: Util.deep_obfuscate_value(
67
+ request.POST,
68
+ LiveQA.configurations.obfuscated_fields
69
+ )
70
+ )
71
+
72
+ LiveQA::Store.bulk_set(
73
+ server_software: request.env['SERVER_SOFTWARE']
74
+ )
75
+ end
76
+ # rubocop:enable Metrics/MethodLength
77
+
78
+ def store_stack
79
+ stack = LiveQA::Store.get(:stack) || []
80
+
81
+ LiveQA::Store.set(
82
+ :stack,
83
+ stack.push(
84
+ name: 'rack',
85
+ version: ::Rack.version
86
+ )
87
+ )
88
+ end
89
+
90
+ def write_cookie_tracker_id!(headers)
91
+ ::Rack::Utils.set_cookie_header!(
92
+ headers || {},
93
+ tracker_id_name,
94
+ value: LiveQA::Store.get(:tracker_id),
95
+ path: '/'
96
+ )
97
+ end
98
+
99
+ def obfuscate_uri(url)
100
+ uri = URI.parse(url)
101
+
102
+ params =
103
+ Util.deep_obfuscate_value(
104
+ ::Rack::Utils.parse_query(uri.query),
105
+ LiveQA.configurations.obfuscated_fields,
106
+ 'HIDDEN'
107
+ )
108
+
109
+ return url if params.blank?
110
+
111
+ uri.merge(
112
+ "?#{::Rack::Utils.build_query(params)}"
113
+ ).to_s
114
+ rescue
115
+ ''
116
+ end
117
+
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,44 @@
1
+ module LiveQA
2
+ module Plugins
3
+ module Rails
4
+ class MiddlewareData
5
+
6
+ class << self
7
+
8
+ def store_data(request)
9
+ LiveQA::Store.bulk_set(
10
+ environement: ::Rails.env
11
+ )
12
+ store_request_data(request)
13
+ store_framework
14
+ end
15
+
16
+ private
17
+
18
+ def store_request_data(request)
19
+ object = LiveQA::Store.get(:request) || {}
20
+
21
+ LiveQA::Store.set(
22
+ :request,
23
+ object.merge(id: request.env['action_dispatch.request_id'])
24
+ )
25
+ end
26
+
27
+ def store_framework
28
+ stack = LiveQA::Store.get(:stack) || []
29
+
30
+ LiveQA::Store.set(
31
+ :stack,
32
+ stack.push(
33
+ name: 'rails',
34
+ version: ::Rails.version
35
+ )
36
+ )
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ module LiveQA
2
+ module Plugins
3
+ module Rails
4
+ class Railtie < ::Rails::Railtie
5
+
6
+ initializer 'liveqa.middleware' do |app|
7
+ app.config.middleware.use(LiveQA::Plugins::Rack::Middleware)
8
+
9
+ if ActiveSupport.const_defined?(:Reloader) && ActiveSupport::Reloader.respond_to?(:to_complete)
10
+ ActiveSupport::Reloader.to_complete do
11
+ LiveQA::Store.clear!
12
+ end
13
+ elsif ActionDispatch.const_defined?(:Reloader) && ActionDispatch::Reloader.respond_to?(:to_cleanup)
14
+ ActionDispatch::Reloader.to_cleanup do
15
+ LiveQA::Store.clear!
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module LiveQA
2
+ module Plugins
3
+ module Sidekiq
4
+ ##
5
+ # LiveQA \Plugins \Sidekiq \Client \Middleware
6
+ #
7
+ # Client middleware for sidekiq
8
+ #
9
+ class ClientMiddleware
10
+
11
+ def call(_worker_class, job, _queue, _redis_pool)
12
+ job['liveqa_store'] = LiveQA::Store.store
13
+ yield
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'liveqa/plugins/sidekiq/client_middleware'
2
+ require 'liveqa/plugins/sidekiq/server_middleware'
3
+
4
+ Sidekiq.configure_client do |config|
5
+ config.client_middleware do |chain|
6
+ chain.add LiveQA::Plugins::Sidekiq::ClientMiddleware
7
+ end
8
+ end
9
+
10
+ Sidekiq.configure_server do |config|
11
+ config.server_middleware do |chain|
12
+ chain.add LiveQA::Plugins::Sidekiq::ServerMiddleware
13
+ end
14
+ end
@@ -0,0 +1,47 @@
1
+ module LiveQA
2
+ module Plugins
3
+ module Sidekiq
4
+ ##
5
+ # LiveQA \Plugins \Sidekiq \Server \Middleware
6
+ #
7
+ # Server middleware for sidekiq
8
+ #
9
+ class ServerMiddleware
10
+
11
+ def call(_worker, job, _queue)
12
+ LiveQA::Store.load_from_hash(job['liveqa_store'])
13
+ store_worker_data(job)
14
+ yield
15
+ ensure
16
+ LiveQA::Store.clear!
17
+ end
18
+
19
+ private
20
+
21
+ def store_worker_data(job)
22
+ LiveQA::Store.set(
23
+ :worker,
24
+ name: 'sidekiq',
25
+ version: ::Sidekiq::VERSION,
26
+ queue: job['queue'],
27
+ class: job['class'],
28
+ id: job['jid'],
29
+ created_at: convert_to_iso8601(job['created_at']),
30
+ process_at: convert_to_iso8601(job['enqueued_at']),
31
+ args: job['args'],
32
+ retried: job['retry_count'].is_a?(Integer),
33
+ retry_number: job['retry_count'],
34
+ failed_at: convert_to_iso8601(job['failed_at'])
35
+ )
36
+ end
37
+
38
+ def convert_to_iso8601(int_time)
39
+ Time.at(int_time).utc.iso8601
40
+ rescue
41
+ nil
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ ##
2
+ # Rack & Rails
3
+ #
4
+ if defined?(Rack)
5
+ require 'liveqa/plugins/rack/middleware'
6
+
7
+ if defined?(Rails)
8
+ require 'liveqa/plugins/rails/railtie'
9
+ require 'liveqa/plugins/rails/middleware_data'
10
+ end
11
+ end
12
+
13
+ ##
14
+ # Sidekiq
15
+ #
16
+ require 'liveqa/plugins/sidekiq/load' if defined?(Sidekiq)
@@ -0,0 +1,132 @@
1
+ module LiveQA
2
+ ##
3
+ # == LiveQA \Request
4
+ #
5
+ # Build request
6
+ class Request
7
+ class << self
8
+
9
+ def execute(params = {})
10
+ klass = new(params)
11
+
12
+ if params[:method] == :post
13
+ klass.post
14
+ elsif params[:method] == :put
15
+ klass.put
16
+ elsif params[:method] == :delete
17
+ klass.delete
18
+ elsif params[:method] == :get
19
+ klass.get
20
+ else
21
+ raise LiveQA::UnknownRequestMethod, "#{params[:method]} haven't been implemented"
22
+ end
23
+ end
24
+
25
+ def post(params = {})
26
+ new(params).post
27
+ end
28
+
29
+ def put(params = {})
30
+ new(params).put
31
+ end
32
+
33
+ def get(params = {})
34
+ new(params).get
35
+ end
36
+
37
+ def delete(params = {})
38
+ new(params).delete
39
+ end
40
+ end
41
+
42
+ attr_reader :params, :http, :request
43
+
44
+ def initialize(params = {})
45
+ @params = params
46
+ @http = Net::HTTP.new(uri.host, uri.port, uri_proxy.host, uri_proxy.port)
47
+
48
+ setup_ssl if params[:use_ssl]
49
+ end
50
+
51
+ def post
52
+ @request = Net::HTTP::Post.new(uri.path)
53
+
54
+ set_header
55
+ request.body = params[:payload]
56
+
57
+ handle_request
58
+ end
59
+
60
+ def put
61
+ @request = Net::HTTP::Put.new(uri.path)
62
+
63
+ request.body = params[:payload]
64
+
65
+ handle_request
66
+ end
67
+
68
+ def get
69
+ @request = Net::HTTP::Get.new(uri)
70
+
71
+ set_header
72
+
73
+ handle_request
74
+ end
75
+
76
+ def delete
77
+ @request = Net::HTTP::Delete.new(uri.path)
78
+
79
+ request.body = params[:payload]
80
+
81
+ handle_request
82
+ end
83
+
84
+ private
85
+
86
+ def handle_request
87
+ response = http.request(request)
88
+
89
+ case [response.code_type]
90
+ when [Net::HTTPOK], [Net::HTTPCreated], [Net::HTTPAccepted]
91
+ response
92
+ when [Net::HTTPUnauthorized]
93
+ raise RequestError.new(response, message: 'Unauthorized, please check your api key')
94
+ when [Net::HTTPNotFound]
95
+ raise RequestError.new(response, message: 'Requested Resource not found')
96
+ when [Net::HTTPRequestTimeOut]
97
+ raise RequestError.new(response, message: 'Server timeout, verify status of the server')
98
+ when [Net::HTTPInternalServerError]
99
+ raise RequestError.new(response, message: 'Server Error, please check with LiveQA')
100
+ else
101
+ raise RequestError, response
102
+ end
103
+ end
104
+
105
+ def set_header
106
+ params[:headers].each do |(type, value)|
107
+ formated_type = type.to_s.split(/_/).map(&:capitalize).join('-')
108
+ request[formated_type] = value
109
+ end
110
+ end
111
+
112
+ def setup_ssl
113
+ http.use_ssl = true
114
+ http.ca_file = params[:ca_file]
115
+ http.verify_mode = params[:verify_mode]
116
+ end
117
+
118
+ def uri
119
+ @uri ||= URI(params[:url])
120
+ end
121
+
122
+ def uri_proxy
123
+ @uri_proxy ||=
124
+ if params[:proxy]
125
+ URI(params[:proxy])
126
+ else
127
+ OpenStruct.new
128
+ end
129
+ end
130
+
131
+ end
132
+ end
@@ -0,0 +1,49 @@
1
+ module LiveQA
2
+ ##
3
+ # == LiveQA \Store
4
+ #
5
+ # Store environement data
6
+ #
7
+ class Store
8
+ class << self
9
+
10
+ def store
11
+ Thread.current[:request_store] ||= {}
12
+ end
13
+
14
+ def clear!
15
+ Thread.current[:request_store] = {}
16
+ end
17
+
18
+ def load_from_hash(object = {})
19
+ clear!
20
+ bulk_set(Util.deep_symbolize_key(object)) if object.is_a?(Hash)
21
+ end
22
+
23
+ def get(key)
24
+ store[key]
25
+ end
26
+ alias [] get
27
+
28
+ def set(key, value)
29
+ store[key] = value
30
+ end
31
+ alias []= set
32
+
33
+ def bulk_set(attributes = {})
34
+ attributes.each do |(key, value)|
35
+ set(key, value)
36
+ end
37
+ end
38
+
39
+ def exist?(key)
40
+ store.key?(key)
41
+ end
42
+
43
+ def delete(key, &block)
44
+ store.delete(key, &block)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,148 @@
1
+ module LiveQA
2
+ module Util
3
+ class << self
4
+
5
+ DATE_FORMAT = '%Y/%m/%d'.freeze
6
+ OBFUSCATED = '[HIDDEN]'.freeze
7
+
8
+ ##
9
+ # Remove nil value from hash
10
+ #
11
+ # @return [Hash]
12
+ def compact(hash)
13
+ hash.reject { |_, value| value.nil? }
14
+ end
15
+
16
+ ##
17
+ # Remove nil value from hash recursively
18
+ #
19
+ # @return [Hash]
20
+ def deep_compact(object)
21
+ object.each_with_object({}) do |(key, value), result|
22
+ if value.is_a?(Hash)
23
+ value = deep_compact(value)
24
+ result[key] = value if !value.nil? && !value.empty?
25
+ else
26
+ result[key] = value unless value.nil?
27
+ end
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Encodes a hash of parameters in a way that's suitable for use as query
33
+ # parameters in a URI or as form parameters in a request body. This mainly
34
+ # involves escaping special characters from parameter keys and values (e.g.
35
+ # `&`).
36
+ def encode_parameters(params = {})
37
+ params.map { |k, v| "#{url_encode(k)}=#{url_encode(v)}" }.join('&')
38
+ end
39
+
40
+ ##
41
+ # Encodes a string in a way that makes it suitable for use in a set of
42
+ # query parameters in a URI or in a set of form parameters in a request
43
+ # body.
44
+ def url_encode(key)
45
+ CGI.escape(key.to_s).gsub('%5B', '[').gsub('%5D', ']')
46
+ end
47
+
48
+ ##
49
+ # Convert string to underscore
50
+ #
51
+ # @param [String]
52
+ #
53
+ # @example
54
+ # underscore('MyModel') => 'my_model'
55
+ def underscore(string)
56
+ string
57
+ .gsub(/::/, '/')
58
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
59
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
60
+ .tr('-', '_')
61
+ .downcase
62
+ end
63
+
64
+ ##
65
+ # Deep convert hash to underscore case keys
66
+ #
67
+ # @param [Hash] hash to transform
68
+ #
69
+ # @return [Hash] transformed
70
+ def deep_underscore_key(hash_object)
71
+ deep_transform_keys_in_object(hash_object) do |key|
72
+ begin
73
+ underscore(key).to_sym
74
+ rescue
75
+ key
76
+ end
77
+ end
78
+ end
79
+
80
+ ##
81
+ # Deep convert hash to string keys
82
+ #
83
+ # @param [Hash] hash to transform
84
+ #
85
+ # @return [Hash] transformed
86
+ def deep_stringify_key(hash_object)
87
+ deep_transform_keys_in_object(hash_object) do |key|
88
+ begin
89
+ key.to_s
90
+ rescue
91
+ key
92
+ end
93
+ end
94
+ end
95
+
96
+ ##
97
+ # Deep convert hash to symbol keys
98
+ #
99
+ # @param [Hash] hash to transform
100
+ #
101
+ # @return [Hash] transformed
102
+ def deep_symbolize_key(hash_object)
103
+ deep_transform_keys_in_object(hash_object) do |key|
104
+ begin
105
+ key.to_sym
106
+ rescue
107
+ key
108
+ end
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Deep remove key from hash
114
+ #
115
+ # @param [Hash] hash to obfuscate
116
+ #
117
+ # @return [Hash] hash obfuscated
118
+ def deep_obfuscate_value(object, fields, obfuscate_name = OBFUSCATED)
119
+ case object
120
+ when Hash
121
+ object.each_with_object({}) do |(key, value), result|
122
+ result[key] = fields.include?(key.to_s) ? obfuscate_name : deep_obfuscate_value(value, fields)
123
+ end
124
+ when Array
125
+ object.map { |e| deep_obfuscate_value(e, fields) }
126
+ else
127
+ object
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ def deep_transform_keys_in_object(object, &block)
134
+ case object
135
+ when Hash
136
+ object.each_with_object({}) do |(key, value), result|
137
+ result[yield(key)] = deep_transform_keys_in_object(value, &block)
138
+ end
139
+ when Array
140
+ object.map { |e| deep_transform_keys_in_object(e, &block) }
141
+ else
142
+ object
143
+ end
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,3 @@
1
+ module LiveQA # :nodoc:
2
+ VERSION = '1.4.6'.freeze
3
+ end