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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +78 -0
- data/.travis.yml +37 -0
- data/CHANGELOG.md +133 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +292 -0
- data/Rakefile +34 -0
- data/config.ru +7 -0
- data/experimental-influxdb-rails.gemspec +35 -0
- data/gemfiles/Gemfile.rails-4.2.x +7 -0
- data/gemfiles/Gemfile.rails-5.0.x +7 -0
- data/gemfiles/Gemfile.rails-5.1.x +7 -0
- data/gemfiles/Gemfile.rails-5.2.x +7 -0
- data/lib/experimental-influxdb-rails.rb +123 -0
- data/lib/influxdb/rails/air_traffic_controller.rb +41 -0
- data/lib/influxdb/rails/backtrace.rb +44 -0
- data/lib/influxdb/rails/configuration.rb +211 -0
- data/lib/influxdb/rails/context.rb +51 -0
- data/lib/influxdb/rails/exception_presenter.rb +94 -0
- data/lib/influxdb/rails/instrumentation.rb +34 -0
- data/lib/influxdb/rails/logger.rb +16 -0
- data/lib/influxdb/rails/middleware/hijack_render_exception.rb +16 -0
- data/lib/influxdb/rails/middleware/hijack_rescue_action_everywhere.rb +31 -0
- data/lib/influxdb/rails/middleware/render_subscriber.rb +26 -0
- data/lib/influxdb/rails/middleware/request_subscriber.rb +59 -0
- data/lib/influxdb/rails/middleware/simple_subscriber.rb +49 -0
- data/lib/influxdb/rails/middleware/sql_subscriber.rb +38 -0
- data/lib/influxdb/rails/middleware/subscriber.rb +48 -0
- data/lib/influxdb/rails/rack.rb +24 -0
- data/lib/influxdb/rails/railtie.rb +51 -0
- data/lib/influxdb/rails/sql/normalizer.rb +27 -0
- data/lib/influxdb/rails/sql/query.rb +32 -0
- data/lib/influxdb/rails/version.rb +5 -0
- data/lib/rails/generators/influxdb/influxdb_generator.rb +15 -0
- data/lib/rails/generators/influxdb/templates/initializer.rb +11 -0
- data/spec/controllers/widgets_controller_spec.rb +15 -0
- data/spec/integration/exceptions_spec.rb +37 -0
- data/spec/integration/integration_helper.rb +1 -0
- data/spec/integration/metrics_spec.rb +28 -0
- data/spec/shared_examples/data.rb +67 -0
- data/spec/shared_examples/tags.rb +45 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/rails4/app.rb +44 -0
- data/spec/support/rails5/app.rb +44 -0
- data/spec/support/views/widgets/_item.html.erb +1 -0
- data/spec/support/views/widgets/index.html.erb +5 -0
- data/spec/unit/backtrace_spec.rb +85 -0
- data/spec/unit/configuration_spec.rb +125 -0
- data/spec/unit/context_spec.rb +40 -0
- data/spec/unit/exception_presenter_spec.rb +23 -0
- data/spec/unit/influxdb_rails_spec.rb +78 -0
- data/spec/unit/middleware/render_subscriber_spec.rb +92 -0
- data/spec/unit/middleware/request_subscriber_spec.rb +94 -0
- data/spec/unit/middleware/sql_subscriber_spec.rb +95 -0
- data/spec/unit/sql/normalizer_spec.rb +15 -0
- data/spec/unit/sql/query_spec.rb +29 -0
- 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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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)
|