rollbar 2.22.1 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +34 -0
  3. data/.github/workflows/ci.yml +104 -0
  4. data/.rubocop.yml +185 -33
  5. data/Gemfile +26 -28
  6. data/README.md +32 -8
  7. data/data/rollbar.snippet.js +1 -1
  8. data/docs/configuration.md +8 -0
  9. data/gemfiles/rails30.gemfile +17 -35
  10. data/gemfiles/rails31.gemfile +20 -37
  11. data/gemfiles/rails32.gemfile +13 -31
  12. data/gemfiles/rails40.gemfile +12 -32
  13. data/gemfiles/rails41.gemfile +11 -31
  14. data/gemfiles/rails42.gemfile +12 -32
  15. data/gemfiles/rails50.gemfile +16 -30
  16. data/gemfiles/rails51.gemfile +16 -30
  17. data/gemfiles/rails52.gemfile +10 -19
  18. data/gemfiles/rails60.gemfile +10 -25
  19. data/gemfiles/rails61.gemfile +52 -0
  20. data/gemfiles/rails70.gemfile +52 -0
  21. data/lib/generators/rollbar/rollbar_generator.rb +18 -14
  22. data/lib/rails/rollbar_runner.rb +11 -20
  23. data/lib/rollbar/capistrano.rb +17 -9
  24. data/lib/rollbar/capistrano3.rb +8 -2
  25. data/lib/rollbar/capistrano_tasks.rb +44 -8
  26. data/lib/rollbar/configuration.rb +138 -84
  27. data/lib/rollbar/delay/girl_friday.rb +3 -7
  28. data/lib/rollbar/delay/resque.rb +2 -3
  29. data/lib/rollbar/delay/shoryuken.rb +4 -3
  30. data/lib/rollbar/delay/sidekiq.rb +5 -5
  31. data/lib/rollbar/delay/sucker_punch.rb +4 -6
  32. data/lib/rollbar/delay/thread.rb +17 -2
  33. data/lib/rollbar/deploy.rb +8 -7
  34. data/lib/rollbar/encoding/encoder.rb +17 -6
  35. data/lib/rollbar/encoding.rb +2 -7
  36. data/lib/rollbar/exception_reporter.rb +17 -8
  37. data/lib/rollbar/item/backtrace.rb +22 -10
  38. data/lib/rollbar/item/frame.rb +8 -5
  39. data/lib/rollbar/item/locals.rb +49 -2
  40. data/lib/rollbar/item.rb +80 -50
  41. data/lib/rollbar/json.rb +2 -1
  42. data/lib/rollbar/language_support.rb +0 -6
  43. data/lib/rollbar/lazy_store.rb +3 -7
  44. data/lib/rollbar/logger.rb +2 -0
  45. data/lib/rollbar/logger_proxy.rb +3 -1
  46. data/lib/rollbar/middleware/js/json_value.rb +15 -5
  47. data/lib/rollbar/middleware/js.rb +70 -38
  48. data/lib/rollbar/middleware/rack/builder.rb +3 -3
  49. data/lib/rollbar/middleware/rack/test_session.rb +3 -3
  50. data/lib/rollbar/middleware/rack.rb +4 -4
  51. data/lib/rollbar/middleware/rails/rollbar.rb +9 -6
  52. data/lib/rollbar/middleware/rails/show_exceptions.rb +8 -4
  53. data/lib/rollbar/notifier/trace_with_bindings.rb +13 -3
  54. data/lib/rollbar/notifier.rb +309 -172
  55. data/lib/rollbar/plugin.rb +8 -8
  56. data/lib/rollbar/plugins/active_job.rb +20 -3
  57. data/lib/rollbar/plugins/delayed_job/plugin.rb +19 -2
  58. data/lib/rollbar/plugins/error_context.rb +11 -0
  59. data/lib/rollbar/plugins/goalie.rb +27 -16
  60. data/lib/rollbar/plugins/rails/controller_methods.rb +18 -14
  61. data/lib/rollbar/plugins/rails/railtie30.rb +2 -1
  62. data/lib/rollbar/plugins/rails/railtie32.rb +2 -1
  63. data/lib/rollbar/plugins/rails/railtie_mixin.rb +2 -2
  64. data/lib/rollbar/plugins/rails.rb +5 -2
  65. data/lib/rollbar/plugins/rake.rb +2 -1
  66. data/lib/rollbar/plugins/sidekiq/plugin.rb +39 -21
  67. data/lib/rollbar/plugins/sidekiq.rb +1 -1
  68. data/lib/rollbar/plugins/thread.rb +8 -7
  69. data/lib/rollbar/plugins/validations.rb +3 -1
  70. data/lib/rollbar/rake_tasks.rb +1 -2
  71. data/lib/rollbar/request_data_extractor.rb +53 -19
  72. data/lib/rollbar/rollbar_test.rb +9 -118
  73. data/lib/rollbar/scrubbers/params.rb +13 -7
  74. data/lib/rollbar/scrubbers/url.rb +56 -17
  75. data/lib/rollbar/scrubbers.rb +2 -6
  76. data/lib/rollbar/truncation/frames_strategy.rb +1 -1
  77. data/lib/rollbar/truncation/mixin.rb +1 -1
  78. data/lib/rollbar/truncation/remove_any_key_strategy.rb +4 -1
  79. data/lib/rollbar/truncation/remove_extra_strategy.rb +3 -1
  80. data/lib/rollbar/truncation/strings_strategy.rb +4 -2
  81. data/lib/rollbar/util/hash.rb +14 -7
  82. data/lib/rollbar/util/ip_anonymizer.rb +1 -1
  83. data/lib/rollbar/util.rb +23 -13
  84. data/lib/rollbar/version.rb +1 -1
  85. data/lib/rollbar.rb +12 -7
  86. data/lib/tasks/benchmark.rake +2 -1
  87. data/rollbar.gemspec +6 -3
  88. data/spec/support/rollbar_api.rb +67 -0
  89. metadata +19 -12
  90. data/.travis.yml +0 -281
  91. data/lib/rollbar/encoding/legacy_encoder.rb +0 -20
  92. /data/lib/generators/rollbar/templates/{initializer.rb → initializer.erb} +0 -0
@@ -3,11 +3,7 @@ require 'rollbar/item/frame'
3
3
  module Rollbar
4
4
  class Item
5
5
  class Backtrace
6
- attr_reader :exception
7
- attr_reader :message
8
- attr_reader :extra
9
- attr_reader :configuration
10
- attr_reader :files
6
+ attr_reader :exception, :message, :extra, :configuration, :files
11
7
 
12
8
  private :files
13
9
 
@@ -54,7 +50,10 @@ module Rollbar
54
50
 
55
51
  current_exception = exception
56
52
 
57
- while current_exception.respond_to?(:cause) && (cause = current_exception.cause) && cause.is_a?(Exception) && !visited.include?(cause)
53
+ while current_exception.respond_to?(:cause) &&
54
+ (cause = current_exception.cause) &&
55
+ cause.is_a?(Exception) &&
56
+ !visited.include?(cause)
58
57
  traces << trace_data(cause)
59
58
  visited << cause
60
59
  current_exception = cause
@@ -74,10 +73,20 @@ module Rollbar
74
73
  end
75
74
 
76
75
  def map_frames(current_exception)
77
- exception_backtrace(current_exception).map do |frame|
76
+ frames = cleaned_backtrace(current_exception).map do |frame|
78
77
  Rollbar::Item::Frame.new(self, frame,
79
78
  :configuration => configuration).to_h
80
- end.reverse
79
+ end
80
+ frames.reverse!
81
+ end
82
+
83
+ def cleaned_backtrace(current_exception)
84
+ normalized_backtrace = exception_backtrace(current_exception)
85
+ if configuration.backtrace_cleaner
86
+ configuration.backtrace_cleaner.clean(normalized_backtrace)
87
+ else
88
+ normalized_backtrace
89
+ end
81
90
  end
82
91
 
83
92
  # Returns the backtrace to be sent to our API. There are 3 options:
@@ -90,7 +99,9 @@ module Rollbar
90
99
  # are those from the user's Rollbar.error line until this method. We want
91
100
  # to remove those lines.
92
101
  def exception_backtrace(current_exception)
93
- return current_exception.backtrace if current_exception.backtrace.respond_to?(:map)
102
+ if current_exception.backtrace.respond_to?(:map)
103
+ return current_exception.backtrace
104
+ end
94
105
  return [] unless configuration.populate_empty_backtraces
95
106
 
96
107
  caller_backtrace = caller
@@ -99,7 +110,8 @@ module Rollbar
99
110
  end
100
111
 
101
112
  def rollbar_lib_gem_dir
102
- Gem::Specification.find_by_name('rollbar').gem_dir + '/lib'
113
+ gem_dir = Gem::Specification.find_by_name('rollbar').gem_dir
114
+ "#{gem_dir}/lib"
103
115
  end
104
116
  end
105
117
  end
@@ -6,9 +6,7 @@ module Rollbar
6
6
  class Item
7
7
  # Representation of the trace data per frame in the payload
8
8
  class Frame
9
- attr_reader :backtrace
10
- attr_reader :frame
11
- attr_reader :configuration
9
+ attr_reader :backtrace, :frame, :configuration
12
10
 
13
11
  MAX_CONTEXT_LENGTH = 4
14
12
 
@@ -97,12 +95,15 @@ module Rollbar
97
95
  end
98
96
 
99
97
  def locals_data(filename, lineno)
98
+ return unless configuration.locals[:enabled]
99
+
100
100
  ::Rollbar::Item::Locals.locals_for_location(filename, lineno)
101
101
  end
102
102
 
103
103
  def post_data(file_lines, lineno)
104
104
  from_line = lineno
105
- number_of_lines = [from_line + MAX_CONTEXT_LENGTH, file_lines.size].min - from_line
105
+ number_of_lines = [from_line + MAX_CONTEXT_LENGTH,
106
+ file_lines.size].min - from_line
106
107
 
107
108
  file_lines[from_line, number_of_lines]
108
109
  end
@@ -111,7 +112,9 @@ module Rollbar
111
112
  to_line = lineno - 2
112
113
  from_line = [to_line - MAX_CONTEXT_LENGTH + 1, 0].max
113
114
 
114
- file_lines[from_line, (to_line - from_line + 1)].select { |line| line && !line.empty? }
115
+ file_lines[from_line, (to_line - from_line + 1)].select do |line|
116
+ line && !line.empty?
117
+ end
115
118
  end
116
119
  end
117
120
  end
@@ -1,5 +1,5 @@
1
- require 'rollbar/notifier'
2
1
  require 'rollbar/scrubbers/params'
2
+ require 'rollbar/util'
3
3
 
4
4
  module Rollbar
5
5
  class Item
@@ -32,6 +32,8 @@ module Rollbar
32
32
  end
33
33
 
34
34
  def locals_for(frame)
35
+ return {} unless frame
36
+
35
37
  {}.tap do |hash|
36
38
  frame.local_variables.map do |var|
37
39
  hash[var] = prepare_value(frame.local_variable_get(var))
@@ -39,8 +41,53 @@ module Rollbar
39
41
  end
40
42
  end
41
43
 
44
+ # Prepare objects to be handled by the payload serializer.
45
+ #
46
+ # Hashes and Arrays are traversed. Then all types execpt strings and
47
+ # immediates are exported using #inspect. Sending the object itself to the
48
+ # serializer can result in large recursive expansions, especially in Rails
49
+ # environments with ActiveRecord, ActiveSupport, etc. on the stack.
50
+ # Other export options could be #to_s, #to_h, and #as_json. Several of these
51
+ # will omit the class name, or are not implemented for many types.
52
+ #
53
+ # #inspect has the advantage that it is specifically intended for debugging
54
+ # output. If the user wants more or different information in the payload
55
+ # about a specific type, #inspect is the correct place to implement it.
56
+ # Likewise the default implementation should be oriented toward usefulness
57
+ # in debugging.
58
+ #
59
+ # Because #inspect outputs a string, it can be handled well by the string
60
+ # truncation strategy for large payloads.
61
+ #
42
62
  def prepare_value(value)
43
- value.to_s
63
+ unless value.is_a?(Hash) || value.is_a?(Array)
64
+ return simple_value?(value) ? value : value.inspect
65
+ end
66
+
67
+ cloned_value = ::Rollbar::Util.deep_copy(value)
68
+ ::Rollbar::Util.iterate_and_update_with_block(cloned_value) do |v|
69
+ simple_value?(v) ? v : v.inspect
70
+ end
71
+
72
+ cloned_value
73
+ end
74
+
75
+ def simple_classes
76
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
77
+ [String, Symbol, Integer, Float, TrueClass, FalseClass, NilClass]
78
+ else
79
+ [String, Symbol, Fixnum, Bignum, Float, TrueClass, FalseClass, NilClass] # rubocop:disable Lint/UnifiedInteger
80
+ end
81
+ end
82
+
83
+ def simple_value?(value)
84
+ simple_classes.each do |klass|
85
+ # Use instance_of? so that subclasses and module containers will
86
+ # be treated like complex object types, not simple values.
87
+ return true if value.instance_of?(klass)
88
+ end
89
+
90
+ false
44
91
  end
45
92
 
46
93
  def scrub(hash)
data/lib/rollbar/item.rb CHANGED
@@ -23,24 +23,15 @@ module Rollbar
23
23
 
24
24
  attr_writer :payload
25
25
 
26
- attr_reader :level
27
- attr_reader :message
28
- attr_reader :exception
29
- attr_reader :extra
30
-
31
- attr_reader :configuration
32
- attr_reader :scope
33
- attr_reader :logger
34
- attr_reader :notifier
35
-
36
- attr_reader :context
26
+ attr_reader :level, :message, :exception, :extra, :configuration, :scope, :logger,
27
+ :notifier, :context
37
28
 
38
29
  def_delegators :payload, :[]
39
30
 
40
31
  class << self
41
32
  def build_with(payload, options = {})
42
33
  new(options).tap do |item|
43
- item.payload = payload
34
+ item.payload = item.add_access_token_to_payload(payload)
44
35
  end
45
36
  end
46
37
  end
@@ -64,10 +55,7 @@ module Rollbar
64
55
 
65
56
  def build
66
57
  data = build_data
67
- self.payload = {
68
- 'access_token' => configuration.access_token,
69
- 'data' => data
70
- }
58
+ self.payload = add_access_token_to_payload({ 'data' => data })
71
59
 
72
60
  enforce_valid_utf8
73
61
  transform
@@ -75,7 +63,22 @@ module Rollbar
75
63
  end
76
64
 
77
65
  def build_data
78
- data = {
66
+ data = initial_data
67
+
68
+ build_optional_data(data)
69
+
70
+ Util.deep_merge(data, configuration.payload_options)
71
+ Util.deep_merge(data, scope)
72
+
73
+ # Our API doesn't allow null context values, so just delete
74
+ # the key if value is nil.
75
+ data.delete(:context) unless data[:context]
76
+
77
+ data
78
+ end
79
+
80
+ def initial_data
81
+ {
79
82
  :timestamp => Time.now.to_i,
80
83
  :environment => build_environment,
81
84
  :level => level,
@@ -89,35 +92,33 @@ module Rollbar
89
92
  },
90
93
  :body => build_body
91
94
  }
92
- data[:project_package_paths] = configuration.project_gem_paths if configuration.project_gem_paths.any?
93
- data[:code_version] = configuration.code_version if configuration.code_version
94
- data[:uuid] = SecureRandom.uuid if defined?(SecureRandom) && SecureRandom.respond_to?(:uuid)
95
+ end
95
96
 
96
- Util.deep_merge(data, configuration.payload_options)
97
- Util.deep_merge(data, scope)
97
+ def build_optional_data(data)
98
+ if configuration.project_gem_paths.any?
99
+ data[:project_package_paths] = configuration.project_gem_paths
100
+ end
98
101
 
99
- # Our API doesn't allow null context values, so just delete
100
- # the key if value is nil.
101
- data.delete(:context) unless data[:context]
102
+ data[:code_version] = configuration.code_version if configuration.code_version
102
103
 
103
- data
104
+ return unless defined?(SecureRandom) && SecureRandom.respond_to?(:uuid)
105
+
106
+ data[:uuid] = SecureRandom.uuid
104
107
  end
105
108
 
106
109
  def configured_options
107
- if Gem.loaded_specs['activesupport'] && Gem.loaded_specs['activesupport'].version < Gem::Version.new('4.1')
108
- # There are too many types that crash ActiveSupport JSON serialization, and not worth
109
- # the risk just to send this diagnostic object. In versions < 4.1, ActiveSupport hooks
110
- # Ruby's JSON.generate so deeply there's no workaround.
110
+ if Gem.loaded_specs['activesupport'] &&
111
+ Gem.loaded_specs['activesupport'].version < Gem::Version.new('4.1')
112
+ # There are too many types that crash ActiveSupport JSON serialization,
113
+ # and not worth the risk just to send this diagnostic object.
114
+ # In versions < 4.1, ActiveSupport hooks Ruby's JSON.generate so deeply
115
+ # that there's no workaround.
111
116
  'not serialized in ActiveSupport < 4.1'
112
- elsif configuration.use_async
113
- # Currently serialization is performed by each handler, and this invariably
114
- # means it is actually performed by ActiveSupport.
115
- #
116
- # TODO: Since serialization must be done prior to scheduling the job,
117
- # it should at least be done by rollbar-gem itself. Much work has been done
118
- # to avoid the bugs in ActiveSupport JSON. The async handlers are currently
119
- # still subject to all those knnown issues.
120
- 'not serialized for async/delayed handlers'
117
+ elsif configuration.use_async && !configuration.async_json_payload
118
+ # The setting allows serialization to be performed by each handler,
119
+ # and this usually means it is actually performed by ActiveSupport,
120
+ # which cannot safely serialize this key.
121
+ 'not serialized when async_json_payload is not set'
121
122
  else
122
123
  scrub(configuration.configured_options.configured)
123
124
  end
@@ -137,22 +138,28 @@ module Rollbar
137
138
  nil
138
139
  end
139
140
 
140
- def handle_too_large_payload(stringified_payload, final_payload, attempts)
141
+ def handle_too_large_payload(stringified_payload, _final_payload, attempts)
141
142
  uuid = stringified_payload['data']['uuid']
142
143
  host = stringified_payload['data'].fetch('server', {})['host']
143
144
 
144
- notifier.send_failsafe(
145
- too_large_payload_string(attempts),
146
- nil,
147
- uuid,
148
- host
149
- )
150
- logger.error("[Rollbar] Payload too large to be sent for UUID #{uuid}: #{Rollbar::JSON.dump(payload)}")
145
+ original_error = {
146
+ :message => message,
147
+ :exception => exception,
148
+ :configuration => configuration,
149
+ :uuid => uuid,
150
+ :host => host
151
+ }
152
+
153
+ notifier.send_failsafe(too_large_payload_string(attempts), nil, original_error)
154
+
155
+ logger.error('[Rollbar] Payload too large to be sent for UUID ' \
156
+ "#{uuid}: #{Rollbar::JSON.dump(payload)}")
151
157
  end
152
158
 
153
159
  def too_large_payload_string(attempts)
154
160
  'Could not send payload due to it being too large after truncating attempts. ' \
155
- "Original size: #{attempts.first} Attempts: #{attempts.join(', ')} Final size: #{attempts.last}"
161
+ "Original size: #{attempts.first} Attempts: #{attempts.join(', ')} " \
162
+ "Final size: #{attempts.last}"
156
163
  end
157
164
 
158
165
  def ignored?
@@ -164,6 +171,22 @@ module Rollbar
164
171
  configuration.ignored_person_ids.include?(person_id)
165
172
  end
166
173
 
174
+ def add_access_token_to_payload(payload)
175
+ # Some use cases remain where the token is needed in the payload. For example:
176
+ #
177
+ # When using async senders, if the access token is changed dynamically in
178
+ # the main process config, the sender process won't see that change.
179
+ #
180
+ # Until the delayed sender interface is changed to allow passing dynamic
181
+ # config options, this workaround allows the main process to set the token
182
+ # by adding it to the payload.
183
+ if configuration && configuration.use_payload_access_token
184
+ payload['access_token'] ||= configuration.access_token
185
+ end
186
+
187
+ payload
188
+ end
189
+
167
190
  private
168
191
 
169
192
  def build_environment
@@ -187,13 +210,20 @@ module Rollbar
187
210
  end
188
211
 
189
212
  def build_extra
213
+ merged_extra = Util.deep_merge(scrub(extra), scrub(error_context))
214
+
190
215
  if custom_data_method? && !Rollbar::Util.method_in_stack(:custom_data, __FILE__)
191
- Util.deep_merge(scrub(custom_data), scrub(extra) || {})
216
+ Util.deep_merge(scrub(custom_data), merged_extra)
192
217
  else
193
- scrub(extra)
218
+ # avoid putting an empty {} in the payload.
219
+ merged_extra.empty? ? nil : merged_extra
194
220
  end
195
221
  end
196
222
 
223
+ def error_context
224
+ exception.respond_to?(:rollbar_context) && exception.rollbar_context
225
+ end
226
+
197
227
  def scrub(data)
198
228
  return data unless data.is_a? Hash
199
229
 
data/lib/rollbar/json.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  require 'rollbar/language_support'
2
+ require 'json'
2
3
 
3
4
  module Rollbar
4
5
  module JSON # :nodoc:
5
- extend self
6
+ module_function
6
7
 
7
8
  attr_writer :options_module
8
9
 
@@ -10,10 +10,6 @@ module Rollbar
10
10
  mod.const_get(target, inherit)
11
11
  end
12
12
 
13
- def ruby_19?
14
- version?('1.9')
15
- end
16
-
17
13
  def version?(version)
18
14
  numbers = version.split('.')
19
15
 
@@ -21,8 +17,6 @@ module Rollbar
21
17
  end
22
18
 
23
19
  def timeout_exceptions
24
- return [] if ruby_19?
25
-
26
20
  [Net::ReadTimeout, Net::OpenTimeout]
27
21
  end
28
22
  end
@@ -1,10 +1,8 @@
1
1
  module Rollbar
2
2
  class LazyStore
3
- attr_reader :loaded_data
3
+ attr_reader :loaded_data, :raw
4
4
  private :loaded_data
5
5
 
6
- attr_reader :raw
7
-
8
6
  def initialize(initial_data)
9
7
  initial_data ||= {}
10
8
 
@@ -41,8 +39,6 @@ module Rollbar
41
39
  raw[key] = value
42
40
 
43
41
  loaded_data.delete(key)
44
-
45
- value
46
42
  end
47
43
 
48
44
  def data
@@ -76,8 +72,8 @@ module Rollbar
76
72
  super
77
73
  end
78
74
 
79
- def respond_to?(method_sym)
80
- super || raw.respond_to?(method_sym)
75
+ def respond_to_missing?(method_sym, include_all)
76
+ raw.respond_to?(method_sym, include_all)
81
77
  end
82
78
  end
83
79
  end
@@ -16,7 +16,9 @@ module Rollbar
16
16
  # Rails.logger.extend(ActiveSupport::Logger.broadcast(Rollbar::Logger.new))
17
17
  class Logger < ::Logger
18
18
  class Error < RuntimeError; end
19
+
19
20
  class DatetimeFormatNotSupported < Error; end
21
+
20
22
  class FormatterNotSupported < Error; end
21
23
 
22
24
  def initialize
@@ -23,7 +23,9 @@ module Rollbar
23
23
  end
24
24
 
25
25
  def log(level, message)
26
- return unless Rollbar.configuration.enabled && acceptable_levels.include?(level.to_sym)
26
+ unless Rollbar.configuration.enabled && acceptable_levels.include?(level.to_sym)
27
+ return
28
+ end
27
29
 
28
30
  @object.send(level, message)
29
31
  rescue StandardError
@@ -1,8 +1,12 @@
1
- # Allows a Ruby String to be used to create native Javascript objects
2
- # when calling JSON#generate.
1
+ # Allows a Ruby String to be used to pass native Javascript objects/functions
2
+ # when calling JSON#generate with a Rollbar::JSON::JsOptionsState instance.
3
3
  #
4
4
  # Example:
5
- # JSON.generate({ foo: Rollbar::JSON::Value.new('function(){ alert("bar") }') })
5
+ # JSON.generate(
6
+ # { foo: Rollbar::JSON::Value.new('function(){ alert("bar") }') },
7
+ # Rollbar::JSON::JsOptionsState.new
8
+ # )
9
+ #
6
10
  # => '{"foo":function(){ alert(\"bar\") }}'
7
11
  #
8
12
  # MUST use the Ruby JSON encoder, as in the example. The ActiveSupport encoder,
@@ -11,6 +15,8 @@
11
15
  #
12
16
  module Rollbar
13
17
  module JSON
18
+ class JsOptionsState < ::JSON::State; end
19
+
14
20
  class Value # :nodoc:
15
21
  attr_accessor :value
16
22
 
@@ -18,8 +24,12 @@ module Rollbar
18
24
  @value = value
19
25
  end
20
26
 
21
- def to_json(*_args)
22
- value
27
+ def to_json(opts = {})
28
+ # Return the raw value if this is from the js middleware
29
+ return value if opts.class == Rollbar::JSON::JsOptionsState
30
+
31
+ # Otherwise convert to a string
32
+ %Q["#{value}"]
23
33
  end
24
34
  end
25
35
  end