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,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
|