1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
#
# A drop in replacement for Capistrano::Logger that integrates better with LEAP CLI.
#
require 'capistrano/logger'
#
# from Capistrano::Logger
# =========================
#
# IMPORTANT = 0
# INFO = 1
# DEBUG = 2
# TRACE = 3
# MAX_LEVEL = 3
# COLORS = {
# :none => "0",
# :black => "30",
# :red => "31",
# :green => "32",
# :yellow => "33",
# :blue => "34",
# :magenta => "35",
# :cyan => "36",
# :white => "37"
# }
# STYLES = {
# :bright => 1,
# :dim => 2,
# :underscore => 4,
# :blink => 5,
# :reverse => 7,
# :hidden => 8
# }
#
module LeapCli
class Logger < Capistrano::Logger
def initialize(options={})
@options = options
@level = options[:level] || 0
@message_buffer = nil
end
def log(level, message, line_prefix=nil, options={})
if message !~ /\n$/ && level <= 2 && line_prefix.is_a?(String)
# in some cases, when the message doesn't end with a return, we buffer it and
# wait until we encounter the return before we log the message out.
@message_buffer ||= ""
@message_buffer += message
return
elsif @message_buffer
message = @message_buffer + message
@message_buffer = nil
end
options[:level] ||= level
[:stdout, :log].each do |mode|
LeapCli::log_raw(mode) do
message_lines(mode, message, line_prefix, options)
end
end
end
private
def message_lines(mode, message, line_prefix, options)
formatted_message, formatted_prefix, message_options = apply_formatting(mode, message, line_prefix, options)
if message_options[:level] <= self.level && formatted_message && formatted_message.chars.any?
if formatted_prefix
formatted_message.lines.collect { |line|
"[#{formatted_prefix}] #{line.sub(/\s+$/, '')}"
}
else
formatted_message.lines.collect {|line| line.sub(/\s+$/, '')}
end
else
nil
end
end
##
## FORMATTING
##
#
# options for formatters:
#
# :match => regexp for matching a log line
# :color => what color the line should be
# :style => what style the line should be
# :priority => what order the formatters are applied in. higher numbers first.
# :match_level => only apply filter at the specified log level
# :level => make this line visible at this log level or higher
# :replace => replace the matched text
# :exit => force the exit code to be this (does not interrupt program, just
# ensures a specific exit code when the program eventually exits)
#
@formatters = [
# TRACE
{ :match => /command finished/, :color => :white, :style => :dim, :match_level => 3, :priority => -10 },
{ :match => /executing locally/, :color => :yellow, :match_level => 3, :priority => -20 },
# DEBUG
#{ :match => /executing .*/, :color => :green, :match_level => 2, :priority => -10, :timestamp => true },
#{ :match => /.*/, :color => :yellow, :match_level => 2, :priority => -30 },
{ :match => /^transaction:/, :level => 3 },
# INFO
{ :match => /.*out\] (fatal:|ERROR:).*/, :color => :red, :match_level => 1, :priority => -10 },
{ :match => /Permission denied/, :color => :red, :match_level => 1, :priority => -20 },
{ :match => /sh: .+: command not found/, :color => :magenta, :match_level => 1, :priority => -30 },
# IMPORTANT
{ :match => /^(E|e)rr ::/, :color => :red, :match_level => 0, :priority => -10, :exit => 1},
{ :match => /^ERROR:/, :color => :red, :priority => -10, :exit => 1},
{ :match => /.*/, :color => :blue, :match_level => 0, :priority => -20 },
# CLEANUP
{ :match => /\s+$/, :replace => '', :priority => 0},
# DEBIAN PACKAGES
{ :match => /^(Hit|Ign) /, :color => :green, :priority => -20},
{ :match => /^Err /, :color => :red, :priority => -20},
{ :match => /^W(ARNING)?: /, :color => :yellow, :priority => -20},
{ :match => /^E: /, :color => :red, :priority => -20},
{ :match => /already the newest version/, :color => :green, :priority => -20},
{ :match => /WARNING: The following packages cannot be authenticated!/, :color => :red, :level => 0, :priority => -10},
# PUPPET
{ :match => /^(W|w)arning: Not collecting exported resources without storeconfigs/, :level => 2, :color => :yellow, :priority => -10},
{ :match => /^(W|w)arning: Found multiple default providers for vcsrepo:/, :level => 2, :color => :yellow, :priority => -10},
{ :match => /^(W|w)arning: .*is deprecated.*$/, :level => 2, :color => :yellow, :priority => -10},
{ :match => /^(W|w)arning: Scope.*$/, :level => 2, :color => :yellow, :priority => -10},
{ :match => /^(N|n)otice:/, :level => 1, :color => :cyan, :priority => -20},
{ :match => /^(N|n)otice:.*executed successfully$/, :level => 2, :color => :cyan, :priority => -15},
{ :match => /^(W|w)arning:/, :level => 0, :color => :yellow, :priority => -20},
{ :match => /^Duplicate declaration:/, :level => 0, :color => :red, :priority => -20},
{ :match => /Finished catalog run/, :level => 0, :color => :green, :priority => -10},
{ :match => /^APPLY COMPLETE \(changes made\)/, :level => 0, :color => :green, :priority => -10},
{ :match => /^APPLY COMPLETE \(no changes\)/, :level => 0, :color => :green, :priority => -10},
# PUPPET FATAL ERRORS
{ :match => /^(E|e)rr(or|):/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^Wrapped exception:/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^Failed to parse template/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^Execution of.*returned/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^Parameter matches failed:/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^Syntax error/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^Cannot reassign variable/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^Could not find template/, :level => 0, :color => :red, :priority => -1, :exit => 1},
{ :match => /^APPLY COMPLETE.*fail/, :level => 0, :color => :red, :priority => -1, :exit => 1},
# TESTS
{ :match => /^PASS: /, :color => :green, :priority => -20},
{ :match => /^(FAIL|ERROR): /, :color => :red, :priority => -20},
{ :match => /^(SKIP|WARN): /, :color => :yellow, :priority => -20},
{ :match => /\d+ tests: \d+ passes, \d+ skips, 0 warnings, 0 failures, 0 errors/, :color => :blue, :priority => -20},
# LOG SUPPRESSION
{ :match => /^(W|w)arning: You cannot collect without storeconfigs being set/, :level => 2, :priority => 10},
{ :match => /^(W|w)arning: You cannot collect exported resources without storeconfigs being set/, :level => 2, :priority => 10}
]
def self.sorted_formatters
# Sort matchers in reverse order so we can break if we found a match.
@sorted_formatters ||= @formatters.sort_by { |i| -(i[:priority] || i[:prio] || 0) }
end
@prefix_formatters = [
{ :match => /(err|out) :: /, :replace => '', :priority => 0},
{ :match => /\s+$/, :replace => '', :priority => 0}
]
def self.prefix_formatters; @prefix_formatters; end
def apply_formatting(mode, message, line_prefix = nil, options={})
message = message.dup
options = options.dup
if !line_prefix.nil?
if !line_prefix.is_a?(String)
line_prefix = line_prefix.to_s.dup
else
line_prefix = line_prefix.dup
end
end
color = options[:color] || :none
style = options[:style]
if line_prefix
self.class.prefix_formatters.each do |formatter|
if line_prefix =~ formatter[:match] && formatter[:replace]
line_prefix.gsub!(formatter[:match], formatter[:replace])
end
end
end
self.class.sorted_formatters.each do |formatter|
if (formatter[:match_level] == level || formatter[:match_level].nil?)
if message =~ formatter[:match]
options[:level] = formatter[:level] if formatter[:level]
color = formatter[:color] if formatter[:color]
style = formatter[:style] || formatter[:attribute] # (support original cap colors)
message.gsub!(formatter[:match], formatter[:replace]) if formatter[:replace]
message.replace(formatter[:prepend] + message) unless formatter[:prepend].nil?
message.replace(message + formatter[:append]) unless formatter[:append].nil?
message.replace(Time.now.strftime('%Y-%m-%d %T') + ' ' + message) if formatter[:timestamp]
if formatter[:exit]
LeapCli::Util.exit_status(formatter[:exit])
end
# stop formatting, unless formatter was just for string replacement
break unless formatter[:replace]
end
end
end
if color == :hide
return nil
elsif mode == :log || (color == :none && style.nil?) || !LeapCli.logger.log_in_color
return [message, line_prefix, options]
else
term_color = COLORS[color]
term_style = STYLES[style]
if line_prefix.nil?
message.replace format(message, term_color, term_style)
else
line_prefix.replace format(line_prefix, term_color, term_style).strip # format() appends a \n
end
return [message, line_prefix, options]
end
end
end
end
|