rollbar 2.12.0 → 2.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|