experimental-influxdb-rails 1.0.0.beta5

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +78 -0
  5. data/.travis.yml +37 -0
  6. data/CHANGELOG.md +133 -0
  7. data/Gemfile +9 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +292 -0
  10. data/Rakefile +34 -0
  11. data/config.ru +7 -0
  12. data/experimental-influxdb-rails.gemspec +35 -0
  13. data/gemfiles/Gemfile.rails-4.2.x +7 -0
  14. data/gemfiles/Gemfile.rails-5.0.x +7 -0
  15. data/gemfiles/Gemfile.rails-5.1.x +7 -0
  16. data/gemfiles/Gemfile.rails-5.2.x +7 -0
  17. data/lib/experimental-influxdb-rails.rb +123 -0
  18. data/lib/influxdb/rails/air_traffic_controller.rb +41 -0
  19. data/lib/influxdb/rails/backtrace.rb +44 -0
  20. data/lib/influxdb/rails/configuration.rb +211 -0
  21. data/lib/influxdb/rails/context.rb +51 -0
  22. data/lib/influxdb/rails/exception_presenter.rb +94 -0
  23. data/lib/influxdb/rails/instrumentation.rb +34 -0
  24. data/lib/influxdb/rails/logger.rb +16 -0
  25. data/lib/influxdb/rails/middleware/hijack_render_exception.rb +16 -0
  26. data/lib/influxdb/rails/middleware/hijack_rescue_action_everywhere.rb +31 -0
  27. data/lib/influxdb/rails/middleware/render_subscriber.rb +26 -0
  28. data/lib/influxdb/rails/middleware/request_subscriber.rb +59 -0
  29. data/lib/influxdb/rails/middleware/simple_subscriber.rb +49 -0
  30. data/lib/influxdb/rails/middleware/sql_subscriber.rb +38 -0
  31. data/lib/influxdb/rails/middleware/subscriber.rb +48 -0
  32. data/lib/influxdb/rails/rack.rb +24 -0
  33. data/lib/influxdb/rails/railtie.rb +51 -0
  34. data/lib/influxdb/rails/sql/normalizer.rb +27 -0
  35. data/lib/influxdb/rails/sql/query.rb +32 -0
  36. data/lib/influxdb/rails/version.rb +5 -0
  37. data/lib/rails/generators/influxdb/influxdb_generator.rb +15 -0
  38. data/lib/rails/generators/influxdb/templates/initializer.rb +11 -0
  39. data/spec/controllers/widgets_controller_spec.rb +15 -0
  40. data/spec/integration/exceptions_spec.rb +37 -0
  41. data/spec/integration/integration_helper.rb +1 -0
  42. data/spec/integration/metrics_spec.rb +28 -0
  43. data/spec/shared_examples/data.rb +67 -0
  44. data/spec/shared_examples/tags.rb +45 -0
  45. data/spec/spec_helper.rb +31 -0
  46. data/spec/support/rails4/app.rb +44 -0
  47. data/spec/support/rails5/app.rb +44 -0
  48. data/spec/support/views/widgets/_item.html.erb +1 -0
  49. data/spec/support/views/widgets/index.html.erb +5 -0
  50. data/spec/unit/backtrace_spec.rb +85 -0
  51. data/spec/unit/configuration_spec.rb +125 -0
  52. data/spec/unit/context_spec.rb +40 -0
  53. data/spec/unit/exception_presenter_spec.rb +23 -0
  54. data/spec/unit/influxdb_rails_spec.rb +78 -0
  55. data/spec/unit/middleware/render_subscriber_spec.rb +92 -0
  56. data/spec/unit/middleware/request_subscriber_spec.rb +94 -0
  57. data/spec/unit/middleware/sql_subscriber_spec.rb +95 -0
  58. data/spec/unit/sql/normalizer_spec.rb +15 -0
  59. data/spec/unit/sql/query_spec.rb +29 -0
  60. metadata +300 -0
@@ -0,0 +1,51 @@
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
+ Thread.current[:_influxdb_rails_values] = nil
32
+ end
33
+
34
+ def tags
35
+ Thread.current[:_influxdb_rails_tags] || {}
36
+ end
37
+
38
+ def tags=(tags)
39
+ Thread.current[:_influxdb_rails_tags] = tags
40
+ end
41
+
42
+ def values
43
+ Thread.current[:_influxdb_rails_values] || {}
44
+ end
45
+
46
+ def values=(values)
47
+ Thread.current[:_influxdb_rails_values] = values
48
+ end
49
+ end
50
+ end
51
+ 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,34 @@
1
+ module InfluxDB
2
+ module Rails
3
+ module Instrumentation # rubocop:disable Style/Documentation
4
+ def benchmark_for_instrumentation # rubocop:disable Metrics/MethodLength
5
+ start = Time.now
6
+ yield
7
+
8
+ c = InfluxDB::Rails.configuration
9
+ return if c.ignore_current_environment?
10
+
11
+ InfluxDB::Rails.client.write_point \
12
+ c.series_name_for_instrumentation,
13
+ values: {
14
+ value: ((Time.now - start) * 1000).ceil,
15
+ },
16
+ tags: configuration.tags_middleware.call(
17
+ method: "#{controller_name}##{action_name}",
18
+ server: Socket.gethostname
19
+ )
20
+ end
21
+
22
+ def self.included(base)
23
+ base.extend(ClassMethods)
24
+ end
25
+
26
+ module ClassMethods # rubocop:disable Style/Documentation
27
+ def instrument(methods = [])
28
+ methods = [methods] unless methods.is_a?(Array)
29
+ around_filter :benchmark_for_instrumentation, only: methods
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -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,26 @@
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
+ }
21
+ super(tags)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,59 @@
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
+ finished = InfluxDB.convert_timestamp(finish.utc, configuration.time_precision)
11
+ started = InfluxDB.convert_timestamp(start.utc, configuration.time_precision)
12
+ tags = tags(payload)
13
+ begin
14
+ series(payload, start, finish).each do |series_name, value|
15
+ InfluxDB::Rails.client.write_point \
16
+ series_name,
17
+ values: values(value, started),
18
+ tags: tags,
19
+ timestamp: finished
20
+ end
21
+ rescue StandardError => e
22
+ log :error, "[InfluxDB::Rails] Unable to write points: #{e.message}"
23
+ ensure
24
+ InfluxDB::Rails.current.reset
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def values(duration, started)
31
+ addititional_values = InfluxDB::Rails.current.values
32
+ { value: duration, started: started }.merge(addititional_values).reject do |_, value|
33
+ value.nil? || value == ""
34
+ end
35
+ end
36
+
37
+ def series(payload, start, finish)
38
+ {
39
+ configuration.series_name_for_controller_runtimes => ((finish - start) * 1000).ceil,
40
+ configuration.series_name_for_view_runtimes => (payload[:view_runtime] || 0).ceil,
41
+ configuration.series_name_for_db_runtimes => (payload[:db_runtime] || 0).ceil,
42
+ }
43
+ end
44
+
45
+ def tags(payload)
46
+ tags = {
47
+ method: "#{payload[:controller]}##{payload[:action]}",
48
+ status: payload[:status],
49
+ format: payload[:format],
50
+ http_method: payload[:method],
51
+ server: Socket.gethostname,
52
+ app_name: configuration.application_name,
53
+ }
54
+ super(tags)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,49 @@
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
+ rescue StandardError => e
26
+ log :error, "[InfluxDB::Rails] Unable to write points: #{e.message}"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def values(started, finished, _payload)
33
+ result = { value: ((finished - started) * 1000).ceil }
34
+ result.merge(InfluxDB::Rails.current.values).reject do |_, value|
35
+ value.nil? || value == ""
36
+ end
37
+ end
38
+
39
+ def timestamp(finished)
40
+ InfluxDB.convert_timestamp(finished.utc, configuration.time_precision)
41
+ end
42
+
43
+ def enabled?
44
+ super && series_name.present?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
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 location
21
+ result = super
22
+ result.empty? ? :raw : result
23
+ end
24
+
25
+ def tags(payload)
26
+ query = InfluxDB::Rails::Sql::Query.new(payload)
27
+ tags = {
28
+ location: location,
29
+ operation: query.operation,
30
+ class_name: query.class_name,
31
+ name: query.name,
32
+ }
33
+ super(tags)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,48 @@
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
+ result = tags.merge(InfluxDB::Rails.current.tags)
26
+ result = configuration.tags_middleware.call(result)
27
+ result.reject! do |_, value|
28
+ value.nil? || value == ""
29
+ end
30
+ result
31
+ end
32
+
33
+ def enabled?
34
+ configuration.instrumentation_enabled? &&
35
+ !configuration.ignore_current_environment?
36
+ end
37
+
38
+ def location
39
+ current = InfluxDB::Rails.current
40
+ [
41
+ current.controller,
42
+ current.action,
43
+ ].reject(&:blank?).join("#")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end