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,24 @@
1
+ module InfluxDB
2
+ module Rails
3
+ class Rack # rubocop:disable Style/Documentation
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ dup._call(env)
10
+ end
11
+
12
+ def _call(env)
13
+ begin
14
+ response = @app.call(env)
15
+ rescue StandardError => e
16
+ InfluxDB::Rails.transmit_unless_ignorable(e, env)
17
+ raise(e)
18
+ end
19
+
20
+ response
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,51 @@
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
+ templates = Middleware::RenderSubscriber.new(c, c.series_name_for_render_template)
37
+ ActiveSupport::Notifications.subscribe "render_template.action_view", templates
38
+
39
+ partials = Middleware::RenderSubscriber.new(c, c.series_name_for_render_partial)
40
+ ActiveSupport::Notifications.subscribe "render_partial.action_view", partials
41
+
42
+ collections = Middleware::RenderSubscriber.new(c, c.series_name_for_render_collection)
43
+ ActiveSupport::Notifications.subscribe "render_collection.action_view", collections
44
+
45
+ sql = Middleware::SqlSubscriber.new(c, c.series_name_for_sql)
46
+ ActiveSupport::Notifications.subscribe "sql.active_record", sql
47
+ end
48
+ end
49
+ end
50
+ end
51
+ 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
@@ -0,0 +1,32 @@
1
+ require "influxdb/rails/sql/normalizer"
2
+
3
+ module InfluxDB
4
+ module Rails
5
+ module Sql
6
+ class Query # :nodoc:
7
+ attr_reader :query, :name
8
+
9
+ TRACKED_SQL_COMMANDS = %w[SELECT INSERT UPDATE DELETE].freeze
10
+ UNTRACKED_NAMES = %w[SCHEMA].freeze
11
+
12
+ def initialize(payload)
13
+ @query = payload[:sql].to_s.dup.upcase
14
+ @name = payload[:name].to_s.dup
15
+ end
16
+
17
+ def operation
18
+ query.split.first
19
+ end
20
+
21
+ def class_name
22
+ name.split.first
23
+ end
24
+
25
+ def track?
26
+ @track ||= query.start_with?(*TRACKED_SQL_COMMANDS) &&
27
+ !name.upcase.start_with?(*UNTRACKED_NAMES)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module InfluxDB
2
+ module Rails
3
+ VERSION = "1.0.0.beta5".freeze
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ require "rails/generators"
2
+
3
+ class InfluxdbGenerator < Rails::Generators::Base # rubocop:disable Style/Documentation
4
+ desc "Description:\n This creates a Rails initializer for InfluxDB::Rails."
5
+
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def copy_initializer_file
9
+ template "initializer.rb", "config/initializers/influxdb_rails.rb"
10
+ end
11
+
12
+ def install
13
+ # nothing to do here
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ InfluxDB::Rails.configure do |config|
2
+ config.influxdb_database = "rails"
3
+ config.influxdb_username = "root"
4
+ config.influxdb_password = "root"
5
+ config.influxdb_hosts = ["localhost"]
6
+ config.influxdb_port = 8086
7
+
8
+ # config.series_name_for_controller_runtimes = "rails.controller"
9
+ # config.series_name_for_view_runtimes = "rails.view"
10
+ # config.series_name_for_db_runtimes = "rails.db"
11
+ end
@@ -0,0 +1,15 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe WidgetsController, type: :controller do
4
+ describe "#new" do
5
+ it "should raise an exception" do
6
+ expect { get :new }.to raise_error(ZeroDivisionError)
7
+ end
8
+ end
9
+
10
+ describe "#index" do
11
+ it "should not raise an exception" do
12
+ expect { get :index }.to_not raise_error
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/integration_helper")
2
+
3
+ RSpec.describe "exception handling", type: :request do
4
+ before do
5
+ InfluxDB::Rails.configure do |config|
6
+ config.ignored_environments = %w[development]
7
+ config.instrumentation_enabled = false
8
+ end
9
+ end
10
+
11
+ describe "in an action that raises an exception" do
12
+ it "should add an exception to the queue" do
13
+ expect(InfluxDB::Rails.client).to receive(:write_point)
14
+ get "/widgets/new"
15
+ end
16
+ end
17
+
18
+ describe "in an action that does not raise an exception" do
19
+ it "should not add anything to the queue" do
20
+ expect(InfluxDB::Rails.client).not_to receive(:write_point)
21
+ get "/widgets"
22
+ end
23
+ end
24
+
25
+ describe "for an ignored user agent" do
26
+ it "should not make an HTTP call to the API" do
27
+ expect(InfluxDB::Rails.client).not_to receive(:write_point)
28
+
29
+ # note: GoogleBot is ignored by default
30
+ if Rails::VERSION::MAJOR >= 5
31
+ get "/widgets/new", headers: { "HTTP_USER_AGENT" => "GoogleBot/2.1" }
32
+ else
33
+ get "/widgets/new", {}, "HTTP_USER_AGENT" => "GoogleBot/2.1"
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
@@ -0,0 +1,28 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/integration_helper")
2
+
3
+ RSpec.describe WidgetsController, type: :controller do
4
+ render_views
5
+
6
+ before do
7
+ allow_any_instance_of(InfluxDB::Rails::Configuration).to receive(:ignored_environments).and_return(%w[development])
8
+ end
9
+
10
+ describe "in a normal request" do
11
+ it "should result in attempts to write metrics via the client" do
12
+ expect(InfluxDB::Rails.client).to receive(:write_point).exactly(6).times
13
+ get :index
14
+ end
15
+
16
+ context "with sql reports enabled" do
17
+ before do
18
+ allow_any_instance_of(InfluxDB::Rails::Middleware::SqlSubscriber).to receive(:series_name).and_return("rails.sql")
19
+ get :index # to not count ActiveRecord initialization
20
+ end
21
+
22
+ it "should result in attempts to write metrics via the client" do
23
+ expect(InfluxDB::Rails.client).to receive(:write_point).exactly(7).times
24
+ get :index
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,67 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.shared_examples_for "with additional data" do |series_names|
4
+ context "values" do
5
+ let(:additional_values) do
6
+ { another: :value }
7
+ end
8
+
9
+ after do
10
+ InfluxDB::Rails.current.reset
11
+ end
12
+
13
+ it "does include the tags" do
14
+ InfluxDB::Rails.current.values = additional_values
15
+
16
+ series_names.each do |series_name|
17
+ expect_any_instance_of(InfluxDB::Client).to receive(:write_point).with(series_name, hash_including(values: hash_including(another: :value)))
18
+ end
19
+
20
+ subject.call("unused", start, finish, "unused", payload)
21
+ end
22
+ end
23
+
24
+ context "tags" do
25
+ context "when tags_middleware is overwritten" do
26
+ before do
27
+ allow(config).to receive(:tags_middleware).and_return(tags_middleware)
28
+ end
29
+
30
+ let(:tags_middleware) { ->(tags) { tags.merge(static: "value", nil: nil, empty: "") } }
31
+
32
+ it "processes tags throught the middleware" do
33
+ tags = data[:tags].merge(static: "value")
34
+
35
+ series_names.each do |series_name|
36
+ expect_any_instance_of(InfluxDB::Client).to receive(:write_point).with(series_name, include(tags: tags))
37
+ end
38
+
39
+ subject.call("unused", start, finish, "unused", payload)
40
+ end
41
+ end
42
+
43
+ context "when tags are set in the current context" do
44
+ let(:input) do
45
+ { another: :value, nil: nil, empty: "" }
46
+ end
47
+ let(:output) do
48
+ { another: :value }
49
+ end
50
+
51
+ after do
52
+ InfluxDB::Rails.current.reset
53
+ end
54
+
55
+ it "does include the tags" do
56
+ InfluxDB::Rails.current.tags = input
57
+ tags = data[:tags].merge(output)
58
+
59
+ series_names.each do |series_name|
60
+ expect_any_instance_of(InfluxDB::Client).to receive(:write_point).with(series_name, include(tags: tags))
61
+ end
62
+
63
+ subject.call("unused", start, finish, "unused", payload)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.shared_examples_for "with additional tags" do |series_names|
4
+ context "when tags_middleware is overwritten" do
5
+ before do
6
+ allow(config).to receive(:tags_middleware).and_return(tags_middleware)
7
+ end
8
+
9
+ let(:tags_middleware) { ->(tags) { tags.merge(static: "value", nil: nil, empty: "") } }
10
+
11
+ it "processes tags throught the middleware" do
12
+ tags = data[:tags].merge(static: "value")
13
+
14
+ series_names.each do |series_name|
15
+ expect_any_instance_of(InfluxDB::Client).to receive(:write_point).with(series_name, include(tags: tags))
16
+ end
17
+
18
+ subject.call("unused", start, finish, "unused", payload)
19
+ end
20
+ end
21
+
22
+ context "when tags are set in the current context" do
23
+ let(:input) do
24
+ { another: :value, nil: nil, empty: "" }
25
+ end
26
+ let(:output) do
27
+ { another: :value }
28
+ end
29
+
30
+ after do
31
+ InfluxDB::Rails.current.reset
32
+ end
33
+
34
+ it "does include the tags" do
35
+ InfluxDB::Rails.current.tags = input
36
+ tags = data[:tags].merge(output)
37
+
38
+ series_names.each do |series_name|
39
+ expect_any_instance_of(InfluxDB::Client).to receive(:write_point).with(series_name, include(tags: tags))
40
+ end
41
+
42
+ subject.call("unused", start, finish, "unused", payload)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ ENV["RAILS_ENV"] ||= "test"
4
+
5
+ require "rails"
6
+
7
+ if Rails::VERSION::MAJOR < 4 || Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR < 2
8
+ raise "Sorry, influxdb-rails only supports Rails 4.2 and higher."
9
+ end
10
+
11
+ require "bundler/setup"
12
+ Bundler.require
13
+
14
+ require "fakeweb"
15
+ FakeWeb.allow_net_connect = false
16
+
17
+ puts "Loading Rails v#{Rails.version}..."
18
+
19
+ require "support/rails#{Rails::VERSION::MAJOR}/app"
20
+ require "rspec/rails"
21
+
22
+ RSpec.configure do |config|
23
+ # use expect syntax
24
+ config.disable_monkey_patching!
25
+
26
+ # reset configuration for each spec
27
+ config.before :each do
28
+ InfluxDB::Rails.instance_variable_set :@configuration, nil
29
+ InfluxDB::Rails.configure(&:load_rails_defaults)
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ require "action_controller/railtie"
2
+ require "active_record"
3
+
4
+ app = Class.new(Rails::Application)
5
+ app.config.secret_key_base = "1234567890abcdef1234567890abcdef"
6
+ app.config.secret_token = "1234567890abcdef1234567890abcdef"
7
+ app.config.session_store :cookie_store, key: "_myapp_session"
8
+ app.config.active_support.deprecation = :log
9
+ app.config.eager_load = false
10
+ app.config.root = __dir__
11
+ Rails.backtrace_cleaner.remove_silencers!
12
+ app.initialize!
13
+
14
+ app.routes.draw do
15
+ resources :widgets
16
+ end
17
+
18
+ InfluxDB::Rails.configure do |config|
19
+ end
20
+
21
+ ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
22
+ ActiveRecord::Schema.define do
23
+ create_table :widgets, force: true do |t|
24
+ t.string :title
25
+
26
+ t.timestamps
27
+ end
28
+ end
29
+
30
+ class Widget < ActiveRecord::Base; end
31
+ class ApplicationController < ActionController::Base; end
32
+ class WidgetsController < ApplicationController
33
+ prepend_view_path File.join(__dir__, "..", "views")
34
+
35
+ def index
36
+ Widget.create!(title: "test")
37
+ end
38
+
39
+ def new
40
+ 1 / 0
41
+ end
42
+ end
43
+
44
+ Object.const_set(:ApplicationHelper, Module.new)