asklytics-influxdb-rails 1.0.0.beta3

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +78 -0
  5. data/.travis.yml +37 -0
  6. data/CHANGELOG.md +127 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +291 -0
  10. data/Rakefile +34 -0
  11. data/config.ru +7 -0
  12. data/gemfiles/Gemfile.rails-4.2.x +7 -0
  13. data/gemfiles/Gemfile.rails-5.0.x +7 -0
  14. data/gemfiles/Gemfile.rails-5.1.x +7 -0
  15. data/gemfiles/Gemfile.rails-5.2.x +7 -0
  16. data/influxdb-rails.gemspec +52 -0
  17. data/lib/httplog.rb +8 -0
  18. data/lib/influxdb-rails.rb +134 -0
  19. data/lib/influxdb/rails/air_traffic_controller.rb +41 -0
  20. data/lib/influxdb/rails/backtrace.rb +44 -0
  21. data/lib/influxdb/rails/configuration.rb +263 -0
  22. data/lib/influxdb/rails/context.rb +42 -0
  23. data/lib/influxdb/rails/exception_presenter.rb +94 -0
  24. data/lib/influxdb/rails/httplog/adapters/ethon.rb +62 -0
  25. data/lib/influxdb/rails/httplog/adapters/excon.rb +67 -0
  26. data/lib/influxdb/rails/httplog/adapters/http.rb +64 -0
  27. data/lib/influxdb/rails/httplog/adapters/httpclient.rb +76 -0
  28. data/lib/influxdb/rails/httplog/adapters/net_http.rb +53 -0
  29. data/lib/influxdb/rails/httplog/adapters/patron.rb +44 -0
  30. data/lib/influxdb/rails/httplog/helpers/al_helper.rb +12 -0
  31. data/lib/influxdb/rails/httplog/http_configuration.rb +55 -0
  32. data/lib/influxdb/rails/httplog/http_log.rb +332 -0
  33. data/lib/influxdb/rails/instrumentation.rb +34 -0
  34. data/lib/influxdb/rails/logger.rb +16 -0
  35. data/lib/influxdb/rails/middleware/hijack_render_exception.rb +16 -0
  36. data/lib/influxdb/rails/middleware/hijack_rescue_action_everywhere.rb +31 -0
  37. data/lib/influxdb/rails/middleware/render_subscriber.rb +28 -0
  38. data/lib/influxdb/rails/middleware/request_subscriber.rb +69 -0
  39. data/lib/influxdb/rails/middleware/simple_subscriber.rb +71 -0
  40. data/lib/influxdb/rails/middleware/sql_subscriber.rb +35 -0
  41. data/lib/influxdb/rails/middleware/subscriber.rb +44 -0
  42. data/lib/influxdb/rails/rack.rb +39 -0
  43. data/lib/influxdb/rails/railtie.rb +52 -0
  44. data/lib/influxdb/rails/sql/normalizer.rb +27 -0
  45. data/lib/influxdb/rails/sql/query.rb +32 -0
  46. data/lib/influxdb/rails/version.rb +5 -0
  47. data/lib/rails/generators/influxdb/influxdb_generator.rb +15 -0
  48. data/lib/rails/generators/influxdb/templates/initializer.rb +11 -0
  49. data/spec/controllers/widgets_controller_spec.rb +15 -0
  50. data/spec/integration/exceptions_spec.rb +37 -0
  51. data/spec/integration/integration_helper.rb +1 -0
  52. data/spec/integration/metrics_spec.rb +28 -0
  53. data/spec/shared_examples/tags.rb +42 -0
  54. data/spec/spec_helper.rb +31 -0
  55. data/spec/support/rails4/app.rb +44 -0
  56. data/spec/support/rails5/app.rb +44 -0
  57. data/spec/support/views/widgets/_item.html.erb +1 -0
  58. data/spec/support/views/widgets/index.html.erb +5 -0
  59. data/spec/unit/backtrace_spec.rb +85 -0
  60. data/spec/unit/configuration_spec.rb +125 -0
  61. data/spec/unit/context_spec.rb +40 -0
  62. data/spec/unit/exception_presenter_spec.rb +23 -0
  63. data/spec/unit/influxdb_rails_spec.rb +78 -0
  64. data/spec/unit/middleware/render_subscriber_spec.rb +92 -0
  65. data/spec/unit/middleware/request_subscriber_spec.rb +91 -0
  66. data/spec/unit/middleware/sql_subscriber_spec.rb +81 -0
  67. data/spec/unit/sql/normalizer_spec.rb +15 -0
  68. data/spec/unit/sql/query_spec.rb +29 -0
  69. metadata +487 -0
@@ -0,0 +1,42 @@
1
+ module InfluxDB
2
+ module Rails
3
+ class Context # rubocop:disable Style/Documentation
4
+ def controller
5
+ Thread.current[:_influxdb_rails_controller]
6
+ end
7
+
8
+ def controller=(value)
9
+ Thread.current[:_influxdb_rails_controller] = value
10
+ end
11
+
12
+ def action
13
+ Thread.current[:_influxdb_rails_action]
14
+ end
15
+
16
+ def action=(value)
17
+ Thread.current[:_influxdb_rails_action] = value
18
+ end
19
+
20
+ def location
21
+ [
22
+ controller,
23
+ action,
24
+ ].reject(&:blank?).join("#")
25
+ end
26
+
27
+ def reset
28
+ Thread.current[:_influxdb_rails_controller] = nil
29
+ Thread.current[:_influxdb_rails_action] = nil
30
+ Thread.current[:_influxdb_rails_tags] = nil
31
+ end
32
+
33
+ def tags
34
+ Thread.current[:_influxdb_rails_tags] || {}
35
+ end
36
+
37
+ def tags=(tags)
38
+ Thread.current[:_influxdb_rails_tags] = tags
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,94 @@
1
+ require "base64"
2
+ require "socket"
3
+ require "json"
4
+
5
+ module InfluxDB
6
+ module Rails
7
+ class ExceptionPresenter # rubocop:disable Style/Documentation
8
+ attr_reader :exception
9
+ attr_reader :backtrace
10
+ attr_reader :params
11
+ attr_reader :session_data
12
+ attr_reader :current_user
13
+ attr_reader :controller
14
+ attr_reader :action
15
+ attr_reader :request_url
16
+ attr_reader :referer
17
+ attr_reader :remote_ip
18
+ attr_reader :user_agent
19
+ attr_reader :custom_data
20
+
21
+ def initialize(ex, params = {})
22
+ ex = ex.continued_exception if ex.respond_to?(:continued_exception)
23
+ ex = ex.original_exception if ex.respond_to?(:original_exception)
24
+
25
+ @exception = ex.is_a?(String) ? Exception.new(ex) : ex
26
+ @backtrace = InfluxDB::Rails::Backtrace.new(@exception.backtrace)
27
+ @dimensions = {}
28
+ configure_from_params(params)
29
+
30
+ @environment_variables = ENV.to_hash || {}
31
+ end
32
+
33
+ def context # rubocop:disable Metrics/MethodLength
34
+ c = {
35
+ application_name: InfluxDB::Rails.configuration.application_name,
36
+ application_root: InfluxDB::Rails.configuration.application_root,
37
+ framework: InfluxDB::Rails.configuration.framework,
38
+ framework_version: InfluxDB::Rails.configuration.framework_version,
39
+ language: "Ruby",
40
+ language_version: "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}",
41
+ custom_data: @custom_data,
42
+ }
43
+
44
+ InfluxDB::Rails.configuration.add_custom_exception_data(self)
45
+ c
46
+ end
47
+
48
+ def dimensions
49
+ {
50
+ class: @exception.class.to_s,
51
+ method: "#{@controller}##{@action}",
52
+ filename: File.basename(@backtrace.lines.first.try(:file)),
53
+ server: Socket.gethostname,
54
+ status: "open",
55
+ }.merge(@dimensions)
56
+ end
57
+
58
+ def values
59
+ {
60
+ exception_message: @exception.message,
61
+ exception_backtrace: JSON.generate(@backtrace.to_a),
62
+ }
63
+ end
64
+
65
+ def request_data
66
+ {
67
+ params: @params,
68
+ session_data: @session_data,
69
+ controller: @controller,
70
+ action: @action,
71
+ request_url: @request_url,
72
+ referer: @referer,
73
+ remote_ip: @remote_ip,
74
+ user_agent: @user_agent,
75
+ }
76
+ end
77
+
78
+ private
79
+
80
+ def configure_from_params(params)
81
+ @params = params[:params]
82
+ @session_data = params[:session_data] || {}
83
+ @current_user = params[:current_user]
84
+ @controller = params[:controller]
85
+ @action = params[:action]
86
+ @request_url = params[:request_url]
87
+ @user_agent = params[:user_agent]
88
+ @referer = params[:referer]
89
+ @remote_ip = params[:remote_ip]
90
+ @custom_data = params[:custom_data] || {}
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Ethon)
4
+ module Ethon
5
+ class Easy
6
+ attr_accessor :action_name
7
+
8
+ module Http
9
+ alias orig_http_request http_request
10
+ def http_request(url, action_name, options = {})
11
+ @http_log = options.merge(method: action_name) # remember this for compact logging
12
+ orig_http_request(url, action_name, options)
13
+ end
14
+ end
15
+
16
+ module Operations
17
+ alias orig_perform perform
18
+ def perform
19
+ return orig_perform unless HttpLog.url_approved?(url)
20
+
21
+ bm = Benchmark.realtime { orig_perform }
22
+
23
+ # Not sure where the actual status code is stored - so let's
24
+ # extract it from the response header.
25
+ encoding = response_headers.scan(/Content-Encoding: (\S+)/).flatten.first
26
+ content_type = response_headers.scan(/Content-Type: (\S+(; charset=\S+)?)/).flatten.first
27
+
28
+ # Hard to believe that Ethon wouldn't parse out the headers into
29
+ # an array; probably overlooked it. Anyway, let's do it ourselves:
30
+ headers = response_headers.split(/\r?\n/).drop(1)
31
+
32
+ # HttpLog.call(
33
+ # method: @http_log[:method],
34
+ # url: @url,
35
+ # request_body: @http_log[:body],
36
+ # request_headers: @http_log[:headers],
37
+ # response_code: @return_code,
38
+ # response_body: response_body,
39
+ # response_headers: headers.map { |header| header.split(/:\s/) }.to_h,
40
+ # benchmark: bm,
41
+ # encoding: encoding,
42
+ # content_type: content_type
43
+ # )
44
+
45
+ HttpLog.save_in_db(
46
+ method: @http_log[:method],
47
+ url: @url,
48
+ request_body: @http_log[:body],
49
+ request_headers: @http_log[:headers],
50
+ response_code: @return_code,
51
+ response_body: response_body,
52
+ response_headers: headers.map { |header| header.split(/:\s/) }.to_h,
53
+ benchmark: bm,
54
+ encoding: encoding,
55
+ content_type: content_type
56
+ )
57
+ return_code
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Excon)
4
+ module Excon
5
+ module HttpLogHelper
6
+ def httplog_url(datum)
7
+ @httplog_url ||= ["#{datum[:scheme]}://#{datum[:host]}:#{datum[:port]}#{datum[:path]}", datum[:query]].compact.join('?')
8
+ end
9
+ end
10
+
11
+ class Socket
12
+ include Excon::HttpLogHelper
13
+ alias orig_connect connect
14
+ def connect
15
+ host = @data[:proxy] ? @data[:proxy][:host] : @data[:host]
16
+ port = @data[:proxy] ? @data[:proxy][:port] : @data[:port]
17
+ HttpLog.log_connection(host, port) if ::HttpLog.url_approved?(httplog_url(@data))
18
+ orig_connect
19
+ end
20
+ end
21
+
22
+ class Connection
23
+ include Excon::HttpLogHelper
24
+ attr_reader :bm
25
+
26
+ alias orig_request request
27
+ def request(params, &block)
28
+ result = nil
29
+ bm = Benchmark.realtime do
30
+ result = orig_request(params, &block)
31
+ end
32
+
33
+ url = httplog_url(@data)
34
+ return result unless HttpLog.url_approved?(url)
35
+
36
+ headers = result[:headers] || {}
37
+
38
+ # HttpLog.call(
39
+ # method: params[:method],
40
+ # url: url,
41
+ # request_body: @data[:body],
42
+ # request_headers: @data[:headers] || {},
43
+ # response_code: result[:status],
44
+ # response_body: result[:body],
45
+ # response_headers: headers,
46
+ # benchmark: bm,
47
+ # encoding: headers['Content-Encoding'],
48
+ # content_type: headers['Content-Type']
49
+ # )
50
+
51
+ HttpLog.save_in_db(
52
+ method: params[:method],
53
+ url: url,
54
+ request_body: @data[:body],
55
+ request_headers: @data[:headers] || {},
56
+ response_code: result[:status],
57
+ response_body: result[:body],
58
+ response_headers: headers,
59
+ benchmark: bm,
60
+ encoding: headers['Content-Encoding'],
61
+ content_type: headers['Content-Type']
62
+ )
63
+ result
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(::HTTP::Client) && defined?(::HTTP::Connection)
4
+ module ::HTTP # rubocop:disable Style/ClassAndModuleChildren
5
+ class Client
6
+ request_method = respond_to?('make_request') ? 'make_request' : 'perform'
7
+ orig_request_method = "orig_#{request_method}"
8
+ alias_method(orig_request_method, request_method) unless method_defined?(orig_request_method)
9
+
10
+ define_method request_method do |req, options|
11
+ bm = Benchmark.realtime do
12
+ @response = send(orig_request_method, req, options)
13
+ end
14
+
15
+ if HttpLog.url_approved?(req.uri)
16
+ body = if defined?(::HTTP::Request::Body)
17
+ req.body.respond_to?(:source) ? req.body.source : req.body.instance_variable_get(:@body)
18
+ else
19
+ req.body
20
+ end
21
+
22
+ # HttpLog.call(
23
+ # method: req.verb,
24
+ # url: req.uri,
25
+ # request_body: body,
26
+ # request_headers: req.headers,
27
+ # response_code: @response.code,
28
+ # response_body: @response.body,
29
+ # response_headers: @response.headers,
30
+ # benchmark: bm,
31
+ # encoding: @response.headers['Content-Encoding'],
32
+ # content_type: @response.headers['Content-Type']
33
+ # )
34
+
35
+ HttpLog.save_in_db(
36
+ method: req.verb,
37
+ url: req.uri,
38
+ request_body: body,
39
+ request_headers: req.headers,
40
+ response_code: @response.code,
41
+ response_body: @response.body,
42
+ response_headers: @response.headers,
43
+ benchmark: bm,
44
+ encoding: @response.headers['Content-Encoding'],
45
+ content_type: @response.headers['Content-Type']
46
+ )
47
+
48
+ body.rewind if body.respond_to?(:rewind)
49
+ end
50
+
51
+ @response
52
+ end
53
+ end
54
+
55
+ class Connection
56
+ alias orig_initialize initialize unless method_defined?(:orig_initialize)
57
+
58
+ def initialize(req, options)
59
+ HttpLog.log_connection(req.uri.host, req.uri.port) if HttpLog.url_approved?(req.uri)
60
+ orig_initialize(req, options)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(::HTTPClient)
4
+ class HTTPClient
5
+ private
6
+
7
+ alias orig_do_get_block do_get_block
8
+
9
+ def do_get_block(req, proxy, conn, &block)
10
+ retryable_response = nil
11
+ bm = Benchmark.realtime do
12
+ begin
13
+ orig_do_get_block(req, proxy, conn, &block)
14
+ rescue RetryableResponse => e
15
+ retryable_response = e
16
+ end
17
+ end
18
+
19
+ if HttpLog.url_approved?(req.header.request_uri)
20
+ res = conn.pop
21
+
22
+ # HttpLog.call(
23
+ # method: req.header.request_method,
24
+ # url: req.header.request_uri,
25
+ # request_body: req.body,
26
+ # request_headers: req.headers,
27
+ # response_code: res.status_code,
28
+ # response_body: res.body,
29
+ # response_headers: res.headers,
30
+ # benchmark: bm,
31
+ # encoding: res.headers['Content-Encoding'],
32
+ # content_type: res.headers['Content-Type']
33
+ # )
34
+
35
+ HttpLog.save_in_db(
36
+ method: req.header.request_method,
37
+ url: req.header.request_uri,
38
+ request_body: req.body,
39
+ request_headers: req.headers,
40
+ response_code: res.status_code,
41
+ response_body: res.body,
42
+ response_headers: res.headers,
43
+ benchmark: bm,
44
+ encoding: res.headers['Content-Encoding'],
45
+ content_type: res.headers['Content-Type']
46
+ )
47
+ conn.push(res)
48
+ end
49
+
50
+ raise retryable_response unless retryable_response.nil?
51
+ end
52
+
53
+ class Session
54
+ alias orig_create_socket create_socket
55
+
56
+ # up to version 2.6, the method signature is `create_socket(site)`; after that,
57
+ # it's `create_socket(hort, port)`
58
+ if instance_method(:create_socket).arity == 1
59
+ def create_socket(site)
60
+ if HttpLog.url_approved?("#{site.host}:#{site.port}")
61
+ HttpLog.log_connection(site.host, site.port)
62
+ end
63
+ orig_create_socket(site)
64
+ end
65
+
66
+ else
67
+ def create_socket(host, port)
68
+ if HttpLog.url_approved?("#{host}:#{port}")
69
+ HttpLog.log_connection(host, port)
70
+ end
71
+ orig_create_socket(host, port)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ require 'securerandom'
3
+ module Net
4
+ class HTTP
5
+ alias orig_request request unless method_defined?(:orig_request)
6
+ alias orig_connect connect unless method_defined?(:orig_connect)
7
+
8
+ def request(req, body = nil, &block)
9
+ url = "http://#{@address}:#{@port}#{req.path}"
10
+
11
+ #req['al-source'] = req['X-Request-Id'] if req['X-Request-Id']
12
+ req['Al-Request-Client-Id'] = SecureRandom.uuid
13
+ req['al-source'] = Thread.current["al_request_id"] if Thread.current["al_request_id"]
14
+
15
+
16
+
17
+ timestamp = nil
18
+
19
+ bm = Benchmark.realtime do
20
+ timestamp= Time.now.to_i
21
+ @response = orig_request(req, body, &block)
22
+ end
23
+
24
+ c = InfluxDB::Rails.configuration
25
+ is_influxdb = (c.influxdb_hosts.include? @address ) && (@port == c.influxdb_port)
26
+
27
+
28
+ if !is_influxdb && (HttpLog.url_approved?(url) && started?)
29
+ HttpLog.save_in_db(
30
+ method: req.method,
31
+ url: url,
32
+ timestamp: timestamp,
33
+ request_body: req.body.nil? || req.body.empty? ? body : req.body,
34
+ request_headers: req.each_header.collect,
35
+ response_code: @response.code,
36
+ response_body: @response.body,
37
+ response_headers: @response.each_header.collect,
38
+ benchmark: bm,
39
+ encoding: @response['Content-Encoding'],
40
+ content_type: @response['Content-Type']
41
+ )
42
+ end
43
+
44
+ @response
45
+ end
46
+
47
+ def connect
48
+ HttpLog.log_connection(@address, @port) if !started? && HttpLog.url_approved?("#{@address}:#{@port}")
49
+
50
+ orig_connect
51
+ end
52
+ end
53
+ end