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,26 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path('../lib', __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'influx_reporter/version'
7
+
8
+ Gem::Specification.new do |gem|
9
+ gem.name = 'influx_reporter'
10
+ gem.version = InfluxReporter::VERSION
11
+ gem.authors = ['Kacper Kawecki']
12
+ gem.email = 'kacper@geniebelt.com'
13
+ gem.summary = 'Metrics collector for rails and InfluxDB based on Opbeat Ruby client library'
14
+ gem.homepage = 'https://github.com/GenieBelt/influx-reporter'
15
+ gem.license = 'BSD-3-Clause'
16
+
17
+ gem.files = `git ls-files -z`.split("\x0")
18
+ gem.require_paths = ['lib']
19
+ gem.extra_rdoc_files = %w[README.md LICENSE]
20
+
21
+ gem.required_ruby_version = '>= 2.3.0'
22
+ gem.add_dependency('activesupport', '>= 3.0.0')
23
+ gem.add_dependency('influxdb', '>= 0.5.3')
24
+
25
+ gem.add_development_dependency('rubocop')
26
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'influx_reporter/version'
4
+ require 'influx_reporter/configuration'
5
+
6
+ require 'influx_reporter/logging'
7
+ require 'influx_reporter/client'
8
+ require 'influx_reporter/error'
9
+ require 'influx_reporter/trace_helpers'
10
+
11
+ require 'influx_reporter/middleware'
12
+
13
+ require 'influx_reporter/integration/railtie' if defined?(Rails)
14
+
15
+ require 'influx_reporter/injections'
16
+ require 'influx_reporter/injections/net_http'
17
+ require 'influx_reporter/injections/redis'
18
+ require 'influx_reporter/injections/sinatra'
19
+ require 'influx_reporter/injections/sequel'
20
+
21
+ require 'influx_reporter/integration/delayed_job'
22
+ require 'influx_reporter/integration/sidekiq'
23
+ require 'influx_reporter/integration/resque'
24
+
25
+ module InfluxReporter
26
+ # Start the InfluxReporter client
27
+ #
28
+ # @param conf [Configuration] An Configuration object
29
+ def self.start!(conf)
30
+ Client.start! conf
31
+ end
32
+
33
+ # Stop the InfluxReporter client
34
+ def self.stop!
35
+ Client.stop!
36
+ end
37
+
38
+ def self.started?
39
+ !!Client.inst
40
+ end
41
+
42
+ # Start a new transaction or return the currently running
43
+ #
44
+ # @param endpoint [String] A description of the transaction, eg `ExamplesController#index`
45
+ # @param kind [String] The kind of the transaction, eg `app.request.get` or `db.mysql2.query`
46
+ # @param result [Object] Result of the transaction, eq `200` for a HTTP server
47
+ # @yield [InfluxReporter::Transaction] Optional block encapsulating transaction
48
+ # @return [InfluxReporter::Transaction] Unless block given
49
+ def self.transaction(endpoint, kind = nil, result = nil, &block)
50
+ unless client
51
+ return yield if block_given?
52
+ return nil
53
+ end
54
+
55
+ client.transaction endpoint, kind, result, &block
56
+ end
57
+
58
+ # Starts a new trace under the current Transaction
59
+ #
60
+ # @param signature [String] A description of the trace, eq `SELECT FROM "users"`
61
+ # @param kind [String] The kind of trace, eq `db.mysql2.query`
62
+ # @param extra [Hash] Extra information about the trace
63
+ # @yield [Trace] Optional block encapsulating trace
64
+ # @return [Trace] Unless block given
65
+ def self.trace(signature, kind = nil, extra = nil, &block)
66
+ unless client
67
+ return yield if block_given?
68
+ return nil
69
+ end
70
+
71
+ client.trace signature, kind, extra, &block
72
+ end
73
+
74
+ def self.flush_transactions
75
+ client&.flush_transactions
76
+ end
77
+
78
+ def self.flush_transactions_if_needed
79
+ client&.flush_transactions_if_needed
80
+ end
81
+
82
+ # Sets context for future errors
83
+ #
84
+ # @param context [Hash]
85
+ def self.set_context(context)
86
+ client&.set_context context
87
+ end
88
+
89
+ # Updates context for errors within the block
90
+ #
91
+ # @param context [Hash]
92
+ # @yield [Trace] Block in which the context is used
93
+ def self.with_context(context, &block)
94
+ unless client
95
+ return yield if block_given?
96
+ return nil
97
+ end
98
+
99
+ client.with_context context, &block
100
+ end
101
+
102
+ # Send an exception to InfluxReporter
103
+ #
104
+ # @param exception [Exception]
105
+ # @param opts [Hash]
106
+ # @option opts [Hash] :rack_env A rack env object
107
+ # @return [Net::HTTPResponse]
108
+ def self.report(exception, opts = {})
109
+ unless client
110
+ return yield if block_given?
111
+ return nil
112
+ end
113
+
114
+ client.report exception, opts
115
+ end
116
+
117
+ # Send an exception to InfluxReporter
118
+ #
119
+ # @param message [String]
120
+ # @param opts [Hash]
121
+ # @return [Net::HTTPResponse]
122
+ def self.report_message(message, opts = {})
123
+ client&.report_message message, opts
124
+ end
125
+
126
+ # Captures any exceptions raised inside the block
127
+ #
128
+ def self.capture(&block)
129
+ unless client
130
+ return yield if block_given?
131
+ return nil
132
+ end
133
+
134
+ client.capture(&block)
135
+ end
136
+
137
+ private
138
+
139
+ def self.client
140
+ Client.inst
141
+ end
142
+ end
@@ -0,0 +1,306 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thread'
4
+ require 'influx_reporter/subscriber'
5
+ require 'influx_reporter/influx_db_client'
6
+ require 'influx_reporter/worker'
7
+ require 'influx_reporter/transaction'
8
+ require 'influx_reporter/trace'
9
+ require 'influx_reporter/error_message'
10
+ require 'influx_reporter/data_builders'
11
+
12
+ module InfluxReporter
13
+ # @api private
14
+ class Client
15
+ include Logging
16
+
17
+ KEY = :__influx_reporter_transaction_key
18
+ LOCK = Mutex.new
19
+
20
+ class TransactionInfo
21
+ def current
22
+ Thread.current[KEY]
23
+ end
24
+
25
+ def current=(transaction)
26
+ Thread.current[KEY] = transaction
27
+ end
28
+ end
29
+
30
+ # life cycle
31
+
32
+ def self.inst
33
+ @instance
34
+ end
35
+
36
+ def self.start!(config = nil)
37
+ return @instance if @instance
38
+
39
+ LOCK.synchronize do
40
+ return @instance if @instance
41
+ @instance = new(config).start!
42
+ end
43
+ end
44
+
45
+ def self.stop!
46
+ LOCK.synchronize do
47
+ return unless @instance
48
+
49
+ @instance.stop!
50
+ @instance = nil
51
+ end
52
+ end
53
+
54
+ def initialize(config)
55
+ @config = config
56
+
57
+ @influx_client = InfluxDBClient.new config
58
+ @queue = Queue.new
59
+
60
+ @data_builders = Struct.new(:transactions, :error_message).new(
61
+ DataBuilders::Transactions.new(config),
62
+ DataBuilders::Error.new(config)
63
+ )
64
+
65
+ unless config.disable_performance
66
+ @transaction_info = TransactionInfo.new
67
+ @subscriber = Subscriber.new config, self
68
+ end
69
+
70
+ @pending_transactions = []
71
+ @last_sent_transactions = Time.now.utc
72
+ end
73
+
74
+ # @!attribute [r] config
75
+ # InfluxReporter configuration
76
+ # @return [InfluxReporter::Configuration]
77
+ # @!attribute [r] queue
78
+ # Worker data queue
79
+ # @return [Thread::Queue]
80
+ # @!attribute [r] pending_transactions
81
+ # Transaction to be submitted
82
+ # @return [Thread::Queue]
83
+ attr_reader :config, :queue, :pending_transactions
84
+
85
+ # Start client
86
+ # @return [InfluxReporter::Client]
87
+ def start!
88
+ info 'Starting client'
89
+
90
+ @subscriber&.register!
91
+
92
+ self
93
+ end
94
+
95
+ def stop!
96
+ flush_transactions
97
+ kill_worker
98
+ unregister! if @subscriber
99
+ end
100
+
101
+ at_exit do
102
+ stop!
103
+ end
104
+
105
+ # metrics
106
+
107
+ def current_transaction
108
+ @transaction_info.current
109
+ end
110
+
111
+ def current_transaction=(transaction)
112
+ @transaction_info.current = transaction
113
+ end
114
+
115
+ def transaction(endpoint, kind = nil, result = nil)
116
+ if config.disable_performance
117
+ return yield if block_given?
118
+ return nil
119
+ end
120
+
121
+ if current_transaction
122
+ transaction = current_transaction
123
+ yield transaction if block_given?
124
+ return transaction
125
+ end
126
+
127
+ transaction = Transaction.new self, endpoint, kind, result
128
+
129
+ self.current_transaction = transaction
130
+ return transaction unless block_given?
131
+
132
+ begin
133
+ yield transaction
134
+ ensure
135
+ self.current_transaction = nil
136
+ transaction.done
137
+ end
138
+
139
+ transaction
140
+ end
141
+
142
+ def trace(*args, &block)
143
+ if config.disable_performance
144
+ return yield if block_given?
145
+ return nil
146
+ end
147
+
148
+ unless transaction = current_transaction
149
+ return yield if block_given?
150
+ return
151
+ end
152
+
153
+ transaction.trace(*args, &block)
154
+ end
155
+
156
+ def submit_transaction(transaction)
157
+ ensure_worker_running
158
+
159
+ if config.debug_traces
160
+ unless transaction.endpoint == 'Rack'
161
+ debug { Util::Inspector.new.transaction transaction, include_parents: true }
162
+ end
163
+ end
164
+
165
+ @pending_transactions << transaction
166
+
167
+ flush_transactions if should_send_transactions?
168
+ end
169
+
170
+ def flush_transactions
171
+ return if @pending_transactions.empty?
172
+
173
+ data = @data_builders.transactions.build(@pending_transactions)
174
+ enqueue Worker::PostRequest.new('/transactions/', data)
175
+
176
+ @last_sent_transactions = Time.now.utc
177
+ @pending_transactions = []
178
+
179
+ true
180
+ end
181
+
182
+ def flush_transactions_if_needed
183
+ if should_send_transactions?
184
+ flush_transactions
185
+ end
186
+ end
187
+
188
+ # errors
189
+
190
+ def set_context(context)
191
+ @context = context
192
+ end
193
+
194
+ def with_context(context)
195
+ current = @context
196
+
197
+ set_context((current || {}).deep_merge(context))
198
+
199
+ yield if block_given?
200
+ ensure
201
+ set_context(current)
202
+ end
203
+
204
+ def report(exception, opts = {})
205
+ return if config.disable_errors
206
+ return unless exception
207
+
208
+ ensure_worker_running
209
+
210
+ exception.set_backtrace caller unless exception.backtrace
211
+
212
+ if error_message = ErrorMessage.from_exception(config, exception, opts)
213
+ error_message.add_extra(@context) if @context
214
+ data = @data_builders.error_message.build error_message
215
+ enqueue Worker::PostRequest.new('/errors/', data)
216
+ end
217
+ end
218
+
219
+ def report_message(message, opts = {})
220
+ return if config.disable_errors
221
+
222
+ ensure_worker_running
223
+
224
+ error_message = ErrorMessage.new(config, message, opts)
225
+ error_message.add_extra(@context) if @context
226
+ data = @data_builders.error_message.build error_message
227
+ enqueue Worker::PostRequest.new('/errors/', data)
228
+ end
229
+
230
+ def capture
231
+ unless block_given?
232
+ return Kernel.at_exit do
233
+ if $ERROR_INFO
234
+ debug $ERROR_INFO.inspect
235
+ report $ERROR_INFO
236
+ end
237
+ end
238
+ end
239
+
240
+ begin
241
+ yield
242
+ rescue Error => e
243
+ raise # Don't capture InfluxReporter errors
244
+ rescue Exception => e
245
+ report e
246
+ raise
247
+ end
248
+ end
249
+
250
+ private
251
+
252
+ def enqueue(request)
253
+ @queue << request
254
+ end
255
+
256
+ def start_worker
257
+ return if worker_running?
258
+
259
+ return if config.disable_worker
260
+
261
+ info 'Starting worker in thread'
262
+
263
+ @worker_thread = Thread.new do
264
+ begin
265
+ Worker.new(config, @queue, @influx_client).run
266
+ rescue => e
267
+ fatal "Failed booting worker:\n#{e.inspect}"
268
+ debug e.backtrace.join("\n")
269
+ raise
270
+ end
271
+ end
272
+ end
273
+
274
+ def kill_worker
275
+ return unless worker_running?
276
+ @queue << Worker::StopMessage.new
277
+ unless @worker_thread.join(config.worker_quit_timeout)
278
+ error 'Failed to wait for worker, not all messages sent'
279
+ end
280
+ @worker_thread = nil
281
+ end
282
+
283
+ def ensure_worker_running
284
+ return if worker_running?
285
+
286
+ LOCK.synchronize do
287
+ return if worker_running?
288
+ start_worker
289
+ end
290
+ end
291
+
292
+ def worker_running?
293
+ @worker_thread&.alive?
294
+ end
295
+
296
+ def unregister!
297
+ @subscriber.unregister!
298
+ end
299
+
300
+ def should_send_transactions?
301
+ return true if config.transaction_post_interval.nil?
302
+
303
+ Time.now.utc - @last_sent_transactions > config.transaction_post_interval
304
+ end
305
+ end
306
+ end