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,16 @@
1
+ module InfluxDB
2
+ module Rails
3
+ module Logger # rubocop:disable Style/Documentation
4
+ PREFIX = "[InfluxDB::Rails] ".freeze
5
+
6
+ private
7
+
8
+ def log(level, message)
9
+ c = InfluxDB::Rails.configuration
10
+ return if level != :error && !c.debug?
11
+
12
+ c.logger&.send(level, PREFIX + message)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module InfluxDB
2
+ module Rails
3
+ module Middleware
4
+ module HijackRenderException # rubocop:disable Style/Documentation
5
+ def render_exception(env, ex)
6
+ controller = env["action_controller.instance"] || env.controller_instance
7
+ request_data = controller.try(:influxdb_request_data) || {}
8
+ unless InfluxDB::Rails.configuration.ignore_user_agent?(request_data[:user_agent])
9
+ InfluxDB::Rails.report_exception_unless_ignorable(ex, request_data)
10
+ end
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ module InfluxDB
2
+ module Rails
3
+ module Middleware
4
+ module HijackRescueActionEverywhere # rubocop:disable Style/Documentation
5
+ def self.included(base)
6
+ base.send(:alias_method_chain, :rescue_action_in_public, :influxdb)
7
+ base.send(:alias_method_chain, :rescue_action_locally, :influxdb)
8
+ end
9
+
10
+ private
11
+
12
+ def rescue_action_in_public_with_influxdb(ex)
13
+ handle_exception(ex)
14
+ rescue_action_in_public_without_influxdb(ex)
15
+ end
16
+
17
+ def rescue_action_locally_with_influxdb(ex)
18
+ handle_exception(ex)
19
+ rescue_action_locally_without_influxdb(ex)
20
+ end
21
+
22
+ def handle_exception(ex)
23
+ request_data = influxdb_request_data || {}
24
+ return if InfluxDB::Rails.configuration.ignore_user_agent?(request_data[:user_agent])
25
+
26
+ InfluxDB::Rails.report_exception_unless_ignorable(ex, request_data)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ require "influxdb/rails/middleware/simple_subscriber"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ class RenderSubscriber < SimpleSubscriber # :nodoc:
7
+ private
8
+
9
+ def values(started, finished, payload)
10
+ super(started, finished, payload).merge(
11
+ count: payload[:count],
12
+ cache_hits: payload[:cache_hits]
13
+ ).reject { |_, value| value.nil? }
14
+ end
15
+
16
+ def tags(payload)
17
+ tags = {
18
+ location: location,
19
+ filename: payload[:identifier],
20
+ app_name: configuration.application_name,
21
+ host: Socket.gethostname
22
+ }
23
+ super(tags)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,69 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ class RequestSubscriber < Subscriber # :nodoc:
7
+ def call(_name, start, finish, _id, payload) # rubocop:disable Metrics/MethodLength
8
+ return unless enabled?
9
+
10
+ #ts = InfluxDB.convert_timestamp(finish.utc, configuration.time_precision)
11
+ ts = InfluxDB.convert_timestamp(start.utc, configuration.time_precision)
12
+ tags = tags(payload)
13
+ x_request_id = "AL_NONE"
14
+ al_request_id = "AL_NONE"
15
+ al_request_guid = "AL_NONE"
16
+ al_source = "AL_NONE"
17
+ al_request_client_id = "AL_NONE"
18
+
19
+ x_request_id = payload[:headers][:HTTP_X_REQUEST_ID] if(payload[:headers] && payload[:headers][:HTTP_X_REQUEST_ID])
20
+ al_request_id = payload[:headers][:HTTP_AL_REQUEST_ID] if(payload[:headers] && payload[:headers][:HTTP_AL_REQUEST_ID])
21
+ #al_request_id = Thread.current["al_request_id"] if(Thread.current["al_request_id"])
22
+ al_request_guid = payload[:headers][:HTTP_AL_REQUEST_GUID] if(payload[:headers] && payload[:headers][:HTTP_AL_REQUEST_GUID])
23
+ #al_request_guid = Thread.current["al_request_guid"] if(Thread.current["al_request_guid"])
24
+ al_source = payload[:headers][:HTTP_AL_SOURCE] if(payload[:headers] && payload[:headers][:HTTP_AL_SOURCE])
25
+ al_request_client_id = payload[:headers][:HTTP_AL_REQUEST_CLIENT_ID] if(payload[:headers] && payload[:headers][:HTTP_AL_REQUEST_CLIENT_ID])
26
+
27
+ al_source = Thread.current["al_source"]
28
+ al_request_client_id = Thread.current["al_request_client_id"]
29
+
30
+ begin
31
+ series(payload, start, finish).each do |series_name, value|
32
+ InfluxDB::Rails.client.write_point \
33
+ series_name,
34
+ values: { value: value, x_request_id: x_request_id, al_request_id: al_request_id, al_request_guid: al_request_guid, al_request_source_id: al_source, al_request_client_id: al_request_client_id },
35
+ tags: tags,
36
+ timestamp: ts
37
+ end
38
+ rescue StandardError => e
39
+ log :error, "[InfluxDB::Rails] Unable to write points: #{e.message}"
40
+ ensure
41
+ InfluxDB::Rails.current.reset
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def series(payload, start, finish)
48
+ {
49
+ configuration.series_name_for_controller_runtimes => ((finish - start) * 1000).ceil,
50
+ configuration.series_name_for_view_runtimes => (payload[:view_runtime] || 0).ceil,
51
+ configuration.series_name_for_db_runtimes => (payload[:db_runtime] || 0).ceil,
52
+ }
53
+ end
54
+
55
+ def tags(payload)
56
+ tags = {
57
+ method: "#{payload[:controller]}##{payload[:action]}",
58
+ status: payload[:status],
59
+ format: payload[:format],
60
+ http_method: payload[:method],
61
+ host: Socket.gethostname,
62
+ app_name: configuration.application_name,
63
+ }
64
+ super(tags)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,71 @@
1
+ require "influxdb/rails/middleware/subscriber"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ # Subscriber acts as base class for different *Subscriber classes,
7
+ # which are intended as ActiveSupport::Notifications.subscribe
8
+ # consumers.
9
+ class SimpleSubscriber < Subscriber
10
+ attr_reader :series_name
11
+
12
+ def initialize(configuration, series_name)
13
+ super(configuration)
14
+ @series_name = series_name
15
+ end
16
+
17
+ def call(_name, started, finished, _unique_id, payload)
18
+ return unless enabled?
19
+
20
+ begin
21
+ # InfluxDB::Rails.client.write_point series_name,
22
+ # values: values(started, finished, payload),
23
+ # tags: tags(payload),
24
+ # timestamp: timestamp(finished.utc)
25
+ InfluxDB::Rails.client.write_point series_name,
26
+ values: values(started, finished, payload),
27
+ tags: tags(payload),
28
+ timestamp: timestamp(started.utc)
29
+ rescue StandardError => e
30
+ log :error, "[InfluxDB::Rails] Unable to write points: #{e.message}"
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def values(started, finished, _payload)
37
+
38
+ x_request_id = "AL_NONE"
39
+ al_source = "AL_NONE"
40
+ al_request_id = "AL_NONE"
41
+ al_request_guid = "AL_NONE"
42
+ al_request_client_id = "AL_NONE"
43
+
44
+ x_request_id = _payload[:headers][:HTTP_X_REQUEST_ID] if(_payload[:headers] && _payload[:headers][:HTTP_X_REQUEST_ID])
45
+ #al_source = _payload[:headers][:HTTP_AL_SOURCE] if(_payload[:headers] && _payload[:headers][:HTTP_AL_SOURCE])
46
+ #al_request_id = _payload[:headers][:HTTP_AL_REQUEST_ID] if(_payload[:headers] && _payload[:headers][:HTTP_AL_REQUEST_ID])
47
+ #al_request_guid = _payload[:headers][:HTTP_AL_REQUEST_GUID] if(_payload[:headers] && _payload[:headers][:HTTP_AL_REQUEST_GUID])
48
+ #al_request_client_id = _payload[:headers][:HTTP_AL_REQUEST_CLIENT_ID] if(_payload[:headers] && _payload[:headers][:HTTP_AL_REQUEST_CLIENT_ID])
49
+
50
+ al_request_id = Thread.current["al_request_id"] if(Thread.current["al_request_id"])
51
+ al_request_guid = Thread.current["al_request_guid"] if(Thread.current["al_request_guid"])
52
+ al_source = Thread.current["al_source"]
53
+ al_request_client_id = Thread.current["al_request_client_id"]
54
+
55
+
56
+
57
+
58
+ { value: ((finished - started) * 1000).ceil , x_request_id: x_request_id, al_request_id: al_request_id, al_request_guid: al_request_guid, al_request_source_id: al_source, al_request_client_id: al_request_client_id}
59
+ end
60
+
61
+ def timestamp(finished)
62
+ InfluxDB.convert_timestamp(finished.utc, configuration.time_precision)
63
+ end
64
+
65
+ def enabled?
66
+ super && series_name.present?
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,35 @@
1
+ require "influxdb/rails/middleware/simple_subscriber"
2
+ require "influxdb/rails/sql/query"
3
+
4
+ module InfluxDB
5
+ module Rails
6
+ module Middleware
7
+ class SqlSubscriber < SimpleSubscriber # :nodoc:
8
+ def call(_name, started, finished, _unique_id, payload)
9
+ return unless InfluxDB::Rails::Sql::Query.new(payload).track?
10
+
11
+ super
12
+ end
13
+
14
+ private
15
+
16
+ def values(started, finished, payload)
17
+ super.merge(sql: InfluxDB::Rails::Sql::Normalizer.new(payload[:sql]).perform)
18
+ end
19
+
20
+ def tags(payload)
21
+ query = InfluxDB::Rails::Sql::Query.new(payload)
22
+ tags = {
23
+ location: location,
24
+ operation: query.operation,
25
+ class_name: query.class_name,
26
+ name: query.name,
27
+ app_name: configuration.application_name,
28
+ host: Socket.gethostname
29
+ }
30
+ super(tags)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,44 @@
1
+ require "influxdb/rails/logger"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Middleware
6
+ # Subscriber acts as base class for different *Subscriber classes,
7
+ # which are intended as ActiveSupport::Notifications.subscribe
8
+ # consumers.
9
+ class Subscriber
10
+ include InfluxDB::Rails::Logger
11
+
12
+ attr_reader :configuration
13
+
14
+ def initialize(configuration)
15
+ @configuration = configuration
16
+ end
17
+
18
+ def call(*)
19
+ raise NotImplementedError, "must be implemented in subclass"
20
+ end
21
+
22
+ private
23
+
24
+ def tags(tags)
25
+ merged_tags = tags.merge(InfluxDB::Rails.current.tags).reject { |_, value| value.nil? }
26
+ configuration.tags_middleware.call(merged_tags)
27
+ end
28
+
29
+ def enabled?
30
+ configuration.instrumentation_enabled? &&
31
+ !configuration.ignore_current_environment?
32
+ end
33
+
34
+ def location
35
+ current = InfluxDB::Rails.current
36
+ [
37
+ current.controller,
38
+ current.action,
39
+ ].reject(&:blank?).join("#")
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ require 'securerandom'
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ class Rack # rubocop:disable Style/Documentation
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ al_request_guid = env["HTTP_AL_REQUEST_GUID"] || SecureRandom.uuid
12
+ #al_request_id = SecureRandom.uuid
13
+ al_request_id = SecureRandom.uuid
14
+ al_source = "AL_NONE"
15
+ al_request_client_id = "AL_NONE"
16
+
17
+ Thread.current["al_request_id"] = al_request_id
18
+ Thread.current["al_request_guid"] = al_request_guid
19
+ Thread.current["al_source"] = env["HTTP_AL_SOURCE"] || al_source
20
+ Thread.current["al_request_client_id"] = env["HTTP_AL_REQUEST_CLIENT_ID"] || al_request_client_id
21
+
22
+ env["HTTP_AL_REQUEST_ID"] = al_request_id
23
+ env["HTTP_AL_REQUEST_GUID"] = al_request_guid
24
+ dup._call(env)
25
+ end
26
+
27
+ def _call(env)
28
+ begin
29
+ response = @app.call(env)
30
+ rescue StandardError => e
31
+ InfluxDB::Rails.transmit_unless_ignorable(e, env)
32
+ raise(e)
33
+ end
34
+
35
+ response
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ require "influxdb"
2
+ require "rails"
3
+
4
+ module InfluxDB
5
+ module Rails
6
+ class Railtie < ::Rails::Railtie # :nodoc:
7
+ initializer "influxdb.insert_rack_middleware" do |app|
8
+ app.config.middleware.insert 0, InfluxDB::Rails::Rack
9
+ end
10
+
11
+ config.after_initialize do # rubocop:disable Metrics/BlockLength
12
+ InfluxDB::Rails.configure(true, &:load_rails_defaults)
13
+
14
+ ActiveSupport.on_load(:action_controller) do
15
+ require "influxdb/rails/air_traffic_controller"
16
+ include InfluxDB::Rails::AirTrafficController
17
+ require "influxdb/rails/instrumentation"
18
+ include InfluxDB::Rails::Instrumentation
19
+ end
20
+
21
+ require "influxdb/rails/middleware/hijack_render_exception"
22
+ ::ActionDispatch::DebugExceptions.prepend InfluxDB::Rails::Middleware::HijackRenderException
23
+
24
+ if defined?(ActiveSupport::Notifications)
25
+ cache = lambda do |_, _, _, _, payload|
26
+ current = InfluxDB::Rails.current
27
+ current.controller = payload[:controller]
28
+ current.action = payload[:action]
29
+ end
30
+ ActiveSupport::Notifications.subscribe "start_processing.action_controller", &cache
31
+
32
+ c = InfluxDB::Rails.configuration
33
+ requests = Middleware::RequestSubscriber.new(c)
34
+ ActiveSupport::Notifications.subscribe "process_action.action_controller", requests
35
+
36
+
37
+ templates = Middleware::RenderSubscriber.new(c, c.series_name_for_render_template)
38
+ ActiveSupport::Notifications.subscribe "render_template.action_view", templates
39
+
40
+ partials = Middleware::RenderSubscriber.new(c, c.series_name_for_render_partial)
41
+ ActiveSupport::Notifications.subscribe "render_partial.action_view", partials
42
+
43
+ collections = Middleware::RenderSubscriber.new(c, c.series_name_for_render_collection)
44
+ ActiveSupport::Notifications.subscribe "render_collection.action_view", collections
45
+
46
+ sql = Middleware::SqlSubscriber.new(c, c.series_name_for_sql)
47
+ ActiveSupport::Notifications.subscribe "sql.active_record", sql
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ module InfluxDB
2
+ module Rails
3
+ module Sql
4
+ class Normalizer # :nodoc:
5
+ def initialize(query)
6
+ @query = query.dup
7
+ end
8
+
9
+ def perform
10
+ query.squish!
11
+ query.gsub!(/(\s(=|>|<|>=|<=|<>|!=)\s)('[^']+'|[\$\+\-\w\.]+)/, '\1xxx')
12
+ query.gsub!(/(\sIN\s)\([^\(\)]+\)/i, '\1(xxx)')
13
+ regex = /(\sBETWEEN\s)('[^']+'|[\+\-\w\.]+)(\sAND\s)('[^']+'|[\+\-\w\.]+)/i
14
+ query.gsub!(regex, '\1xxx\3xxx')
15
+ query.gsub!(/(\sVALUES\s)\(.+\)/i, '\1(xxx)')
16
+ query.gsub!(/(\s(LIKE|ILIKE|SIMILAR TO|NOT SIMILAR TO)\s)('[^']+')/i, '\1xxx')
17
+ query.gsub!(/(\s(LIMIT|OFFSET)\s)(\d+)/i, '\1xxx')
18
+ query
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :query
24
+ end
25
+ end
26
+ end
27
+ end