rollbar 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,6 +11,7 @@ end
11
11
 
12
12
  require 'rollbar/version'
13
13
  require 'rollbar/json'
14
+ require 'rollbar/js'
14
15
  require 'rollbar/configuration'
15
16
  require 'rollbar/encoding'
16
17
  require 'rollbar/logger_proxy'
@@ -20,6 +21,8 @@ require 'rollbar/railtie' if defined?(Rails::VERSION)
20
21
  require 'rollbar/delay/girl_friday' if defined?(GirlFriday)
21
22
  require 'rollbar/delay/thread'
22
23
  require 'rollbar/truncation'
24
+ require 'rollbar/exceptions'
25
+ require 'rollbar/lazy_store'
23
26
 
24
27
  module Rollbar
25
28
  ATTACHMENT_CLASSES = %w[
@@ -33,18 +36,20 @@ module Rollbar
33
36
  class Notifier
34
37
  attr_accessor :configuration
35
38
  attr_accessor :last_report
39
+ attr_reader :scope_object
36
40
 
37
41
  @file_semaphore = Mutex.new
38
42
 
39
- def initialize(parent_notifier = nil, payload_options = nil)
43
+ def initialize(parent_notifier = nil, payload_options = nil, scope = nil)
40
44
  if parent_notifier
41
45
  @configuration = parent_notifier.configuration.clone
46
+ @scope_object = parent_notifier.scope_object.clone
42
47
 
43
- if payload_options
44
- Rollbar::Util.deep_merge(@configuration.payload_options, payload_options)
45
- end
48
+ Rollbar::Util.deep_merge(@configuration.payload_options, payload_options) if payload_options
49
+ Rollbar::Util.deep_merge(@scope_object, scope) if scope
46
50
  else
47
51
  @configuration = ::Rollbar::Configuration.new
52
+ @scope_object = ::Rollbar::LazyStore.new(scope)
48
53
  end
49
54
  end
50
55
 
@@ -62,11 +67,12 @@ module Rollbar
62
67
  end
63
68
 
64
69
  def scope(options = {})
65
- self.class.new(self, options)
70
+ self.class.new(self, nil, options)
66
71
  end
67
72
 
68
73
  def scope!(options = {})
69
- Rollbar::Util.deep_merge(@configuration.payload_options, options)
74
+ Rollbar::Util.deep_merge(scope_object, options)
75
+
70
76
  self
71
77
  end
72
78
 
@@ -117,33 +123,28 @@ module Rollbar
117
123
  def log(level, *args)
118
124
  return 'disabled' unless configuration.enabled
119
125
 
120
- message = nil
121
- exception = nil
122
- extra = nil
123
-
124
- args.each do |arg|
125
- if arg.is_a?(String)
126
- message = arg
127
- elsif arg.is_a?(Exception)
128
- exception = arg
129
- elsif arg.is_a?(Hash)
130
- extra = arg
131
- end
132
- end
133
-
126
+ message, exception, extra = extract_arguments(args)
134
127
  use_exception_level_filters = extra && extra.delete(:use_exception_level_filters) == true
135
128
 
136
129
  return 'ignored' if ignored?(exception, use_exception_level_filters)
137
130
 
138
- if use_exception_level_filters
139
- exception_level = filtered_level(exception)
140
- level = exception_level if exception_level
131
+ begin
132
+ call_before_process(:level => level,
133
+ :exception => exception,
134
+ :message => message,
135
+ :extra => extra)
136
+ rescue Rollbar::Ignore
137
+ return 'ignored'
141
138
  end
142
139
 
140
+ level = lookup_exception_level(level, exception,
141
+ use_exception_level_filters)
142
+
143
143
  begin
144
144
  report(level, message, exception, extra)
145
145
  rescue Exception => e
146
146
  report_internal_error(e)
147
+
147
148
  'error'
148
149
  end
149
150
  end
@@ -241,6 +242,56 @@ module Rollbar
241
242
 
242
243
  private
243
244
 
245
+ def call_before_process(options)
246
+ options = {
247
+ :level => options[:level],
248
+ :scope => scope_object,
249
+ :exception => options[:exception],
250
+ :message => options[:message],
251
+ :extra => options[:extra]
252
+ }
253
+ handlers = configuration.before_process
254
+
255
+ handlers.each do |handler|
256
+ begin
257
+ handler.call(options)
258
+ rescue Rollbar::Ignore
259
+ raise
260
+ rescue => e
261
+ log_error("[Rollbar] Error calling the `before_process` hook: #{e}")
262
+
263
+ break
264
+ end
265
+ end
266
+ end
267
+
268
+ def extract_arguments(args)
269
+ message = nil
270
+ exception = nil
271
+ extra = nil
272
+
273
+ args.each do |arg|
274
+ if arg.is_a?(String)
275
+ message = arg
276
+ elsif arg.is_a?(Exception)
277
+ exception = arg
278
+ elsif arg.is_a?(Hash)
279
+ extra = arg
280
+ end
281
+ end
282
+
283
+ [message, exception, extra]
284
+ end
285
+
286
+ def lookup_exception_level(orig_level, exception, use_exception_level_filters)
287
+ return orig_level unless use_exception_level_filters
288
+
289
+ exception_level = filtered_level(exception)
290
+ return exception_level if exception_level
291
+
292
+ orig_level
293
+ end
294
+
244
295
  def ignored?(exception, use_exception_level_filters = false)
245
296
  return false unless exception
246
297
  return true if use_exception_level_filters && filtered_level(exception) == 'ignore'
@@ -336,10 +387,7 @@ module Rollbar
336
387
  data[:uuid] = SecureRandom.uuid if defined?(SecureRandom) && SecureRandom.respond_to?(:uuid)
337
388
 
338
389
  Rollbar::Util.deep_merge(data, configuration.payload_options)
339
-
340
- data[:person] = data[:person].call if data[:person].respond_to?(:call)
341
- data[:request] = data[:request].call if data[:request].respond_to?(:call)
342
- data[:context] = data[:context].call if data[:context].respond_to?(:call)
390
+ Rollbar::Util.deep_merge(data, scope_object)
343
391
 
344
392
  # Our API doesn't allow null context values, so just delete
345
393
  # the key if value is nil.
@@ -352,9 +400,37 @@ module Rollbar
352
400
 
353
401
  enforce_valid_utf8(payload)
354
402
 
403
+ call_transform(:level => level,
404
+ :exception => exception,
405
+ :message => message,
406
+ :extra => extra,
407
+ :payload => payload)
408
+
355
409
  payload
356
410
  end
357
411
 
412
+ def call_transform(options)
413
+ options = {
414
+ :level => options[:level],
415
+ :scope => scope_object,
416
+ :exception => options[:exception],
417
+ :message => options[:message],
418
+ :extra => options[:extra],
419
+ :payload => options[:payload]
420
+ }
421
+ handlers = configuration.transform
422
+
423
+ handlers.each do |handler|
424
+ begin
425
+ handler.call(options)
426
+ rescue => e
427
+ log_error("[Rollbar] Error calling the `transform` hook: #{e}")
428
+
429
+ break
430
+ end
431
+ end
432
+ end
433
+
358
434
  def build_payload_body(message, exception, extra)
359
435
  extra = Rollbar::Util.deep_merge(custom_data, extra || {}) if custom_data_method?
360
436
 
@@ -372,7 +448,7 @@ module Rollbar
372
448
  def report_custom_data_error(e)
373
449
  data = safely.error(e)
374
450
 
375
- return {} unless data[:uuid]
451
+ return {} unless data.is_a?(Hash) && data[:uuid]
376
452
 
377
453
  uuid_url = uuid_rollbar_url(data)
378
454
 
@@ -744,6 +820,8 @@ module Rollbar
744
820
  # to configure it without initializing any of the third party hooks
745
821
  def preconfigure
746
822
  yield(configuration)
823
+
824
+ reset_notifier!
747
825
  end
748
826
 
749
827
  def configure
@@ -752,9 +830,7 @@ module Rollbar
752
830
 
753
831
  yield(configuration)
754
832
 
755
- require_hooks
756
- require_core_extensions
757
-
833
+ prepare
758
834
  reset_notifier!
759
835
  end
760
836
 
@@ -774,10 +850,24 @@ module Rollbar
774
850
  @configuration ||= Configuration.new
775
851
  end
776
852
 
853
+ def scope_object
854
+ @scope_obejct ||= ::Rollbar::LazyStore.new({})
855
+ end
856
+
777
857
  def safely?
778
858
  configuration.safely?
779
859
  end
780
860
 
861
+ def prepare
862
+ prepare_js
863
+ require_hooks
864
+ require_core_extensions
865
+ end
866
+
867
+ def prepare_js
868
+ ::Rollbar::Js.prepare if configuration.js_enabled
869
+ end
870
+
781
871
  def require_hooks
782
872
  return if configuration.disable_monkey_patch
783
873
  wrap_delayed_worker
@@ -786,7 +876,7 @@ module Rollbar
786
876
  require 'rollbar/sidekiq' if defined?(Sidekiq)
787
877
  require 'rollbar/active_job' if defined?(ActiveJob)
788
878
  require 'rollbar/goalie' if defined?(Goalie)
789
- require 'rollbar/rack' if defined?(Rack)
879
+ require 'rollbar/rack' if defined?(Rack) unless configuration.disable_rack_monkey_patch
790
880
  require 'rollbar/rake' if defined?(Rake)
791
881
  end
792
882
 
@@ -6,11 +6,13 @@ module Rollbar
6
6
  attr_accessor :access_token
7
7
  attr_accessor :async_handler
8
8
  attr_accessor :branch
9
+ attr_accessor :before_process
9
10
  attr_accessor :code_version
10
11
  attr_accessor :custom_data_method
11
12
  attr_accessor :delayed_job_enabled
12
13
  attr_accessor :default_logger
13
14
  attr_accessor :disable_monkey_patch
15
+ attr_accessor :disable_rack_monkey_patch
14
16
  attr_accessor :disable_core_monkey_patch
15
17
  attr_accessor :dj_threshold
16
18
  attr_accessor :enabled
@@ -31,6 +33,8 @@ module Rollbar
31
33
  attr_accessor :report_dj_data
32
34
  attr_accessor :request_timeout
33
35
  attr_accessor :root
36
+ attr_accessor :js_options
37
+ attr_accessor :js_enabled
34
38
  attr_accessor :safely
35
39
  attr_accessor :scrub_fields
36
40
  attr_accessor :scrub_user
@@ -40,6 +44,7 @@ module Rollbar
40
44
  attr_accessor :uncaught_exception_level
41
45
  attr_accessor :scrub_headers
42
46
  attr_accessor :sidekiq_threshold
47
+ attr_accessor :transform
43
48
  attr_accessor :verify_ssl_peer
44
49
  attr_accessor :use_async
45
50
  attr_accessor :use_eventmachine
@@ -55,12 +60,14 @@ module Rollbar
55
60
 
56
61
  def initialize
57
62
  @async_handler = nil
63
+ @before_process = []
58
64
  @code_version = nil
59
65
  @custom_data_method = nil
60
66
  @default_logger = lambda { Logger.new(STDERR) }
61
67
  @delayed_job_enabled = true
62
68
  @disable_monkey_patch = false
63
69
  @disable_core_monkey_patch = false
70
+ @disable_rack_monkey_patch = false
64
71
  @dj_threshold = 0
65
72
  @enabled = nil # set to true when configure is called
66
73
  @endpoint = DEFAULT_ENDPOINT
@@ -82,6 +89,8 @@ module Rollbar
82
89
  @populate_empty_backtraces = false
83
90
  @report_dj_data = true
84
91
  @request_timeout = 3
92
+ @js_enabled = false
93
+ @js_options = {}
85
94
  @scrub_fields = [:passwd, :password, :password_confirmation, :secret,
86
95
  :confirm_password, :password_confirmation, :secret_token,
87
96
  :api_key, :access_token ]
@@ -92,6 +101,7 @@ module Rollbar
92
101
  @scrub_headers = ['Authorization']
93
102
  @sidekiq_threshold = 0
94
103
  @safely = false
104
+ @transform = []
95
105
  @use_async = false
96
106
  @use_eventmachine = false
97
107
  @verify_ssl_peer = true
@@ -164,6 +174,14 @@ module Rollbar
164
174
  end.flatten.uniq.map(&:gem_dir)
165
175
  end
166
176
 
177
+ def before_process=(*handler)
178
+ @before_process = Array(handler)
179
+ end
180
+
181
+ def transform=(*handler)
182
+ @transform = Array(handler)
183
+ end
184
+
167
185
  # allow params to be read like a hash
168
186
  def [](option)
169
187
  send(option)
@@ -0,0 +1,3 @@
1
+ module Rollbar
2
+ class Ignore < StandardError; end
3
+ end
@@ -0,0 +1,32 @@
1
+ require "rollbar/js/version"
2
+
3
+ module Rollbar
4
+ module Js
5
+ extend self
6
+
7
+ attr_reader :framework
8
+ attr_reader :framework_loader
9
+
10
+ def prepare
11
+ @framework ||= detect_framework
12
+ @framework_loader ||= load_framework_class.new
13
+
14
+ @framework_loader.prepare
15
+ end
16
+
17
+ private
18
+
19
+ def detect_framework
20
+ case
21
+ when defined?(::Rails::VERSION)
22
+ :rails
23
+ end
24
+ end
25
+
26
+ def load_framework_class
27
+ require "rollbar/js/frameworks/#{framework}"
28
+
29
+ Rollbar::Js::Frameworks.const_get(framework.to_s.capitalize)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ module Rollbar
2
+ module Js
3
+ module Frameworks
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,29 @@
1
+ module Rollbar
2
+ module Js
3
+ module Frameworks
4
+ class Rails
5
+ attr_accessor :prepared
6
+
7
+ alias prepared? prepared
8
+
9
+ def prepare
10
+ return if prepared?
11
+
12
+ require 'rollbar/js/middleware'
13
+
14
+ config = {
15
+ :options => Rollbar.configuration.js_options,
16
+ :enabled => Rollbar.configuration.js_enabled
17
+ }
18
+ rails_config.middleware.use(::Rollbar::Js::Middleware, config)
19
+
20
+ self.prepared = true
21
+ end
22
+
23
+ def rails_config
24
+ ::Rails.configuration
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,129 @@
1
+ require 'rack'
2
+ require 'rack/response'
3
+
4
+
5
+ module Rollbar
6
+ module Js
7
+ class Middleware
8
+ attr_reader :app
9
+ attr_reader :config
10
+
11
+ JS_IS_INJECTED_KEY = 'rollbar.js_is_injected'
12
+ SNIPPET = File.read(File.expand_path('../../../../data/rollbar.snippet.js', __FILE__))
13
+
14
+ def initialize(app, config)
15
+ @app = app
16
+ @config = config
17
+ end
18
+
19
+ def call(env)
20
+ result = app.call(env)
21
+
22
+ _call(env, result)
23
+ end
24
+
25
+ private
26
+
27
+ def _call(env, result)
28
+ return result unless should_add_js?(env, result[0], result[1])
29
+
30
+ if response_string = add_js(result[2])
31
+ env[JS_IS_INJECTED_KEY] = true
32
+ response = ::Rack::Response.new(response_string, result[0], result[1])
33
+
34
+ response.finish
35
+ else
36
+ result
37
+ end
38
+ rescue => e
39
+ Rollbar.log_error("[Rollbar] Rollbar.js could not be added because #{e} exception")
40
+ result
41
+ end
42
+
43
+ def enabled?
44
+ !!config[:enabled]
45
+ end
46
+
47
+ def should_add_js?(env, status, headers)
48
+ enabled? &&
49
+ status == 200 &&
50
+ !env[JS_IS_INJECTED_KEY] &&
51
+ html?(headers) &&
52
+ !attachment?(headers) &&
53
+ !streaming?(env)
54
+ end
55
+
56
+ def html?(headers)
57
+ headers['Content-Type'] && headers['Content-Type'].include?('text/html')
58
+ end
59
+
60
+ def attachment?(headers)
61
+ headers['Content-Disposition'].to_s.include?('attachment')
62
+ end
63
+
64
+ def streaming?(env)
65
+ return false unless defined?(ActionController::Live)
66
+
67
+ env['action_controller.instance'].class.included_modules.include?(ActionController::Live)
68
+ end
69
+
70
+ def add_js(response)
71
+ body = join_body(response)
72
+ close_old_response(response)
73
+
74
+ return nil unless body
75
+
76
+ head_open_end = find_end_of_head_open(body)
77
+ return nil unless head_open_end
78
+
79
+ if head_open_end
80
+ body = body[0..head_open_end] <<
81
+ config_js_tag <<
82
+ snippet_js_tag <<
83
+ body[head_open_end..-1]
84
+ end
85
+
86
+ body
87
+ rescue => e
88
+ Rollbar.log_error("[Rollbar] Rollbar.js could not be added because #{e} exception")
89
+ nil
90
+ end
91
+
92
+ def find_end_of_head_open(body)
93
+ head_open = body.index('<head')
94
+ body.index('>', head_open) + 1 if head_open
95
+ end
96
+
97
+ def join_body(response)
98
+ source = nil
99
+ response.each { |fragment| source ? (source << fragment.to_s) : (source = fragment.to_s)}
100
+ source
101
+ end
102
+
103
+ def close_old_response(response)
104
+ response.close if response.respond_to?(:close)
105
+ end
106
+
107
+ def config_js_tag
108
+ script_tag("var _rollbarConfig = #{config[:options].to_json};")
109
+ end
110
+
111
+ def snippet_js_tag
112
+ script_tag(js_snippet)
113
+ end
114
+
115
+ def js_snippet
116
+ SNIPPET
117
+ end
118
+
119
+ def script_tag(content)
120
+ html_safe_if_needed("\n<script type=\"text/javascript\">#{content}</script>")
121
+ end
122
+
123
+ def html_safe_if_needed(string)
124
+ string = string.html_safe if string.respond_to?(:html_safe)
125
+ string
126
+ end
127
+ end
128
+ end
129
+ end