sapience 0.2.4 → 0.2.5

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +21 -0
  3. data/.simplecov +19 -16
  4. data/.travis.yml +3 -3
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +0 -8
  7. data/README.md +5 -0
  8. data/config/default.yml +4 -0
  9. data/docker-compose.yml +14 -0
  10. data/lib/sapience/appender/stream.rb +0 -1
  11. data/lib/sapience/base.rb +34 -4
  12. data/lib/sapience/configuration.rb +41 -20
  13. data/lib/sapience/core_ext/hash.rb +10 -7
  14. data/lib/sapience/core_ext/symbol.rb +15 -0
  15. data/lib/sapience/core_ext/thread.rb +1 -0
  16. data/lib/sapience/extensions/action_cable/tagged_logger_proxy.rb +2 -0
  17. data/lib/sapience/extensions/action_controller/live.rb +2 -0
  18. data/lib/sapience/extensions/action_controller/log_subscriber.rb +76 -121
  19. data/lib/sapience/extensions/action_dispatch/debug_exceptions.rb +2 -0
  20. data/lib/sapience/extensions/action_view/log_subscriber.rb +16 -6
  21. data/lib/sapience/extensions/action_view/streaming_template_renderer.rb +2 -0
  22. data/lib/sapience/extensions/active_job/logging.rb +1 -1
  23. data/lib/sapience/extensions/active_model_serializers/logging.rb +7 -2
  24. data/lib/sapience/extensions/active_record/log_subscriber.rb +45 -29
  25. data/lib/sapience/extensions/rails/rack/logger.rb +1 -0
  26. data/lib/sapience/extensions/rails/rack/logger_info_as_debug.rb +2 -0
  27. data/lib/sapience/formatters/color.rb +0 -1
  28. data/lib/sapience/formatters/default.rb +0 -1
  29. data/lib/sapience/formatters/json.rb +0 -1
  30. data/lib/sapience/formatters/raw.rb +0 -1
  31. data/lib/sapience/log.rb +54 -35
  32. data/lib/sapience/logger.rb +50 -71
  33. data/lib/sapience/rails.rb +17 -20
  34. data/lib/sapience/sapience.rb +23 -27
  35. data/lib/sapience/subscriber.rb +1 -13
  36. data/lib/sapience/version.rb +1 -1
  37. data/lib/sapience.rb +4 -3
  38. data/sapience.gemspec +6 -1
  39. data/test_app/Gemfile +7 -0
  40. data/test_app/Rakefile +2 -0
  41. data/test_app/app/models/post.rb +1 -1
  42. data/test_app/app/views/posts/_form.html.slim +18 -0
  43. data/test_app/app/views/posts/edit.html.slim +8 -0
  44. data/test_app/app/views/posts/index.html.slim +25 -0
  45. data/test_app/app/views/posts/new.html.slim +5 -0
  46. data/test_app/app/views/posts/show.html.slim +15 -0
  47. data/test_app/app/workers/test_worker.rb +17 -0
  48. data/test_app/bin/sneakers +10 -0
  49. data/test_app/config/initializers/sneakers.rb +15 -0
  50. data/test_app/config/sapience_example.yml +24 -0
  51. data/test_app/db/migrate/{20160812093621_create_posts.rb → 20160902141445_create_posts.rb} +1 -1
  52. data/test_app/db/schema.rb +1 -1
  53. data/test_app/lib/external_sneaker.rb +46 -0
  54. data/test_app/spec/factories/posts.rb +7 -0
  55. data/test_app/spec/factories/users.rb +8 -0
  56. data/test_app/spec/rails_helper.rb +8 -3
  57. data/test_app/spec/requests/posts_spec.rb +2 -1
  58. data/test_app/spec/views/posts/edit.html.slim_spec.rb +17 -0
  59. data/test_app/spec/views/posts/index.html.slim_spec.rb +17 -0
  60. data/test_app/spec/views/posts/new.html.slim_spec.rb +17 -0
  61. data/test_app/spec/views/posts/show.html.slim_spec.rb +14 -0
  62. data/test_app/spec/workers/test_worker_spec.rb +36 -0
  63. data/test_app.simplecov +19 -0
  64. metadata +95 -15
  65. data/.coveralls.yml +0 -1
  66. data/lib/sapience/extensions/action_controller/log_subscriber_processing.rb +0 -24
  67. data/test_app/app/views/posts/_form.html.erb +0 -32
  68. data/test_app/app/views/posts/create.html.erb +0 -2
  69. data/test_app/app/views/posts/destroy.html.erb +0 -2
  70. data/test_app/app/views/posts/edit.html.erb +0 -6
  71. data/test_app/app/views/posts/index.html.erb +0 -31
  72. data/test_app/app/views/posts/new.html.erb +0 -5
  73. data/test_app/app/views/posts/show.html.erb +0 -19
  74. data/test_app/app/views/posts/update.html.erb +0 -2
@@ -1,127 +1,82 @@
1
1
  require "action_controller/log_subscriber"
2
2
 
3
- class ActionController::LogSubscriber # rubocop:disable ClassAndModuleChildren
4
- # Log as debug to hide Processing messages in production
5
- def start_processing(event)
6
- controller_logger(event).debug { "Processing ##{event.payload[:action]}" }
7
- end
8
-
9
- def process_action(event) # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
10
- controller_logger(event).info do
11
- payload = event.payload.dup
12
- payload[:params].except!(*INTERNAL_PARAMS)
13
- payload.delete(:params) if payload[:params].empty?
14
-
15
- format = payload[:format]
16
- payload[:format] = format.to_s.upcase if format.is_a?(Symbol)
17
-
18
- payload[:path] = extract_path(payload[:path]) if payload.key?(:path)
19
-
20
- exception = payload.delete(:exception)
21
- if payload[:status].nil? && exception.present?
22
- exception_class_name = exception.first
23
- payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
24
- end
25
-
26
- # Rounds off the runtimes. For example, :view_runtime, :mongo_runtime, etc.
27
- payload.keys.each do |key|
28
- payload[key] = payload[key].to_f.round(2) if key.to_s.match(/(.*)_runtime/)
3
+ module Sapience
4
+ module Extensions
5
+ module ActionController
6
+ class LogSubscriber < ::ActionController::LogSubscriber # rubocop:disable ClassLength
7
+ alias_method :orig_start_processing, :start_processing
8
+ alias_method :orig_process_action, :process_action
9
+
10
+ # Log as debug to hide Processing messages in production
11
+ def start_processing(event)
12
+ debug { "Processing ##{event.payload[:action]}" }
13
+ end
14
+
15
+ def process_action(event) # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
16
+ return unless logger.info?
17
+ data = request(event.payload)
18
+ data.merge! runtimes(event)
19
+ data.merge! exception(event.payload)
20
+ info(data)
21
+ end
22
+
23
+ private
24
+
25
+ def request(payload) # rubocop:disable AbcSize
26
+ {
27
+ method: payload[:method].upcase,
28
+ request_path: request_path(payload),
29
+ format: format(payload),
30
+ status: payload[:status].to_i,
31
+ controller: payload[:params]["controller"],
32
+ action: payload[:params]["action"],
33
+ host: Sapience.config.host,
34
+ route: "#{payload[:params].delete("controller")}##{payload[:params]["action"]}",
35
+ message: "Completed ##{payload[:params].delete("action")}",
36
+ tags: Sapience.tags,
37
+ params: payload[:params],
38
+ }
39
+ end
40
+
41
+ def format(payload)
42
+ if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR == 0
43
+ payload[:formats].first
44
+ else
45
+ payload[:format]
46
+ end
47
+ end
48
+
49
+ def runtimes(event)
50
+ {
51
+ total: event.duration,
52
+ view: event.payload[:view_runtime],
53
+ db: event.payload[:db_runtime],
54
+ }.each_with_object({}) do |(name, runtime), runtimes|
55
+ runtimes[:runtimes] ||= {}
56
+ runtimes[:runtimes][name] = runtime.to_f.round(2) if runtime
57
+ runtimes
58
+ end
59
+ end
60
+
61
+ # Monkey patching to enable exception logging
62
+ def exception(payload)
63
+ if payload[:exception]
64
+ exception, message = payload[:exception]
65
+ message ||= exception.message
66
+ status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception)
67
+ backtrace = $ERROR_INFO.try(:backtrace).try(:first)
68
+ backtrace ||= exception.backtrace.first
69
+ message = "#{exception}\n#{message}\n#{backtrace}"
70
+ { status: status, error: message }
71
+ else
72
+ {}
73
+ end
74
+ end
75
+
76
+ def request_path(payload)
77
+ payload[:path].split("?").first
78
+ end
29
79
  end
30
-
31
- payload[:message] = "Completed ##{payload[:action]}"
32
- payload[:status_message] = Rack::Utils::HTTP_STATUS_CODES[payload[:status]] if payload[:status].present?
33
- payload[:duration] = event.duration
34
- # Causes excessive log output with Rails 5 RC1
35
- payload.delete(:headers)
36
- payload
37
- end
38
- end
39
-
40
- def halted_callback(event)
41
- controller_logger(event).info do
42
- "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected"
43
- end
44
- end
45
-
46
- def send_file(event)
47
- controller_logger(event).info("Sent file") { { path: event.payload[:path], duration: event.duration } }
48
- end
49
-
50
- def redirect_to(event)
51
- controller_logger(event).info("Redirected to") { { location: event.payload[:location] } }
52
- end
53
-
54
- def send_data(event)
55
- controller_logger(event).info("Sent data") { { file_name: event.payload[:filename], duration: event.duration } }
56
- end
57
-
58
- def unpermitted_parameters(event)
59
- controller_logger(event).debug do
60
- unpermitted_keys = event.payload[:keys]
61
- "Unpermitted parameter#{"s" if unpermitted_keys.size > 1}: #{unpermitted_keys.join(", ")}"
62
- end
63
- end
64
-
65
- private
66
-
67
- # Returns the logger for the supplied event.
68
- # Returns ActionController::Base.logger if no controller is present
69
- def controller_logger(event)
70
- if (controller = event.payload[:controller])
71
- begin
72
- controller.constantize.logger
73
- rescue NameError
74
- ActionController::Base.logger
75
- end
76
- else
77
- ActionController::Base.logger
78
- end
79
- end
80
-
81
- def extract_path(path)
82
- index = path.index("?")
83
- index ? path[0, index] : path
84
- end
85
-
86
- def write_fragment(event)
87
- controller_logger(event).info do
88
- key_or_path = event.payload[:key] || event.payload[:path]
89
- { message: "Write fragment #{key_or_path}", duration: event.duration }
90
- end
91
- end
92
-
93
- def read_fragment(event)
94
- controller_logger(event).info do
95
- key_or_path = event.payload[:key] || event.payload[:path]
96
- { message: "Read fragment #{key_or_path}", duration: event.duration }
97
- end
98
- end
99
-
100
- def exist_fragment(event)
101
- controller_logger(event).info do
102
- key_or_path = event.payload[:key] || event.payload[:path]
103
- { message: "Exist fragment #{key_or_path}", duration: event.duration }
104
- end
105
- end
106
-
107
- def expire_fragment(event)
108
- controller_logger(event).info do
109
- key_or_path = event.payload[:key] || event.payload[:path]
110
- { message: "Expire fragment #{key_or_path}", duration: event.duration }
111
- end
112
- end
113
-
114
- def expire_page(event)
115
- controller_logger(event).info do
116
- key_or_path = event.payload[:key] || event.payload[:path]
117
- { message: "Expire page #{key_or_path}", duration: event.duration }
118
- end
119
- end
120
-
121
- def write_page(event)
122
- controller_logger(event).info do
123
- key_or_path = event.payload[:key] || event.payload[:path]
124
- { message: "Write page #{key_or_path}", duration: event.duration }
125
80
  end
126
81
  end
127
82
  end
@@ -3,6 +3,8 @@
3
3
  class ActionDispatch::DebugExceptions # rubocop:disable ClassAndModuleChildren
4
4
  private
5
5
 
6
+ alias_method :orig_log_error, :log_error
7
+
6
8
  def log_error(_request, wrapper)
7
9
  ActiveSupport::Deprecation.silence do
8
10
  ActionController::Base.logger.fatal(wrapper.exception)
@@ -1,9 +1,19 @@
1
- class ActionView::LogSubscriber # rubocop:disable ClassAndModuleChildren
2
- def info(message = nil, &block)
3
- debug(message, &block)
4
- end
1
+ require "action_view/log_subscriber"
2
+
3
+ module Sapience
4
+ module Extensions
5
+ module ActionView
6
+ class LogSubscriber < ::ActionView::LogSubscriber
7
+ include Sapience::Loggable
8
+
9
+ def info(message = nil, &block)
10
+ logger.debug(message, &block)
11
+ end
5
12
 
6
- def info?
7
- debug?
13
+ def info?
14
+ logger.debug?
15
+ end
16
+ end
17
+ end
8
18
  end
9
19
  end
@@ -4,6 +4,8 @@ class ActionView::StreamingTemplateRenderer # rubocop:disable ClassAndModuleChil
4
4
  class Body
5
5
  private
6
6
 
7
+ alias_method :log_error_original, :log_error
8
+
7
9
  def log_error(exception) #:nodoc:
8
10
  ActionView::Base.logger.fatal(exception)
9
11
  end
@@ -6,7 +6,7 @@ module ActiveJob::Logging # rubocop:disable ClassAndModuleChildren
6
6
 
7
7
  private
8
8
 
9
- alias_method :tag_logger_old, :tag_logger
9
+ alias_method :orig_tag_logger, :tag_logger
10
10
 
11
11
  def tag_logger(*tags, &block)
12
12
  logger.tagged(*tags, &block)
@@ -1,14 +1,19 @@
1
1
  # Patch ActiveModelSerializers logger
2
+ require "active_model_serializers"
2
3
  require "active_model_serializers/logging"
3
4
 
4
5
  module ActiveModelSerializers::Logging # rubocop:disable ClassAndModuleChildren
5
- include Sapience::Loggable
6
+ def self.included(base)
7
+ base.send(:include, Sapience::Loggable)
8
+ end
6
9
 
7
10
  private
8
11
 
9
- alias_method :tag_logger_old, :tag_logger
12
+ alias_method :orig_tag_logger, :tag_logger
10
13
 
11
14
  def tag_logger(*tags, &block)
12
15
  logger.tagged(*tags, &block)
13
16
  end
14
17
  end
18
+
19
+ ActiveModelSerializers.send(:include, Sapience::Loggable)
@@ -1,35 +1,51 @@
1
- ActiveRecord::LogSubscriber # rubocop:disable Lint/Void
2
-
3
- class ActiveRecord::LogSubscriber # rubocop:disable ClassAndModuleChildren
4
- def sql(event) # rubocop:disable AbcSize
5
- self.class.runtime += event.duration
6
-
7
- return unless logger.debug?
8
-
9
- payload = event.payload
10
- name = payload[:name]
11
- return if IGNORE_PAYLOAD_NAMES.include?(name)
12
-
13
- log = {
14
- message: name,
15
- sql: payload[:sql],
16
- duration: event.duration,
17
- }
18
- unless (payload[:binds] || []).empty?
19
- log[:binds] = binds = {}
20
- # Changed with Rails 5
21
- if Rails.version.to_i >= 5
22
- payload[:binds].each do |attr|
23
- attr_name, value = render_bind(attr)
24
- binds[attr_name] = value
1
+ require "active_support/notifications"
2
+ require "active_record/log_subscriber"
3
+
4
+ module Sapience
5
+ module Extensions
6
+ module ActiveRecord
7
+ class LogSubscriber < ::ActiveRecord::LogSubscriber
8
+ include Sapience::Loggable
9
+
10
+ def identity(event)
11
+ lsevent = logstash_event(event)
12
+ logger << lsevent.to_json + "\n" if logger && lsevent
25
13
  end
26
- else
27
- payload[:binds].each do |col, v|
28
- attr_name, value = render_bind(col, v)
29
- binds[attr_name] = value
14
+ alias_method :sql, :identity
15
+
16
+ private
17
+
18
+ def logstash_event(event)
19
+ data = event.payload
20
+
21
+ return if "SCHEMA" == data[:name]
22
+
23
+ data.merge! runtimes(event)
24
+ data.merge! extract_sql(data)
25
+
26
+ data.merge! tags(data)
27
+ debug(data)
28
+ end
29
+
30
+ def runtimes(event)
31
+ if event.duration
32
+ { duration: event.duration.to_f.round(2) }
33
+ else
34
+ {}
35
+ end
36
+ end
37
+
38
+ def extract_sql(data)
39
+ { sql: data[:sql].squeeze(" ") }
40
+ end
41
+
42
+ def tags(data)
43
+ tags = Sapience.tags.dup
44
+ tags.push("request")
45
+ tags.push("exception") if data[:exception]
46
+ { tags: tags }
30
47
  end
31
48
  end
32
49
  end
33
- debug(log)
34
50
  end
35
51
  end
@@ -1,5 +1,6 @@
1
1
  # Replace rack started message with a semantic equivalent
2
2
  class Rails::Rack::Logger # rubocop:disable ClassAndModuleChildren
3
+ alias_method :started_request_message_original, :started_request_message
3
4
  def started_request_message(request)
4
5
  {
5
6
  message: "Started",
@@ -13,6 +13,8 @@ class Rails::Rack::Logger # rubocop:disable ClassAndModuleChildren
13
13
  end
14
14
  end
15
15
 
16
+ alias_method :orig_logger, :logger
17
+
16
18
  def logger
17
19
  @logger ||= begin
18
20
  logger = Sapience["Rails"]
@@ -62,7 +62,6 @@ module Sapience
62
62
  end
63
63
  message
64
64
  end
65
-
66
65
  end
67
66
  end
68
67
  end
@@ -35,7 +35,6 @@ module Sapience
35
35
  end
36
36
  message
37
37
  end
38
-
39
38
  end
40
39
  end
41
40
  end
@@ -16,7 +16,6 @@ module Sapience
16
16
  h[:timestamp] = format_time(log.time)
17
17
  h.to_json
18
18
  end
19
-
20
19
  end
21
20
  end
22
21
  end
@@ -6,7 +6,6 @@ module Sapience
6
6
  def call(log, logger)
7
7
  log.to_h(log_host ? logger.host : nil, log_application ? logger.application : nil)
8
8
  end
9
-
10
9
  end
11
10
  end
12
11
  end
data/lib/sapience/log.rb CHANGED
@@ -38,7 +38,7 @@ module Sapience
38
38
  # Object supplied when measure_x was called
39
39
  #
40
40
  # backtrace [Array<String>]
41
- # The backtrace captured at source when the log level >= Sapience.config.backtrace_level
41
+ # The backtrace_level captured at source when the log level >= Sapience.config.backtrace_level
42
42
  #
43
43
  # metric_amount [Numeric]
44
44
  # Used for numeric or counter metrics.
@@ -47,34 +47,17 @@ module Sapience
47
47
  # rubocop:disable LineLength
48
48
  Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount) do
49
49
  MAX_EXCEPTIONS_TO_UNWRAP = 5
50
- # Call the block for exception and any nested exception
51
- def each_exception # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
52
- # With thanks to https://github.com/bugsnag/bugsnag-ruby/blob/6348306e44323eee347896843d16c690cd7c4362/lib/bugsnag/notification.rb#L81
53
- depth = 0
54
- exceptions = []
55
- ex = exception
56
- while !ex.nil? && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
57
- exceptions << ex
58
- yield(ex, depth)
59
-
60
- depth += 1
61
- ex =
62
- if ex.respond_to?(:cause) && ex.cause
63
- ex.cause
64
- elsif ex.respond_to?(:continued_exception) && ex.continued_exception
65
- ex.continued_exception
66
- elsif ex.respond_to?(:original_exception) && ex.original_exception
67
- ex.original_exception
68
- end
69
- end
70
- end
50
+ MILLISECONDS_IN_SECOND = 1_000
51
+ MILLISECONDS_IN_MINUTE = 60_000
52
+ MILLISECONDS_IN_HOUR = 3_600_000
53
+ MILLISECONDS_IN_DAY = 86_400_000
71
54
 
72
55
  # Returns [String] the exception backtrace including all of the child / caused by exceptions
73
56
  def backtrace_to_s
74
57
  trace = ""
75
58
  each_exception do |exception, i|
76
59
  if i == 0
77
- trace = (exception.backtrace || []).join("\n")
60
+ trace << (exception.backtrace || []).join("\n")
78
61
  else
79
62
  trace << "\nCause: #{exception.class.name}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}"
80
63
  end
@@ -90,19 +73,28 @@ module Sapience
90
73
  end
91
74
 
92
75
  # Returns [String] the duration in human readable form
93
- def duration_human # rubocop:disable AbcSize
76
+ def duration_human # rubocop:disable AbcSize, CyclomaticComplexity, PerceivedComplexity
94
77
  return nil unless duration
95
- seconds = duration / 1000
96
- if seconds >= 86_400.0 # 1 day
97
- "#{(seconds / 86_400).to_i}d #{Time.at(seconds).strftime("%-Hh %-Mm")}"
98
- elsif seconds >= 3600.0 # 1 hour
99
- Time.at(seconds).strftime("%-Hh %-Mm")
100
- elsif seconds >= 60.0 # 1 minute
101
- Time.at(seconds).strftime("%-Mm %-Ss")
102
- elsif seconds >= 1.0 # 1 second
103
- format "%.3fs", seconds
78
+ days, ms = duration.divmod(MILLISECONDS_IN_DAY)
79
+ hours, ms = ms.divmod(MILLISECONDS_IN_HOUR)
80
+ minutes, ms = ms.divmod(MILLISECONDS_IN_MINUTE)
81
+ seconds, ms = ms.divmod(MILLISECONDS_IN_SECOND)
82
+
83
+ str = ""
84
+ str << "#{days}d" if days > 0
85
+ str << " #{hours}h" if hours > 0
86
+ str << " #{minutes}m" if minutes > 0
87
+ str << " #{seconds}s" if seconds > 0
88
+ str << " #{ms}ms" if ms > 0
89
+
90
+ if days > 0 || hours > 0 || minutes > 0
91
+ str.strip
104
92
  else
105
- duration_to_s
93
+ if seconds >= 1.0
94
+ format "%.3fs", duration / MILLISECONDS_IN_SECOND.to_f
95
+ else
96
+ duration_to_s
97
+ end
106
98
  end
107
99
  end
108
100
 
@@ -210,7 +202,7 @@ module Sapience
210
202
  message: exception.message,
211
203
  stack_trace: exception.backtrace,
212
204
  }
213
- root = root[name]
205
+ root = root[name]
214
206
  end
215
207
  end
216
208
 
@@ -218,6 +210,33 @@ module Sapience
218
210
  h[:metric] = metric if metric
219
211
  h
220
212
  end
213
+
214
+ private
215
+
216
+ # Call the block for exception and any nested exception
217
+ def each_exception # rubocop:disable AbcSize, PerceivedComplexity, CyclomaticComplexity
218
+ # With thanks to https://github.com/bugsnag/bugsnag-ruby/blob/6348306e44323eee347896843d16c690cd7c4362/lib/bugsnag/notification.rb#L81
219
+ depth = 0
220
+ exceptions = []
221
+ ex = exception
222
+ while !ex.nil? && !exceptions.include?(ex) && exceptions.length < MAX_EXCEPTIONS_TO_UNWRAP
223
+ exceptions << ex
224
+ yield(ex, depth)
225
+
226
+ depth += 1
227
+ ex =
228
+ # continued_exception is only used by REXML
229
+ # original_exception is deprecated in Rails 5+
230
+ # Not worth testing to thoroughly?
231
+ if ex.respond_to?(:cause) && ex.cause
232
+ ex.cause
233
+ elsif ex.respond_to?(:continued_exception) && ex.continued_exception
234
+ ex.continued_exception
235
+ elsif ex.respond_to?(:original_exception) && ex.original_exception
236
+ ex.original_exception
237
+ end
238
+ end
239
+ end
221
240
  end
222
241
  # rubocop:enable LineLength
223
242
  end