jordan-brough-hoptoad_notifier 2.3.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 (45) hide show
  1. data/CHANGELOG +161 -0
  2. data/INSTALL +25 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +384 -0
  5. data/Rakefile +217 -0
  6. data/SUPPORTED_RAILS_VERSIONS +9 -0
  7. data/TESTING.rdoc +8 -0
  8. data/generators/hoptoad/hoptoad_generator.rb +63 -0
  9. data/generators/hoptoad/lib/insert_commands.rb +34 -0
  10. data/generators/hoptoad/lib/rake_commands.rb +24 -0
  11. data/generators/hoptoad/templates/capistrano_hook.rb +6 -0
  12. data/generators/hoptoad/templates/hoptoad_notifier_tasks.rake +25 -0
  13. data/generators/hoptoad/templates/initializer.rb +6 -0
  14. data/lib/hoptoad_notifier.rb +148 -0
  15. data/lib/hoptoad_notifier/backtrace.rb +99 -0
  16. data/lib/hoptoad_notifier/capistrano.rb +20 -0
  17. data/lib/hoptoad_notifier/configuration.rb +232 -0
  18. data/lib/hoptoad_notifier/notice.rb +318 -0
  19. data/lib/hoptoad_notifier/rack.rb +40 -0
  20. data/lib/hoptoad_notifier/rails.rb +37 -0
  21. data/lib/hoptoad_notifier/rails/action_controller_catcher.rb +29 -0
  22. data/lib/hoptoad_notifier/rails/controller_methods.rb +63 -0
  23. data/lib/hoptoad_notifier/rails/error_lookup.rb +33 -0
  24. data/lib/hoptoad_notifier/rails3_tasks.rb +90 -0
  25. data/lib/hoptoad_notifier/railtie.rb +23 -0
  26. data/lib/hoptoad_notifier/sender.rb +63 -0
  27. data/lib/hoptoad_notifier/tasks.rb +97 -0
  28. data/lib/hoptoad_notifier/version.rb +3 -0
  29. data/lib/hoptoad_tasks.rb +44 -0
  30. data/lib/rails/generators/hoptoad/hoptoad_generator.rb +69 -0
  31. data/lib/templates/rescue.erb +91 -0
  32. data/rails/init.rb +1 -0
  33. data/script/integration_test.rb +38 -0
  34. data/test/backtrace_test.rb +118 -0
  35. data/test/catcher_test.rb +324 -0
  36. data/test/configuration_test.rb +208 -0
  37. data/test/helper.rb +239 -0
  38. data/test/hoptoad_tasks_test.rb +152 -0
  39. data/test/logger_test.rb +85 -0
  40. data/test/notice_test.rb +443 -0
  41. data/test/notifier_test.rb +222 -0
  42. data/test/rack_test.rb +58 -0
  43. data/test/rails_initializer_test.rb +36 -0
  44. data/test/sender_test.rb +123 -0
  45. metadata +205 -0
@@ -0,0 +1,217 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'cucumber/rake/task'
6
+
7
+ desc 'Default: run unit tests.'
8
+ task :default => [:test, :cucumber]
9
+
10
+ desc 'Test the hoptoad_notifier gem.'
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = true
15
+ end
16
+
17
+ desc 'Run ginger tests'
18
+ task :ginger do
19
+ $LOAD_PATH << File.join(*%w[vendor ginger lib])
20
+ ARGV.clear
21
+ ARGV << 'test'
22
+ load File.join(*%w[vendor ginger bin ginger])
23
+ end
24
+
25
+ namespace :changeling do
26
+ desc "Bumps the version by a minor or patch version, depending on what was passed in."
27
+ task :bump, :part do |t, args|
28
+ # Thanks, Jeweler!
29
+ if HoptoadNotifier::VERSION =~ /^(\d+)\.(\d+)\.(\d+)(?:\.(.*?))?$/
30
+ major = $1.to_i
31
+ minor = $2.to_i
32
+ patch = $3.to_i
33
+ build = $4
34
+ else
35
+ abort
36
+ end
37
+
38
+ case args[:part]
39
+ when /minor/
40
+ minor += 1
41
+ patch = 0
42
+ when /patch/
43
+ patch += 1
44
+ else
45
+ abort
46
+ end
47
+
48
+ version = [major, minor, patch, build].compact.join('.')
49
+
50
+ File.open(File.join("lib", "hoptoad_notifier", "version.rb"), "w") do |f|
51
+ f.write <<EOF
52
+ module HoptoadNotifier
53
+ VERSION = "#{version}".freeze
54
+ end
55
+ EOF
56
+ end
57
+ end
58
+
59
+ desc "Writes out the new CHANGELOG and prepares the release"
60
+ task :change do
61
+ load 'lib/hoptoad_notifier/version.rb'
62
+ file = "CHANGELOG"
63
+ old = File.read(file)
64
+ version = HoptoadNotifier::VERSION
65
+ message = "Bumping to version #{version}"
66
+
67
+ File.open(file, "w") do |f|
68
+ f.write <<EOF
69
+ Version #{version} - #{Date.today}
70
+ ===============================================================================
71
+
72
+ #{`git log $(git tag | tail -1)..HEAD | git shortlog`}
73
+ #{old}
74
+ EOF
75
+ end
76
+
77
+ exec ["#{ENV["EDITOR"]} #{file}",
78
+ "git commit -aqm '#{message}'",
79
+ "git tag -a -m '#{message}' v#{version}",
80
+ "echo '\n\n\033[32mMarked v#{version} /' `git show-ref -s refs/heads/master` 'for release. Run: rake changeling:push\033[0m\n\n'"].join(' && ')
81
+ end
82
+
83
+ desc "Bump by a minor version (1.2.3 => 1.3.0)"
84
+ task :minor do |t|
85
+ Rake::Task['changeling:bump'].invoke(t.name)
86
+ Rake::Task['changeling:change'].invoke
87
+ end
88
+
89
+ desc "Bump by a patch version, (1.2.3 => 1.2.4)"
90
+ task :patch do |t|
91
+ Rake::Task['changeling:bump'].invoke(t.name)
92
+ Rake::Task['changeling:change'].invoke
93
+ end
94
+
95
+ desc "Push the latest version and tags"
96
+ task :push do |t|
97
+ system("git push origin master")
98
+ system("git push origin $(git tag | tail -1)")
99
+ end
100
+ end
101
+
102
+ begin
103
+ require 'yard'
104
+ YARD::Rake::YardocTask.new do |t|
105
+ t.files = ['lib/**/*.rb', 'TESTING.rdoc']
106
+ end
107
+ rescue LoadError
108
+ end
109
+
110
+ GEM_ROOT = File.dirname(__FILE__).freeze
111
+ VERSION_FILE = File.join(GEM_ROOT, 'lib', 'hoptoad_notifier', 'version')
112
+
113
+ require VERSION_FILE
114
+
115
+ gemspec = Gem::Specification.new do |s|
116
+ s.name = %q{hoptoad_notifier}
117
+ s.version = HoptoadNotifier::VERSION
118
+ s.summary = %q{Send your application errors to our hosted service and reclaim your inbox.}
119
+
120
+ s.files = FileList['[A-Z]*', 'generators/**/*.*', 'lib/**/*.rb',
121
+ 'test/**/*.rb', 'rails/**/*.rb', 'script/*',
122
+ 'lib/templates/*.erb']
123
+ s.require_path = 'lib'
124
+ s.test_files = Dir[*['test/**/*_test.rb']]
125
+
126
+ s.has_rdoc = true
127
+ s.extra_rdoc_files = ["README.rdoc"]
128
+ s.rdoc_options = ['--line-numbers', "--main", "README.rdoc"]
129
+
130
+ s.add_runtime_dependency("activesupport")
131
+ s.add_development_dependency("activerecord")
132
+ s.add_development_dependency("actionpack")
133
+ s.add_development_dependency("jferris-mocha")
134
+ s.add_development_dependency("nokogiri")
135
+ s.add_development_dependency("shoulda")
136
+
137
+ s.authors = ["thoughtbot, inc"]
138
+ s.email = %q{support@hoptoadapp.com}
139
+ s.homepage = "http://www.hoptoadapp.com"
140
+
141
+ s.platform = Gem::Platform::RUBY
142
+ end
143
+
144
+ Rake::GemPackageTask.new gemspec do |pkg|
145
+ pkg.need_tar = true
146
+ pkg.need_zip = true
147
+ end
148
+
149
+ desc "Clean files generated by rake tasks"
150
+ task :clobber => [:clobber_rdoc, :clobber_package]
151
+
152
+ desc "Generate a gemspec file"
153
+ task :gemspec do
154
+ File.open("#{gemspec.name}.gemspec", 'w') do |f|
155
+ f.write gemspec.to_ruby
156
+ end
157
+ end
158
+
159
+ LOCAL_GEM_ROOT = File.join(GEM_ROOT, 'tmp', 'local_gems').freeze
160
+ RAILS_VERSIONS = IO.read('SUPPORTED_RAILS_VERSIONS').strip.split("\n")
161
+ LOCAL_GEMS = [['sham_rack', nil], ['capistrano', nil], ['sqlite3-ruby', nil], ['sinatra', nil]] +
162
+ RAILS_VERSIONS.collect { |version| ['rails', version] }
163
+
164
+ task :vendor_test_gems do
165
+ old_gem_path = ENV['GEM_PATH']
166
+ old_gem_home = ENV['GEM_HOME']
167
+ ENV['GEM_PATH'] = LOCAL_GEM_ROOT
168
+ ENV['GEM_HOME'] = LOCAL_GEM_ROOT
169
+ LOCAL_GEMS.each do |gem_name, version|
170
+ gem_file_pattern = [gem_name, version || '*'].compact.join('-')
171
+ version_option = version ? "-v #{version}" : ''
172
+ pattern = File.join(LOCAL_GEM_ROOT, 'gems', "#{gem_file_pattern}")
173
+ existing = Dir.glob(pattern).first
174
+ unless existing
175
+ command = "gem install -i #{LOCAL_GEM_ROOT} --no-ri --no-rdoc --backtrace #{version_option} #{gem_name}"
176
+ puts "Vendoring #{gem_file_pattern}..."
177
+ unless system("#{command} 2>&1")
178
+ puts "Command failed: #{command}"
179
+ exit(1)
180
+ end
181
+ end
182
+ end
183
+ ENV['GEM_PATH'] = old_gem_path
184
+ ENV['GEM_HOME'] = old_gem_home
185
+ end
186
+
187
+ Cucumber::Rake::Task.new(:cucumber) do |t|
188
+ t.fork = true
189
+ t.cucumber_opts = ['--format', (ENV['CUCUMBER_FORMAT'] || 'progress')]
190
+ end
191
+
192
+ task :cucumber => [:gemspec, :vendor_test_gems]
193
+
194
+ def define_rails_cucumber_tasks(additional_cucumber_args = '')
195
+ namespace :rails do
196
+ RAILS_VERSIONS.each do |version|
197
+ desc "Test integration of the gem with Rails #{version}"
198
+ task version => [:gemspec, :vendor_test_gems] do
199
+ puts "Testing Rails #{version}"
200
+ ENV['RAILS_VERSION'] = version
201
+ system("cucumber --format #{ENV['CUCUMBER_FORMAT'] || 'progress'} #{additional_cucumber_args} features/rails.feature")
202
+ end
203
+ end
204
+
205
+ desc "Test integration of the gem with all Rails versions"
206
+ task :all => RAILS_VERSIONS
207
+ end
208
+ end
209
+
210
+ namespace :cucumber do
211
+ namespace :wip do
212
+ define_rails_cucumber_tasks('--tags @wip')
213
+ end
214
+
215
+ define_rails_cucumber_tasks
216
+ end
217
+
@@ -0,0 +1,9 @@
1
+ 1.2.6
2
+ 2.0.2
3
+ 2.1.0
4
+ 2.1.2
5
+ 2.2.2
6
+ 2.3.2
7
+ 2.3.4
8
+ 2.3.5
9
+ 3.0.0.beta4
@@ -0,0 +1,8 @@
1
+ = For Maintainers:
2
+
3
+ When developing the Hoptoad Notifier, be sure to use the integration test
4
+ against an existing project on staging before pushing to master.
5
+
6
+ +./script/integration_test.rb <test project's api key> <staging server hostname>+
7
+
8
+ +./script/integration_test.rb <test project's api key> <staging server hostname> secure+
@@ -0,0 +1,63 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/lib/insert_commands.rb")
2
+ require File.expand_path(File.dirname(__FILE__) + "/lib/rake_commands.rb")
3
+
4
+ class HoptoadGenerator < Rails::Generator::Base
5
+ def add_options!(opt)
6
+ opt.on('-k', '--api-key=key', String, "Your Hoptoad API key") {|v| options[:api_key] = v}
7
+ opt.on('-h', '--heroku', "Use the Heroku addon to provide your Hoptoad API key") {|v| options[:heroku] = v}
8
+ end
9
+
10
+ def manifest
11
+ if !api_key_configured? && !options[:api_key] && !options[:heroku]
12
+ puts "Must pass --api-key or --heroku or create config/initializers/hoptoad.rb"
13
+ exit
14
+ end
15
+ if plugin_is_present?
16
+ puts "You must first remove the hoptoad_notifier plugin. Please run: script/plugin remove hoptoad_notifier"
17
+ exit
18
+ end
19
+ record do |m|
20
+ m.directory 'lib/tasks'
21
+ m.file 'hoptoad_notifier_tasks.rake', 'lib/tasks/hoptoad_notifier_tasks.rake'
22
+ if ['config/deploy.rb', 'Capfile'].all? { |file| File.exists?(file) }
23
+ m.append_to 'config/deploy.rb', capistrano_hook
24
+ end
25
+ if api_key_expression
26
+ if use_initializer?
27
+ m.template 'initializer.rb', 'config/initializers/hoptoad.rb',
28
+ :assigns => {:api_key => api_key_expression}
29
+ else
30
+ m.template 'initializer.rb', 'config/hoptoad.rb',
31
+ :assigns => {:api_key => api_key_expression}
32
+ m.append_to 'config/environment.rb', "require 'config/hoptoad'"
33
+ end
34
+ end
35
+ m.rake "hoptoad:test", :generate_only => true
36
+ end
37
+ end
38
+
39
+ def api_key_expression
40
+ s = if options[:api_key]
41
+ "'#{options[:api_key]}'"
42
+ elsif options[:heroku]
43
+ "ENV['HOPTOAD_API_KEY']"
44
+ end
45
+ end
46
+
47
+ def use_initializer?
48
+ Rails::VERSION::MAJOR > 1
49
+ end
50
+
51
+ def api_key_configured?
52
+ File.exists?('config/initializers/hoptoad.rb') ||
53
+ system("grep HoptoadNotifier config/environment.rb")
54
+ end
55
+
56
+ def capistrano_hook
57
+ IO.read(source_path('capistrano_hook.rb'))
58
+ end
59
+
60
+ def plugin_is_present?
61
+ File.exists?('vendor/plugins/hoptoad_notifier')
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ # Mostly pinched from http://github.com/ryanb/nifty-generators/tree/master
2
+
3
+ Rails::Generator::Commands::Base.class_eval do
4
+ def file_contains?(relative_destination, line)
5
+ File.read(destination_path(relative_destination)).include?(line)
6
+ end
7
+ end
8
+
9
+ Rails::Generator::Commands::Create.class_eval do
10
+ def append_to(file, line)
11
+ logger.insert "#{line} appended to #{file}"
12
+ unless options[:pretend] || file_contains?(file, line)
13
+ File.open(file, "a") do |file|
14
+ file.puts
15
+ file.puts line
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Rails::Generator::Commands::Destroy.class_eval do
22
+ def append_to(file, line)
23
+ logger.remove "#{line} removed from #{file}"
24
+ unless options[:pretend]
25
+ gsub_file file, "\n#{line}", ''
26
+ end
27
+ end
28
+ end
29
+
30
+ Rails::Generator::Commands::List.class_eval do
31
+ def append_to(file, line)
32
+ logger.insert "#{line} appended to #{file}"
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ Rails::Generator::Commands::Create.class_eval do
2
+ def rake(cmd, opts = {})
3
+ logger.rake "rake #{cmd}"
4
+ unless system("rake #{cmd}")
5
+ logger.rake "#{cmd} failed. Rolling back"
6
+ command(:destroy).invoke!
7
+ end
8
+ end
9
+ end
10
+
11
+ Rails::Generator::Commands::Destroy.class_eval do
12
+ def rake(cmd, opts = {})
13
+ unless opts[:generate_only]
14
+ logger.rake "rake #{cmd}"
15
+ system "rake #{cmd}"
16
+ end
17
+ end
18
+ end
19
+
20
+ Rails::Generator::Commands::List.class_eval do
21
+ def rake(cmd, opts = {})
22
+ logger.rake "rake #{cmd}"
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+
2
+ Dir[File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'hoptoad_notifier-*')].each do |vendored_notifier|
3
+ $: << File.join(vendored_notifier, 'lib')
4
+ end
5
+
6
+ require 'hoptoad_notifier/capistrano'
@@ -0,0 +1,25 @@
1
+ # Don't load anything when running the gems:* tasks.
2
+ # Otherwise, hoptoad_notifier will be considered a framework gem.
3
+ # https://thoughtbot.lighthouseapp.com/projects/14221/tickets/629
4
+ unless ARGV.any? {|a| a =~ /^gems/}
5
+
6
+ Dir[File.join(RAILS_ROOT, 'vendor', 'gems', 'hoptoad_notifier-*')].each do |vendored_notifier|
7
+ $: << File.join(vendored_notifier, 'lib')
8
+ end
9
+
10
+ begin
11
+ require 'hoptoad_notifier/tasks'
12
+ rescue LoadError => exception
13
+ namespace :hoptoad do
14
+ %w(deploy test log_stdout).each do |task_name|
15
+ desc "Missing dependency for hoptoad:#{task_name}"
16
+ task task_name do
17
+ $stderr.puts "Failed to run hoptoad:#{task_name} because of missing dependency."
18
+ $stderr.puts "You probably need to run `rake gems:install` to install the hoptoad_notifier gem"
19
+ abort exception.inspect
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,6 @@
1
+ <% if Rails::VERSION::MAJOR < 3 && Rails::VERSION::MINOR < 2 -%>
2
+ require 'hoptoad_notifier/rails'
3
+ <% end -%>
4
+ HoptoadNotifier.configure do |config|
5
+ config.api_key = <%= api_key_expression %>
6
+ end
@@ -0,0 +1,148 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'rubygems'
4
+ require 'active_support'
5
+ require 'hoptoad_notifier/version'
6
+ require 'hoptoad_notifier/configuration'
7
+ require 'hoptoad_notifier/notice'
8
+ require 'hoptoad_notifier/sender'
9
+ require 'hoptoad_notifier/backtrace'
10
+ require 'hoptoad_notifier/rack'
11
+
12
+ require 'hoptoad_notifier/railtie' if defined?(Rails::Railtie)
13
+
14
+ # Gem for applications to automatically post errors to the Hoptoad of their choice.
15
+ module HoptoadNotifier
16
+
17
+ API_VERSION = "2.0"
18
+ LOG_PREFIX = "** [Hoptoad] "
19
+
20
+ HEADERS = {
21
+ 'Content-type' => 'text/xml',
22
+ 'Accept' => 'text/xml, application/xml'
23
+ }
24
+
25
+ class << self
26
+ # The sender object is responsible for delivering formatted data to the Hoptoad server.
27
+ # Must respond to #send_to_hoptoad. See HoptoadNotifier::Sender.
28
+ attr_accessor :sender
29
+
30
+ # A Hoptoad configuration object. Must act like a hash and return sensible
31
+ # values for all Hoptoad configuration options. See HoptoadNotifier::Configuration.
32
+ attr_accessor :configuration
33
+
34
+ # Tell the log that the Notifier is good to go
35
+ def report_ready
36
+ write_verbose_log("Notifier #{VERSION} ready to catch errors")
37
+ end
38
+
39
+ # Prints out the environment info to the log for debugging help
40
+ def report_environment_info
41
+ write_verbose_log("Environment Info: #{environment_info}")
42
+ end
43
+
44
+ # Prints out the response body from Hoptoad for debugging help
45
+ def report_response_body(response)
46
+ write_verbose_log("Response from Hoptoad: \n#{response}")
47
+ end
48
+
49
+ # Returns the Ruby version, Rails version, and current Rails environment
50
+ def environment_info
51
+ info = "[Ruby: #{RUBY_VERSION}]"
52
+ info << " [#{configuration.framework}]"
53
+ info << " [Env: #{configuration.environment_name}]"
54
+ end
55
+
56
+ # Writes out the given message to the #logger
57
+ def write_verbose_log(message)
58
+ logger.info LOG_PREFIX + message if logger
59
+ end
60
+
61
+ # Look for the Rails logger currently defined
62
+ def logger
63
+ self.configuration.logger
64
+ end
65
+
66
+ # Call this method to modify defaults in your initializers.
67
+ #
68
+ # @example
69
+ # HoptoadNotifier.configure do |config|
70
+ # config.api_key = '1234567890abcdef'
71
+ # config.secure = false
72
+ # end
73
+ def configure(silent = false)
74
+ self.configuration ||= Configuration.new
75
+ yield(configuration)
76
+ self.sender = Sender.new(configuration)
77
+ report_ready unless silent
78
+ end
79
+
80
+ # Sends an exception manually using this method, even when you are not in a controller.
81
+ #
82
+ # @param [Exception] exception The exception you want to notify Hoptoad about.
83
+ # @param [Hash] opts Data that will be sent to Hoptoad.
84
+ #
85
+ # @option opts [String] :api_key The API key for this project. The API key is a unique identifier that Hoptoad uses for identification.
86
+ # @option opts [String] :error_message The error returned by the exception (or the message you want to log).
87
+ # @option opts [String] :backtrace A backtrace, usually obtained with +caller+.
88
+ # @option opts [String] :request The controller's request object.
89
+ # @option opts [String] :session The contents of the user's session.
90
+ # @option opts [String] :environment ENV merged with the contents of the request's environment.
91
+ def notify(exception, opts = {})
92
+ send_notice(build_notice_for(exception, opts))
93
+ end
94
+
95
+ # Sends the notice unless it is one of the default ignored exceptions
96
+ # @see HoptoadNotifier.notify
97
+ def notify_or_ignore(exception, opts = {})
98
+ notice = build_notice_for(exception, opts)
99
+ send_notice(notice) unless notice.ignore?
100
+ end
101
+
102
+ def build_lookup_hash_for(exception, options = {})
103
+ notice = build_notice_for(exception, options)
104
+
105
+ result = {}
106
+ result[:action] = notice.action rescue nil
107
+ result[:component] = notice.component rescue nil
108
+ result[:error_class] = notice.error_class if notice.error_class
109
+ result[:environment_name] = 'production'
110
+
111
+ unless notice.backtrace.lines.empty?
112
+ result[:file] = notice.backtrace.lines.first.file
113
+ result[:line_number] = notice.backtrace.lines.first.number
114
+ end
115
+
116
+ result
117
+ end
118
+
119
+ private
120
+
121
+ def send_notice(notice)
122
+ if configuration.public?
123
+ sender.send_to_hoptoad(notice.to_xml)
124
+ end
125
+ end
126
+
127
+ def build_notice_for(exception, opts = {})
128
+ exception = unwrap_exception(exception)
129
+ if exception.respond_to?(:to_hash)
130
+ opts = opts.merge(exception)
131
+ else
132
+ opts = opts.merge(:exception => exception)
133
+ end
134
+ Notice.new(configuration.merge(opts))
135
+ end
136
+
137
+ def unwrap_exception(exception)
138
+ if exception.respond_to?(:original_exception)
139
+ exception.original_exception
140
+ elsif exception.respond_to?(:continued_exception)
141
+ exception.continued_exception
142
+ else
143
+ exception
144
+ end
145
+ end
146
+ end
147
+ end
148
+