influx_reporter 1.0.0

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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +51 -0
  5. data/.travis.yml +42 -0
  6. data/.yardopts +3 -0
  7. data/Dockerfile +9 -0
  8. data/Gemfile +7 -0
  9. data/HISTORY.md +1 -0
  10. data/LICENSE +14 -0
  11. data/README.md +189 -0
  12. data/Rakefile +27 -0
  13. data/gemfiles/Gemfile.base +29 -0
  14. data/gemfiles/Gemfile.rails-3.2.x +4 -0
  15. data/gemfiles/Gemfile.rails-4.0.x +4 -0
  16. data/gemfiles/Gemfile.rails-4.1.x +4 -0
  17. data/gemfiles/Gemfile.rails-4.2.x +5 -0
  18. data/gemfiles/Gemfile.rails-5.0.x +5 -0
  19. data/gemfiles/Gemfile.rails-HEAD +7 -0
  20. data/influx_reporter.gemspec +26 -0
  21. data/lib/influx_reporter.rb +142 -0
  22. data/lib/influx_reporter/client.rb +306 -0
  23. data/lib/influx_reporter/configuration.rb +88 -0
  24. data/lib/influx_reporter/data_builders.rb +18 -0
  25. data/lib/influx_reporter/data_builders/error.rb +52 -0
  26. data/lib/influx_reporter/data_builders/transactions.rb +120 -0
  27. data/lib/influx_reporter/error.rb +7 -0
  28. data/lib/influx_reporter/error_message.rb +85 -0
  29. data/lib/influx_reporter/error_message/exception.rb +14 -0
  30. data/lib/influx_reporter/error_message/http.rb +73 -0
  31. data/lib/influx_reporter/error_message/stacktrace.rb +76 -0
  32. data/lib/influx_reporter/error_message/user.rb +25 -0
  33. data/lib/influx_reporter/filter.rb +66 -0
  34. data/lib/influx_reporter/http_client.rb +140 -0
  35. data/lib/influx_reporter/influx_db_client.rb +74 -0
  36. data/lib/influx_reporter/injections.rb +89 -0
  37. data/lib/influx_reporter/injections/json.rb +21 -0
  38. data/lib/influx_reporter/injections/net_http.rb +50 -0
  39. data/lib/influx_reporter/injections/redis.rb +25 -0
  40. data/lib/influx_reporter/injections/sequel.rb +37 -0
  41. data/lib/influx_reporter/injections/sinatra.rb +59 -0
  42. data/lib/influx_reporter/integration/delayed_job.rb +30 -0
  43. data/lib/influx_reporter/integration/rails/inject_exceptions_catcher.rb +25 -0
  44. data/lib/influx_reporter/integration/railtie.rb +56 -0
  45. data/lib/influx_reporter/integration/resque.rb +18 -0
  46. data/lib/influx_reporter/integration/sidekiq.rb +130 -0
  47. data/lib/influx_reporter/line_cache.rb +21 -0
  48. data/lib/influx_reporter/logging.rb +39 -0
  49. data/lib/influx_reporter/middleware.rb +63 -0
  50. data/lib/influx_reporter/normalizers.rb +65 -0
  51. data/lib/influx_reporter/normalizers/action_controller.rb +34 -0
  52. data/lib/influx_reporter/normalizers/action_view.rb +72 -0
  53. data/lib/influx_reporter/normalizers/active_record.rb +45 -0
  54. data/lib/influx_reporter/sql_summarizer.rb +31 -0
  55. data/lib/influx_reporter/subscriber.rb +80 -0
  56. data/lib/influx_reporter/tasks.rb +28 -0
  57. data/lib/influx_reporter/trace.rb +48 -0
  58. data/lib/influx_reporter/trace_helpers.rb +31 -0
  59. data/lib/influx_reporter/transaction.rb +114 -0
  60. data/lib/influx_reporter/util.rb +18 -0
  61. data/lib/influx_reporter/util/constantize.rb +56 -0
  62. data/lib/influx_reporter/util/inspector.rb +72 -0
  63. data/lib/influx_reporter/util/timestamp.rb +11 -0
  64. data/lib/influx_reporter/version.rb +5 -0
  65. data/lib/influx_reporter/worker.rb +56 -0
  66. data/spec/influx_reporter/client_spec.rb +264 -0
  67. data/spec/influx_reporter/configuration_spec.rb +42 -0
  68. data/spec/influx_reporter/data_builders/error_spec.rb +40 -0
  69. data/spec/influx_reporter/data_builders/transactions_spec.rb +48 -0
  70. data/spec/influx_reporter/error_message/exception_spec.rb +22 -0
  71. data/spec/influx_reporter/error_message/http_spec.rb +55 -0
  72. data/spec/influx_reporter/error_message/stacktrace_spec.rb +56 -0
  73. data/spec/influx_reporter/error_message/user_spec.rb +26 -0
  74. data/spec/influx_reporter/error_message_spec.rb +102 -0
  75. data/spec/influx_reporter/filter_spec.rb +33 -0
  76. data/spec/influx_reporter/injections/net_http_spec.rb +35 -0
  77. data/spec/influx_reporter/injections/sequel_spec.rb +33 -0
  78. data/spec/influx_reporter/injections/sinatra_spec.rb +13 -0
  79. data/spec/influx_reporter/injections_spec.rb +50 -0
  80. data/spec/influx_reporter/integration/delayed_job_spec.rb +37 -0
  81. data/spec/influx_reporter/integration/json_spec.rb +41 -0
  82. data/spec/influx_reporter/integration/rails_spec.rb +92 -0
  83. data/spec/influx_reporter/integration/redis_spec.rb +20 -0
  84. data/spec/influx_reporter/integration/resque_spec.rb +42 -0
  85. data/spec/influx_reporter/integration/sidekiq_spec.rb +40 -0
  86. data/spec/influx_reporter/integration/sinatra_spec.rb +99 -0
  87. data/spec/influx_reporter/line_cache_spec.rb +38 -0
  88. data/spec/influx_reporter/logging_spec.rb +49 -0
  89. data/spec/influx_reporter/middleware_spec.rb +32 -0
  90. data/spec/influx_reporter/normalizers/action_controller_spec.rb +37 -0
  91. data/spec/influx_reporter/normalizers/action_view_spec.rb +78 -0
  92. data/spec/influx_reporter/normalizers/active_record_spec.rb +70 -0
  93. data/spec/influx_reporter/normalizers_spec.rb +16 -0
  94. data/spec/influx_reporter/sql_summarizer_spec.rb +35 -0
  95. data/spec/influx_reporter/subscriber_spec.rb +83 -0
  96. data/spec/influx_reporter/trace_spec.rb +43 -0
  97. data/spec/influx_reporter/transaction_spec.rb +98 -0
  98. data/spec/influx_reporter/util/inspector_spec.rb +41 -0
  99. data/spec/influx_reporter/util_spec.rb +20 -0
  100. data/spec/influx_reporter/worker_spec.rb +54 -0
  101. data/spec/influx_reporter_spec.rb +50 -0
  102. data/spec/spec_helper.rb +86 -0
  103. metadata +188 -0
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ class ErrorMessage
5
+ class Stacktrace
6
+ def initialize(config, frames)
7
+ @config = config
8
+ @frames = frames
9
+ end
10
+
11
+ attr_reader :frames
12
+
13
+ def self.from(config, exception)
14
+ return unless exception.backtrace
15
+
16
+ new(config, exception.backtrace.reverse.map do |line|
17
+ Frame.from_line config, line
18
+ end)
19
+ end
20
+
21
+ def to_h
22
+ { frames: frames.map(&:to_h) }
23
+ end
24
+
25
+ private
26
+
27
+ class Frame < Struct.new(:filename, :lineno, :abs_path, :function, :vars,
28
+ :pre_context, :context_line, :post_context)
29
+
30
+ BACKTRACE_REGEX = /^(.+?):(\d+)(?::in `(.+?)')?$/
31
+
32
+ class << self
33
+ def from_line(config, line)
34
+ _, abs_path, lineno, function = line.match(BACKTRACE_REGEX).to_a
35
+ lineno = lineno.to_i
36
+ filename = strip_load_path(abs_path)
37
+
38
+ if lines = config.context_lines
39
+ pre_context, context_line, post_context =
40
+ get_contextlines(abs_path, lineno, lines)
41
+ end
42
+
43
+ new filename, lineno, abs_path, function, nil,
44
+ pre_context, context_line, post_context
45
+ end
46
+
47
+ private
48
+
49
+ def strip_load_path(path)
50
+ prefix = $LOAD_PATH
51
+ .map(&:to_s)
52
+ .select { |s| path.start_with?(s) }
53
+ .sort_by(&:length)
54
+ .last
55
+
56
+ return path unless prefix
57
+
58
+ path[prefix.chomp(File::SEPARATOR).length + 1..-1]
59
+ end
60
+
61
+ def get_contextlines(path, line, context)
62
+ lines = (2 * context + 1).times.map do |i|
63
+ LineCache.find(path, line - context + i)
64
+ end
65
+
66
+ pre = lines[0..(context - 1)]
67
+ line = lines[context]
68
+ post = lines[(context + 1)..-1]
69
+
70
+ [pre, line, post]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ class ErrorMessage
5
+ class User < Struct.new(:is_authenticated, :id, :username, :email)
6
+ CONTROLLER_KEY = 'action_controller.instance'
7
+
8
+ def self.from_rack_env(config, env)
9
+ controller = env[CONTROLLER_KEY]
10
+ method = config.current_user_method.to_sym
11
+
12
+ return unless controller && controller.respond_to?(method)
13
+
14
+ user = controller.send method
15
+
16
+ new(
17
+ true,
18
+ user.respond_to?(:id) ? user.id : nil,
19
+ user.respond_to?(:username) ? user.username : nil,
20
+ user.respond_to?(:email) ? user.email : nil
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InfluxReporter
4
+ # @api private
5
+ class Filter
6
+ MASK = '[FILTERED]'
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ @params = rails_filters || config.filter_parameters
11
+ end
12
+
13
+ attr_reader :config
14
+
15
+ def apply(data, _opts = {})
16
+ case data
17
+ when String
18
+ apply_to_string data, opts = {}
19
+ when Hash
20
+ apply_to_hash data
21
+ end
22
+ end
23
+
24
+ def apply_to_string(str, opts = {})
25
+ sep = opts[:separator] || '&'
26
+ kv_sep = opts[:kv_separator] || '='
27
+
28
+ str.split(sep).map do |kv|
29
+ key, value = kv.split(kv_sep)
30
+ [key, kv_sep, sanitize(key, value)].join
31
+ end.join(sep)
32
+ end
33
+
34
+ def apply_to_hash(hsh)
35
+ hsh.each_with_object({}) do |kv, filtered|
36
+ key, value = kv
37
+ filtered[key] = sanitize(key, value)
38
+ end
39
+ end
40
+
41
+ def sanitize(key, value)
42
+ should_filter?(key) ? MASK : value
43
+ end
44
+
45
+ private
46
+
47
+ def should_filter?(key)
48
+ @params.any? do |param|
49
+ case param
50
+ when String, Symbol
51
+ key.to_s == param.to_s
52
+ when Regexp
53
+ param.match(key)
54
+ end
55
+ end
56
+ end
57
+
58
+ def rails_filters
59
+ if defined?(::Rails) && Rails.respond_to?(:application) && Rails.application
60
+ if filters = ::Rails.application.config.filter_parameters
61
+ filters.any? ? filters : nil
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'json'
6
+
7
+ module InfluxReporter
8
+ # @api private
9
+ class HttpClient
10
+ include Logging
11
+
12
+ USER_AGENT = "influx_reporter-ruby/#{InfluxReporter::VERSION}"
13
+
14
+ attr_reader :state
15
+ attr_reader :adapter
16
+
17
+ def initialize(config)
18
+ @config = config
19
+ @adapter = HTTPAdapter.new(config)
20
+ @state = ClientState.new config
21
+ end
22
+
23
+ attr_reader :config
24
+
25
+ def post(resource, body)
26
+ path = abs_path(resource)
27
+ debug "POST #{resource}"
28
+
29
+ unless state.should_try?
30
+ info 'Temporarily skipping sending to InfluxReporter due to previous failure.'
31
+ return
32
+ end
33
+
34
+ body = JSON.dump(body) if body.is_a?(Hash) || body.is_a?(Array)
35
+
36
+ request = adapter.post path do |req|
37
+ req['Authorization'] = auth_header
38
+ req['Content-Type'] = 'application/json'
39
+ req['Content-Length'] = body.bytesize.to_s
40
+ req['User-Agent'] = USER_AGENT
41
+ req.body = body
42
+ end
43
+
44
+ begin
45
+ response = adapter.perform_request request
46
+ unless response.code.to_i.between?(200, 299)
47
+ raise Error, "Error from InfluxReporter server (#{response.code}): #{response.body}"
48
+ end
49
+ rescue
50
+ debug { JSON.parse(body).inspect }
51
+ @state.fail!
52
+ raise
53
+ end
54
+
55
+ @state.success!
56
+
57
+ response
58
+ end
59
+
60
+ private
61
+
62
+ def auth_header
63
+ "Bearer #{@config.secret_token}"
64
+ end
65
+
66
+ def abs_path(path)
67
+ "/api/v1/organizations/#{@config.organization_id}" \
68
+ "/apps/#{@config.app_id}#{path}"
69
+ end
70
+
71
+ def encode(event)
72
+ event_hash = @filter.process_event_hash(event.to_hash)
73
+ event_hash.to_json
74
+ end
75
+
76
+ class HTTPAdapter
77
+ def initialize(conf)
78
+ @config = conf
79
+ end
80
+
81
+ def post(path)
82
+ req = Net::HTTP::Post.new path
83
+ yield req if block_given?
84
+ req
85
+ end
86
+
87
+ def perform_request(req)
88
+ http.start do |http|
89
+ http.request req
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def http
96
+ return @http if @http
97
+
98
+ http = Net::HTTP.new server_uri.host, server_uri.port
99
+ http.use_ssl = @config.use_ssl
100
+ http.read_timeout = @config.timeout
101
+ http.open_timeout = @config.open_timeout
102
+
103
+ @http = http
104
+ end
105
+
106
+ def server_uri
107
+ @uri ||= URI(@config.server)
108
+ end
109
+ end
110
+
111
+ class ClientState
112
+ def initialize(config)
113
+ @config = config
114
+ @retry_number = 0
115
+ @last_check = Time.now.utc
116
+ end
117
+
118
+ def should_try?
119
+ return true if @status == :online
120
+
121
+ interval = ([@retry_number, 6].min**2) * @config.backoff_multiplier
122
+ return true if Time.now.utc - @last_check > interval
123
+
124
+ false
125
+ end
126
+
127
+ def fail!
128
+ @status = :error
129
+ @retry_number += 1
130
+ @last_check = Time.now.utc
131
+ end
132
+
133
+ def success!
134
+ @status = :online
135
+ @retry_number = 0
136
+ @last_check = nil
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'influxdb'
5
+
6
+ module InfluxReporter
7
+ # @api private
8
+ class InfluxDBClient
9
+ include Logging
10
+
11
+ attr_reader :state
12
+ attr_reader :client
13
+
14
+ # @param config [InfluxReporter::Configuration]
15
+ def initialize(config)
16
+ @config = config
17
+ @client = InfluxDB::Client.new config.database, config.influx_db.merge(time_precision: 'ns')
18
+ @state = ClientState.new config
19
+ end
20
+
21
+ attr_reader :config
22
+
23
+ def post(resource, data)
24
+ debug "POST #{resource}"
25
+
26
+ unless state.should_try?
27
+ info 'Temporarily skipping sending to InfluxReporter due to previous failure.'
28
+ return
29
+ end
30
+
31
+ begin
32
+ data = [data] unless data.is_a?(Array)
33
+ client.write_points data
34
+ rescue StandardError => e
35
+ debug { e.message }
36
+ @state.fail!
37
+ raise
38
+ end
39
+
40
+ @state.success!
41
+
42
+ true
43
+ end
44
+
45
+ class ClientState
46
+ def initialize(config)
47
+ @config = config
48
+ @retry_number = 0
49
+ @last_check = Time.now.utc
50
+ end
51
+
52
+ def should_try?
53
+ return true if @status == :online
54
+
55
+ interval = ([@retry_number, 6].min**2) * @config.backoff_multiplier
56
+ return true if Time.now.utc - @last_check > interval
57
+
58
+ false
59
+ end
60
+
61
+ def fail!
62
+ @status = :error
63
+ @retry_number += 1
64
+ @last_check = Time.now.utc
65
+ end
66
+
67
+ def success!
68
+ @status = :online
69
+ @retry_number = 0
70
+ @last_check = nil
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'influx_reporter/util/constantize'
4
+
5
+ module InfluxReporter
6
+ # @api private
7
+ module Injections
8
+ class Registration
9
+ def initialize(const_name, require_paths, injector)
10
+ @const_name = const_name
11
+ @require_paths = Array(require_paths)
12
+ @injector = injector
13
+ end
14
+
15
+ attr_reader :const_name, :require_paths, :injector
16
+
17
+ def install
18
+ injector.install
19
+ end
20
+ end
21
+
22
+ def self.require_hooks
23
+ @require_hooks ||= {}
24
+ end
25
+
26
+ def self.installed
27
+ @installed ||= {}
28
+ end
29
+
30
+ def self.register(*args)
31
+ registration = Registration.new(*args)
32
+
33
+ if const_defined?(registration.const_name)
34
+ installed[registration.const_name] = registration
35
+ registration.install
36
+ else
37
+ register_require_hook registration
38
+ end
39
+ end
40
+
41
+ def self.register_require_hook(registration)
42
+ registration.require_paths.each do |path|
43
+ require_hooks[path] = registration
44
+ end
45
+ end
46
+
47
+ def self.hook_into(name)
48
+ return unless registration = lookup(name)
49
+
50
+ if const_defined?(registration.const_name)
51
+ installed[registration.const_name] = registration
52
+ registration.install
53
+
54
+ registration.require_paths.each do |path|
55
+ require_hooks.delete path
56
+ end
57
+ end
58
+ end
59
+
60
+ def self.lookup(require_path)
61
+ require_hooks[require_path]
62
+ end
63
+
64
+ def self.const_defined?(const_name)
65
+ const = begin
66
+ Util.constantize(const_name)
67
+ rescue
68
+ nil
69
+ end
70
+ !!const
71
+ end
72
+ end
73
+ end
74
+
75
+ # @api private
76
+ module ::Kernel
77
+ alias_method :require_without_op, :require
78
+
79
+ def require(name)
80
+ res = require_without_op name
81
+
82
+ begin
83
+ InfluxReporter::Injections.hook_into name
84
+ rescue Exception
85
+ end
86
+
87
+ res
88
+ end
89
+ end