liveqa 1.4.6

Sign up to get free protection for your applications and to get access to all the features.
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