rollbar 2.12.0 → 2.13.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -6
- data/README.md +58 -8
- data/docs/configuration.md +12 -0
- data/gemfiles/rails30.gemfile +1 -0
- data/gemfiles/rails31.gemfile +1 -0
- data/gemfiles/rails32.gemfile +1 -0
- data/gemfiles/rails40.gemfile +3 -0
- data/gemfiles/rails41.gemfile +1 -0
- data/gemfiles/rails42.gemfile +7 -1
- data/gemfiles/rails50.gemfile +2 -1
- data/gemfiles/ruby_1_8_and_1_9_2.gemfile +3 -1
- data/lib/rollbar.rb +70 -654
- data/lib/rollbar/configuration.rb +32 -0
- data/lib/rollbar/item.rb +16 -6
- data/lib/rollbar/item/backtrace.rb +26 -17
- data/lib/rollbar/item/frame.rb +112 -0
- data/lib/rollbar/middleware/js.rb +39 -35
- data/lib/rollbar/middleware/rails/rollbar.rb +3 -3
- data/lib/rollbar/notifier.rb +645 -0
- data/lib/rollbar/plugins/delayed_job/job_data.rb +40 -21
- data/lib/rollbar/plugins/rails.rb +2 -2
- data/lib/rollbar/plugins/rake.rb +32 -6
- data/lib/rollbar/plugins/resque.rb +11 -0
- data/lib/rollbar/plugins/resque/failure.rb +39 -0
- data/lib/rollbar/plugins/validations.rb +10 -0
- data/lib/rollbar/request_data_extractor.rb +36 -18
- data/lib/rollbar/scrubbers/params.rb +2 -1
- data/lib/rollbar/truncation.rb +1 -1
- data/lib/rollbar/truncation/frames_strategy.rb +2 -1
- data/lib/rollbar/truncation/min_body_strategy.rb +2 -1
- data/lib/rollbar/truncation/strings_strategy.rb +1 -1
- data/lib/rollbar/version.rb +1 -1
- data/spec/controllers/home_controller_spec.rb +13 -24
- data/spec/delayed/backend/test.rb +1 -0
- data/spec/requests/home_spec.rb +1 -1
- data/spec/rollbar/configuration_spec.rb +22 -0
- data/spec/rollbar/item/backtrace_spec.rb +26 -0
- data/spec/rollbar/item/frame_spec.rb +267 -0
- data/spec/rollbar/item_spec.rb +27 -2
- data/spec/rollbar/middleware/js_spec.rb +23 -0
- data/spec/rollbar/middleware/sinatra_spec.rb +7 -7
- data/spec/rollbar/notifier_spec.rb +43 -0
- data/spec/rollbar/plugins/delayed_job/{job_data.rb → job_data_spec.rb} +15 -2
- data/spec/rollbar/plugins/rack_spec.rb +7 -7
- data/spec/rollbar/plugins/rake_spec.rb +1 -2
- data/spec/rollbar/plugins/resque/failure_spec.rb +36 -0
- data/spec/rollbar/request_data_extractor_spec.rb +103 -1
- data/spec/rollbar/truncation/min_body_strategy_spec.rb +1 -1
- data/spec/rollbar/truncation/strings_strategy_spec.rb +2 -2
- data/spec/rollbar_bc_spec.rb +4 -4
- data/spec/rollbar_spec.rb +99 -37
- data/spec/spec_helper.rb +2 -2
- data/spec/support/notifier_helpers.rb +2 -0
- metadata +16 -4
@@ -2,6 +2,7 @@ require 'logger'
|
|
2
2
|
|
3
3
|
module Rollbar
|
4
4
|
class Configuration
|
5
|
+
SEND_EXTRA_FRAME_DATA_OPTIONS = [:none, :app, :all].freeze
|
5
6
|
|
6
7
|
attr_accessor :access_token
|
7
8
|
attr_accessor :async_handler
|
@@ -52,6 +53,7 @@ module Rollbar
|
|
52
53
|
attr_accessor :use_eventmachine
|
53
54
|
attr_accessor :web_base
|
54
55
|
attr_accessor :write_to_file
|
56
|
+
attr_reader :send_extra_frame_data
|
55
57
|
|
56
58
|
attr_reader :project_gem_paths
|
57
59
|
|
@@ -111,6 +113,8 @@ module Rollbar
|
|
111
113
|
@verify_ssl_peer = true
|
112
114
|
@web_base = DEFAULT_WEB_BASE
|
113
115
|
@write_to_file = false
|
116
|
+
@send_extra_frame_data = :none
|
117
|
+
@project_gem_paths = []
|
114
118
|
end
|
115
119
|
|
116
120
|
def initialize_copy(orig)
|
@@ -122,6 +126,24 @@ module Rollbar
|
|
122
126
|
end
|
123
127
|
end
|
124
128
|
|
129
|
+
def merge(options)
|
130
|
+
new_configuration = clone
|
131
|
+
new_configuration.merge!(options)
|
132
|
+
|
133
|
+
new_configuration
|
134
|
+
end
|
135
|
+
|
136
|
+
def merge!(options)
|
137
|
+
options.each do |name, value|
|
138
|
+
variable_name = "@#{name}"
|
139
|
+
next unless instance_variable_defined?(variable_name)
|
140
|
+
|
141
|
+
instance_variable_set(variable_name, value)
|
142
|
+
end
|
143
|
+
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
125
147
|
def use_delayed_job
|
126
148
|
require 'rollbar/delay/delayed_job'
|
127
149
|
@use_async = true
|
@@ -192,6 +214,16 @@ module Rollbar
|
|
192
214
|
@transform = Array(handler)
|
193
215
|
end
|
194
216
|
|
217
|
+
def send_extra_frame_data=(value)
|
218
|
+
unless SEND_EXTRA_FRAME_DATA_OPTIONS.include?(value)
|
219
|
+
logger.warning("Wrong 'send_extra_frame_data' value, :none, :app or :full is expected")
|
220
|
+
|
221
|
+
return
|
222
|
+
end
|
223
|
+
|
224
|
+
@send_extra_frame_data = value
|
225
|
+
end
|
226
|
+
|
195
227
|
# allow params to be read like a hash
|
196
228
|
def [](option)
|
197
229
|
send(option)
|
data/lib/rollbar/item.rb
CHANGED
@@ -10,6 +10,8 @@ end
|
|
10
10
|
require 'rollbar/item/backtrace'
|
11
11
|
require 'rollbar/util'
|
12
12
|
require 'rollbar/encoding'
|
13
|
+
require 'rollbar/truncation'
|
14
|
+
require 'rollbar/json'
|
13
15
|
|
14
16
|
module Rollbar
|
15
17
|
# This class represents the payload to be sent to the API.
|
@@ -82,7 +84,7 @@ module Rollbar
|
|
82
84
|
},
|
83
85
|
:body => build_body
|
84
86
|
}
|
85
|
-
data[:project_package_paths] = configuration.project_gem_paths if configuration.project_gem_paths
|
87
|
+
data[:project_package_paths] = configuration.project_gem_paths if configuration.project_gem_paths.any?
|
86
88
|
data[:code_version] = configuration.code_version if configuration.code_version
|
87
89
|
data[:uuid] = SecureRandom.uuid if defined?(SecureRandom) && SecureRandom.respond_to?(:uuid)
|
88
90
|
|
@@ -101,16 +103,24 @@ module Rollbar
|
|
101
103
|
# from an async handler job, which can be serialized.
|
102
104
|
stringified_payload = Util::Hash.deep_stringify_keys(payload)
|
103
105
|
result = Truncation.truncate(stringified_payload)
|
106
|
+
|
104
107
|
return result unless Truncation.truncate?(result)
|
105
108
|
|
106
|
-
|
107
|
-
final_size = result.bytesize
|
108
|
-
notifier.send_failsafe("Could not send payload due to it being too large after truncating attempts. Original size: #{original_size} Final size: #{final_size}", nil)
|
109
|
-
logger.error("[Rollbar] Payload too large to be sent: #{Rollbar::JSON.dump(payload)}")
|
109
|
+
handle_too_large_payload(stringified_payload, result)
|
110
110
|
|
111
111
|
nil
|
112
112
|
end
|
113
113
|
|
114
|
+
def handle_too_large_payload(stringified_payload, final_payload)
|
115
|
+
original_size = Rollbar::JSON.dump(stringified_payload).bytesize
|
116
|
+
final_size = final_payload.bytesize
|
117
|
+
uuid = stringified_payload['data']['uuid']
|
118
|
+
host = stringified_payload['data'].fetch('server', {})['host']
|
119
|
+
|
120
|
+
notifier.send_failsafe("Could not send payload due to it being too large after truncating attempts. Original size: #{original_size} Final size: #{final_size}", nil, uuid, host)
|
121
|
+
logger.error("[Rollbar] Payload too large to be sent for UUID #{uuid}: #{Rollbar::JSON.dump(payload)}")
|
122
|
+
end
|
123
|
+
|
114
124
|
def ignored?
|
115
125
|
data = payload['data']
|
116
126
|
|
@@ -140,7 +150,7 @@ module Rollbar
|
|
140
150
|
:configuration => configuration
|
141
151
|
)
|
142
152
|
|
143
|
-
backtrace.
|
153
|
+
backtrace.to_h
|
144
154
|
end
|
145
155
|
|
146
156
|
def build_extra
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rollbar/item/frame'
|
2
|
+
|
1
3
|
module Rollbar
|
2
4
|
class Item
|
3
5
|
class Backtrace
|
@@ -5,15 +7,19 @@ module Rollbar
|
|
5
7
|
attr_reader :message
|
6
8
|
attr_reader :extra
|
7
9
|
attr_reader :configuration
|
10
|
+
attr_reader :files
|
11
|
+
|
12
|
+
private :files
|
8
13
|
|
9
14
|
def initialize(exception, options = {})
|
10
15
|
@exception = exception
|
11
16
|
@message = options[:message]
|
12
17
|
@extra = options[:extra]
|
13
18
|
@configuration = options[:configuration]
|
19
|
+
@files = {}
|
14
20
|
end
|
15
21
|
|
16
|
-
def
|
22
|
+
def to_h
|
17
23
|
traces = trace_chain
|
18
24
|
|
19
25
|
traces[0][:exception][:description] = message if message
|
@@ -26,10 +32,23 @@ module Rollbar
|
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
35
|
+
alias_method :build, :to_h
|
36
|
+
|
37
|
+
def get_file_lines(filename)
|
38
|
+
files[filename] ||= read_file(filename)
|
39
|
+
end
|
40
|
+
|
29
41
|
private
|
30
42
|
|
43
|
+
def read_file(filename)
|
44
|
+
return unless File.exist?(filename)
|
45
|
+
|
46
|
+
File.read(filename).split("\n")
|
47
|
+
rescue
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
31
51
|
def trace_chain
|
32
|
-
exception
|
33
52
|
traces = [trace_data(exception)]
|
34
53
|
visited = [exception]
|
35
54
|
|
@@ -45,12 +64,8 @@ module Rollbar
|
|
45
64
|
end
|
46
65
|
|
47
66
|
def trace_data(current_exception)
|
48
|
-
frames = reduce_frames(current_exception)
|
49
|
-
# reverse so that the order is as rollbar expects
|
50
|
-
frames.reverse!
|
51
|
-
|
52
67
|
{
|
53
|
-
:frames =>
|
68
|
+
:frames => map_frames(current_exception),
|
54
69
|
:exception => {
|
55
70
|
:class => current_exception.class.name,
|
56
71
|
:message => current_exception.message
|
@@ -58,16 +73,10 @@ module Rollbar
|
|
58
73
|
}
|
59
74
|
end
|
60
75
|
|
61
|
-
def
|
62
|
-
exception_backtrace(current_exception).map do |frame|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if match
|
67
|
-
{ :filename => match[1], :lineno => match[2].to_i, :method => match[3] }
|
68
|
-
else
|
69
|
-
{ :filename => '<unknown>', :lineno => 0, :method => frame }
|
70
|
-
end
|
76
|
+
def map_frames(current_exception)
|
77
|
+
exception_backtrace(current_exception).reverse.map do |frame|
|
78
|
+
Rollbar::Item::Frame.new(self, frame,
|
79
|
+
:configuration => configuration).to_h
|
71
80
|
end
|
72
81
|
end
|
73
82
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# We want to use Gem.path
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
module Rollbar
|
5
|
+
class Item
|
6
|
+
# Representation of the trace data per frame in the payload
|
7
|
+
class Frame
|
8
|
+
attr_reader :backtrace
|
9
|
+
attr_reader :frame
|
10
|
+
attr_reader :configuration
|
11
|
+
|
12
|
+
MAX_CONTEXT_LENGTH = 4
|
13
|
+
|
14
|
+
def initialize(backtrace, frame, options = {})
|
15
|
+
@backtrace = backtrace
|
16
|
+
@frame = frame
|
17
|
+
@configuration = options[:configuration]
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
# parse the line
|
22
|
+
match = frame.match(/(.*):(\d+)(?::in `([^']+)')?/)
|
23
|
+
|
24
|
+
return unknown_frame unless match
|
25
|
+
|
26
|
+
filename = match[1]
|
27
|
+
lineno = match[2].to_i
|
28
|
+
frame_data = {
|
29
|
+
:filename => filename,
|
30
|
+
:lineno => lineno,
|
31
|
+
:method => match[3]
|
32
|
+
}
|
33
|
+
|
34
|
+
frame_data.merge(extra_frame_data(filename, lineno))
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def unknown_frame
|
40
|
+
{ :filename => '<unknown>', :lineno => 0, :method => frame }
|
41
|
+
end
|
42
|
+
|
43
|
+
def extra_frame_data(filename, lineno)
|
44
|
+
file_lines = backtrace.get_file_lines(filename)
|
45
|
+
|
46
|
+
return {} if skip_extra_frame_data?(filename, file_lines)
|
47
|
+
|
48
|
+
{
|
49
|
+
:code => code_data(file_lines, lineno),
|
50
|
+
:context => context_data(file_lines, lineno)
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def skip_extra_frame_data?(filename, file_lines)
|
55
|
+
config = configuration.send_extra_frame_data
|
56
|
+
missing_file_lines = !file_lines || file_lines.empty?
|
57
|
+
|
58
|
+
return false if !missing_file_lines && config == :all
|
59
|
+
|
60
|
+
missing_file_lines ||
|
61
|
+
config == :none ||
|
62
|
+
config == :app && outside_project?(filename)
|
63
|
+
end
|
64
|
+
|
65
|
+
def outside_project?(filename)
|
66
|
+
project_gem_paths = configuration.project_gem_paths
|
67
|
+
inside_project_gem_paths = project_gem_paths.any? do |path|
|
68
|
+
filename.start_with?(path)
|
69
|
+
end
|
70
|
+
|
71
|
+
# The file is inside the configuration.project_gem_paths,
|
72
|
+
return false if inside_project_gem_paths
|
73
|
+
|
74
|
+
root = configuration.root
|
75
|
+
inside_root = root && filename.start_with?(root.to_s)
|
76
|
+
|
77
|
+
# The file is outside the configuration.root
|
78
|
+
return true unless inside_root
|
79
|
+
|
80
|
+
# At this point, the file is inside the configuration.root.
|
81
|
+
# Since it's common to have gems installed in {root}/vendor/bundle,
|
82
|
+
# let's check it's in any of the Gem.path paths
|
83
|
+
Gem.path.any? { |path| filename.start_with?(path) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def code_data(file_lines, lineno)
|
87
|
+
file_lines[lineno - 1]
|
88
|
+
end
|
89
|
+
|
90
|
+
def context_data(file_lines, lineno)
|
91
|
+
{
|
92
|
+
:pre => pre_data(file_lines, lineno),
|
93
|
+
:post => post_data(file_lines, lineno)
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def post_data(file_lines, lineno)
|
98
|
+
from_line = lineno
|
99
|
+
number_of_lines = [from_line + MAX_CONTEXT_LENGTH, file_lines.size].min - from_line
|
100
|
+
|
101
|
+
file_lines[from_line, number_of_lines]
|
102
|
+
end
|
103
|
+
|
104
|
+
def pre_data(file_lines, lineno)
|
105
|
+
to_line = lineno - 2
|
106
|
+
from_line = [to_line - MAX_CONTEXT_LENGTH + 1, 0].max
|
107
|
+
|
108
|
+
file_lines[from_line, (to_line - from_line + 1)].select(&:present?)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -3,6 +3,7 @@ require 'rack/response'
|
|
3
3
|
|
4
4
|
module Rollbar
|
5
5
|
module Middleware
|
6
|
+
# Middleware to inject the rollbar.js snippet into a 200 html response
|
6
7
|
class Js
|
7
8
|
attr_reader :app
|
8
9
|
attr_reader :config
|
@@ -18,38 +19,24 @@ module Rollbar
|
|
18
19
|
def call(env)
|
19
20
|
result = app.call(env)
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def _call(env, result)
|
27
|
-
return result unless should_add_js?(env, result[0], result[1])
|
22
|
+
begin
|
23
|
+
return result unless add_js?(env, result[0], result[1])
|
28
24
|
|
29
|
-
|
30
|
-
env
|
31
|
-
|
32
|
-
|
33
|
-
response.finish
|
34
|
-
else
|
25
|
+
response_string = add_js(env, result[2])
|
26
|
+
build_response(env, result, response_string)
|
27
|
+
rescue => e
|
28
|
+
Rollbar.log_error("[Rollbar] Rollbar.js could not be added because #{e} exception")
|
35
29
|
result
|
36
30
|
end
|
37
|
-
rescue => e
|
38
|
-
Rollbar.log_error("[Rollbar] Rollbar.js could not be added because #{e} exception")
|
39
|
-
result
|
40
31
|
end
|
41
32
|
|
42
33
|
def enabled?
|
43
34
|
!!config[:enabled]
|
44
35
|
end
|
45
36
|
|
46
|
-
def
|
47
|
-
enabled? &&
|
48
|
-
|
49
|
-
!env[JS_IS_INJECTED_KEY] &&
|
50
|
-
html?(headers) &&
|
51
|
-
!attachment?(headers) &&
|
52
|
-
!streaming?(env)
|
37
|
+
def add_js?(env, status, headers)
|
38
|
+
enabled? && status == 200 && !env[JS_IS_INJECTED_KEY] &&
|
39
|
+
html?(headers) && !attachment?(headers) && !streaming?(env)
|
53
40
|
end
|
54
41
|
|
55
42
|
def html?(headers)
|
@@ -75,28 +62,39 @@ module Rollbar
|
|
75
62
|
head_open_end = find_end_of_head_open(body)
|
76
63
|
return nil unless head_open_end
|
77
64
|
|
78
|
-
|
79
|
-
body = body[0..head_open_end] <<
|
80
|
-
config_js_tag(env) <<
|
81
|
-
snippet_js_tag(env) <<
|
82
|
-
body[head_open_end + 1..-1]
|
83
|
-
end
|
84
|
-
|
85
|
-
body
|
65
|
+
build_body_with_js(env, body, head_open_end)
|
86
66
|
rescue => e
|
87
67
|
Rollbar.log_error("[Rollbar] Rollbar.js could not be added because #{e} exception")
|
88
68
|
nil
|
89
69
|
end
|
90
70
|
|
71
|
+
def build_response(env, app_result, response_string)
|
72
|
+
return result unless response_string
|
73
|
+
|
74
|
+
env[JS_IS_INJECTED_KEY] = true
|
75
|
+
response = ::Rack::Response.new(response_string, app_result[0],
|
76
|
+
app_result[1])
|
77
|
+
|
78
|
+
response.finish
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_body_with_js(env, body, head_open_end)
|
82
|
+
return body unless head_open_end
|
83
|
+
|
84
|
+
body[0..head_open_end] << config_js_tag(env) << snippet_js_tag(env) <<
|
85
|
+
body[head_open_end + 1..-1]
|
86
|
+
end
|
87
|
+
|
91
88
|
def find_end_of_head_open(body)
|
92
89
|
head_open = body.index(/<head\W/)
|
93
90
|
body.index('>', head_open) if head_open
|
94
91
|
end
|
95
92
|
|
96
93
|
def join_body(response)
|
97
|
-
|
98
|
-
|
99
|
-
|
94
|
+
response.to_enum.reduce('') do |acc, fragment|
|
95
|
+
acc << fragment.to_s
|
96
|
+
acc
|
97
|
+
end
|
100
98
|
end
|
101
99
|
|
102
100
|
def close_old_response(response)
|
@@ -116,7 +114,7 @@ module Rollbar
|
|
116
114
|
end
|
117
115
|
|
118
116
|
def script_tag(content, env)
|
119
|
-
if
|
117
|
+
if append_nonce?
|
120
118
|
nonce = ::SecureHeaders.content_security_policy_script_nonce(::Rack::Request.new(env))
|
121
119
|
script_tag_content = "\n<script type=\"text/javascript\" nonce=\"#{nonce}\">#{content}</script>"
|
122
120
|
else
|
@@ -130,6 +128,12 @@ module Rollbar
|
|
130
128
|
string = string.html_safe if string.respond_to?(:html_safe)
|
131
129
|
string
|
132
130
|
end
|
131
|
+
|
132
|
+
def append_nonce?
|
133
|
+
defined?(::SecureHeaders) && ::SecureHeaders.respond_to?(:content_security_policy_script_nonce) &&
|
134
|
+
defined?(::SecureHeaders::Configuration) &&
|
135
|
+
!::SecureHeaders::Configuration.get.current_csp[:script_src].to_a.include?("'unsafe-inline'")
|
136
|
+
end
|
133
137
|
end
|
134
138
|
end
|
135
139
|
end
|
@@ -66,11 +66,11 @@ module Rollbar
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def context(request_data)
|
69
|
-
return unless request_data[:
|
69
|
+
return unless request_data[:params]
|
70
70
|
|
71
|
-
|
71
|
+
route_params = request_data[:params]
|
72
72
|
# make sure route is a hash built by RequestDataExtractor
|
73
|
-
return
|
73
|
+
return route_params[:controller].to_s + '#' + route_params[:action].to_s if route_params.is_a?(Hash) && !route_params.empty?
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|