Class: ChefApply::UI::ErrorPrinter

Inherits:
Object
  • Object
show all
Defined in:
lib/chef_apply/ui/error_printer.rb

Constant Summary collapse

DEFAULT_ERROR_NO =
"CHEFINT001".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(wrapper, unwrapped = nil, target_host = nil) ⇒ ErrorPrinter

Returns a new instance of ErrorPrinter.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/chef_apply/ui/error_printer.rb', line 92

def initialize(wrapper, unwrapped = nil, target_host = nil)
  @exception = unwrapped || wrapper.contained_exception
  @target_host = wrapper.target_host || target_host
  @command = exception.respond_to?(:command) ? exception.command : nil
  @pastel = Pastel.new
  @content = StringIO.new
  @id = if exception.is_a? ChefApply::Error
          exception.id
        else
          DEFAULT_ERROR_NO
        end
  @translation = ChefApply::Text::ErrorTranslation.new(id)
rescue => e
  ErrorPrinter.dump_unexpected_error(e)
  exit! 128
end

Instance Attribute Details

#exceptionObject (readonly)

Returns the value of attribute exception.



28
29
30
# File 'lib/chef_apply/ui/error_printer.rb', line 28

def exception
  @exception
end

#idObject (readonly)

Returns the value of attribute id.



28
29
30
# File 'lib/chef_apply/ui/error_printer.rb', line 28

def id
  @id
end

#pastelObject (readonly)

Returns the value of attribute pastel.



28
29
30
# File 'lib/chef_apply/ui/error_printer.rb', line 28

def pastel
  @pastel
end

#target_hostObject (readonly)

Returns the value of attribute target_host.



28
29
30
# File 'lib/chef_apply/ui/error_printer.rb', line 28

def target_host
  @target_host
end

#translationObject (readonly)

Returns the value of attribute translation.



28
29
30
# File 'lib/chef_apply/ui/error_printer.rb', line 28

def translation
  @translation
end

Class Method Details

.capture_multiple_failures(e) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/chef_apply/ui/error_printer.rb', line 51

def self.capture_multiple_failures(e)
  out_file = ChefApply::Config.error_output_path
  e.params << out_file # Tell the operator where to find this info
  File.open(out_file, "w") do |out|
    e.jobs.each do |j|
      wrapped = ChefApply::Errors::StandardErrorResolver.wrap_exception(j.exception, j.target_host)
      ep = ErrorPrinter.new(wrapped)
      msg = ep.format_body.tr("\n", " ").gsub(/ {2,}/, " ").chomp.strip
      out.write("Host: #{j.target_host.hostname} ")
      if ep.exception.respond_to? :id
        out.write("Error: #{ep.exception.id}: ")
      else
        out.write(": ")
      end
      out.write("#{msg}\n")
    end
  end
end

.dump_unexpected_error(e) ⇒ Object

Use this to dump an an exception to output. useful if an error occurs in the error handling itself.



82
83
84
85
86
87
88
89
90
# File 'lib/chef_apply/ui/error_printer.rb', line 82

def self.dump_unexpected_error(e)
  Terminal.output "INTERNAL ERROR"
  Terminal.output "-=" * 30
  Terminal.output "Message:"
  Terminal.output e.message if e.respond_to?(:message)
  Terminal.output "Backtrace:"
  Terminal.output e.backtrace if e.respond_to?(:backtrace)
  Terminal.output "=-" * 30
end

.error_summary(e) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/chef_apply/ui/error_printer.rb', line 181

def self.error_summary(e)
  if e.is_a? ChefApply::Error
    # By convention, all of our defined messages have a short summary on the first line.
    ChefApply::Text.errors.send(e.id).text(*e.params).split("\n").first
  elsif e.is_a? String
    e
  else
    if e.respond_to? :message
      e.message
    else
      ChefApply::Text.errors.UNKNOWN
    end
  end
end

.show_error(e) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/chef_apply/ui/error_printer.rb', line 38

def self.show_error(e)
  # Name is misleading - it's unwrapping but also doing further
  # error resolution for common errors:
  unwrapped = ChefApply::Errors::StandardErrorResolver.unwrap_exception(e)
  if unwrapped.class == ChefApply::MultiJobFailure
    capture_multiple_failures(unwrapped)
  end
  formatter = ErrorPrinter.new(e, unwrapped)
  Terminal.output(formatter.format_error)
rescue => e
  dump_unexpected_error(e)
end

.write_backtrace(e, args) ⇒ Object



70
71
72
73
74
75
76
77
78
# File 'lib/chef_apply/ui/error_printer.rb', line 70

def self.write_backtrace(e, args)
  formatter = ErrorPrinter.new(e)
  out = StringIO.new
  formatter.add_backtrace_header(out, args)
  formatter.add_formatted_backtrace(out)
  formatter.save_backtrace(out)
rescue => ex
  dump_unexpected_error(ex)
end

Instance Method Details

#_format_single(out, exception, backtrace = nil) ⇒ Object



245
246
247
248
249
# File 'lib/chef_apply/ui/error_printer.rb', line 245

def _format_single(out, exception, backtrace = nil)
  out.puts "#{exception.class}: #{exception.message}"
  backtrace ||= exception.backtrace.to_a
  backtrace.each { |trace| out.puts "\t#{trace}" }
end

#_unique_trace(backtrace1, backtrace2) ⇒ Object



251
252
253
254
255
256
257
258
259
# File 'lib/chef_apply/ui/error_printer.rb', line 251

def _unique_trace(backtrace1, backtrace2)
  i = 1
  while i <= backtrace1.size && i <= backtrace2.size
    break if backtrace1[-i] != backtrace2[-i]

    i += 1
  end
  backtrace1[0..-i]
end

#add_backtrace_header(out, args) ⇒ Object



168
169
170
171
172
173
# File 'lib/chef_apply/ui/error_printer.rb', line 168

def add_backtrace_header(out, args)
  out.write("\n#{"-" * 80}\n")
  out.print("#{Time.now}: Error encountered while running the following:\n")
  out.print("  #{args.join(" ")}\n")
  out.print("Backtrace:\n")
end

#add_formatted_backtrace(out) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/chef_apply/ui/error_printer.rb', line 227

def add_formatted_backtrace(out)
  _format_single(out, exception)
  current_backtrace = exception.backtrace
  cause = exception.cause
  until cause.nil?
    cause_trace = _unique_trace(cause.backtrace.to_a, current_backtrace)
    out.print "Caused by: "
    _format_single(out, cause, cause_trace)
    backtrace_length = cause.backtrace.length
    if backtrace_length > cause_trace.length
      out.print "\t... #{backtrace_length - cause_trace.length} more"
    end
    out.print "\n"
    current_backtrace = cause.backtrace
    cause = cause.cause
  end
end

#format_bodyObject



141
142
143
144
145
146
147
148
149
# File 'lib/chef_apply/ui/error_printer.rb', line 141

def format_body
  if exception.is_a? ChefApply::Error
    format_workstation_exception
  elsif exception.is_a? Train::Error
    format_train_exception
  else
    format_other_exception
  end
end

#format_decoratedObject



127
128
129
130
131
132
133
134
135
# File 'lib/chef_apply/ui/error_printer.rb', line 127

def format_decorated
  @content << "\n"
  @content << format_header
  @content << "\n\n"
  @content << format_body
  @content << "\n"
  @content << format_footer
  @content << "\n"
end

#format_errorObject



109
110
111
112
113
114
115
116
# File 'lib/chef_apply/ui/error_printer.rb', line 109

def format_error
  if translation.decorations
    format_decorated
  else
    format_undecorated
  end
  @content.string
end


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/chef_apply/ui/error_printer.rb', line 151

def format_footer
  if translation.log
    if translation.stack
      t.footer.both(ChefApply::Config.log.location,
        ChefApply::Config.stack_trace_path)
    else
      t.footer.log_only(ChefApply::Config.log.location)
    end
  else
    if translation.stack
      t.footer.stack_only
    else
      t.footer.neither
    end
  end
end

#format_headerObject



137
138
139
# File 'lib/chef_apply/ui/error_printer.rb', line 137

def format_header
  pastel.decorate(@id, :bold)
end

#format_other_exceptionObject



212
213
214
# File 'lib/chef_apply/ui/error_printer.rb', line 212

def format_other_exception
  t.send(DEFAULT_ERROR_NO).text(exception.message)
end

#format_train_exceptionObject

TODO this gets moved to trainerrormapper or simply removed since

many of these issues are now handled in the RemoteTarget::ConnectionFailure


203
204
205
206
207
208
209
210
# File 'lib/chef_apply/ui/error_printer.rb', line 203

def format_train_exception
  backend, host = formatted_host
  if host.nil?
    t.CHEFTRN002.text(exception.message)
  else
    t.CHEFTRN001.text(backend, host, exception.message)
  end
end

#format_undecoratedObject



118
119
120
121
122
123
124
125
# File 'lib/chef_apply/ui/error_printer.rb', line 118

def format_undecorated
  @content << "\n"
  @content << format_body
  if @command
    @content << "\n"
    @content << @command.usage
  end
end

#format_workstation_exceptionObject



196
197
198
199
# File 'lib/chef_apply/ui/error_printer.rb', line 196

def format_workstation_exception
  params = exception.params
  t.send(@id).text(*params)
end

#formatted_hostObject



216
217
218
219
220
221
222
223
# File 'lib/chef_apply/ui/error_printer.rb', line 216

def formatted_host
  return nil if target_host.nil?

  cfg = target_host.config
  port = cfg[:port].nil? ? "" : ":#{cfg[:port]}"
  user = cfg[:user].nil? ? "" : "#{cfg[:user]}@"
  "#{user}#{target_host.hostname}#{port}"
end

#save_backtrace(output) ⇒ Object



175
176
177
178
179
# File 'lib/chef_apply/ui/error_printer.rb', line 175

def save_backtrace(output)
  File.open(ChefApply::Config.stack_trace_path, "ab+") do |f|
    f.write(output.string)
  end
end

#tObject

TODO define ‘t’ as a method is a temporary workaround to ensure that text key lookups are testable.



32
33
34
# File 'lib/chef_apply/ui/error_printer.rb', line 32

def t
  ChefApply::Text.errors
end