-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathformatter.rb
295 lines (263 loc) · 8.44 KB
/
formatter.rb
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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
require 'active_support/tagged_logging'
require 'colorize'
module Shog
# A rails logger formatter that spices up the log message adding color to
# and context to log messages.
#
# Shog automatically overrides the default formatter in your rails app. Use
# {Shog.configure} to configure the default logger.
class Formatter < ::ActiveSupport::Logger::SimpleFormatter
include ActiveSupport::TaggedLogging::Formatter
def initialize
reset_config!
end
# Called by the logger to prepare a message for output.
# @return [String]
def call( severity, time, progname, msg )
return if msg.blank? || _silence?( msg )
msg = [
_tagged( time, :timestamp ),
_tagged( progname, :progname ),
formatted_severity_tag( severity ),
formatted_message( severity, msg )
].compact.join(" ")
super severity, time, progname, msg
end
# Formats the message according to the configured {#match} blocks.
#
# @param [String] msg to format.
# @return [String] the formatted message.
def formatted_message( severity, msg )
msg = String === msg ? msg : msg.inspect
if args = _matched( msg )
args.first.call msg, args.last
elsif proc = configuration[:severities][severity]
proc.call msg
else
msg
end
end
# Formats the severity indicator prefixed before each line when writing to
# the log.
#
# @param [String] the severity of the message (ex DEBUG, WARN, etc.)
# @return [String] formatted version of the severity
def formatted_severity_tag( severity )
length = configuration[:severity_tags][:_length] ||= begin
configuration[:severity_tags].reduce(0){ |l,(k,_)| [k.length,l].max }
end
return if length == 0
padded_severity = severity.ljust length
formatted = if proc = configuration[:severity_tags][severity]
proc.call padded_severity
else
padded_severity
end
_tagged formatted, :severity_tags
end
# Formats a time value expressed in ms, adding color to highlight times
# outside the expected range.
#
# If `time` is more than `expected` it's highlighted yellow. If it's more
# than double it's highlighted red.
#
# @param [String] time in ms.
# @param [Float] expected maximum amount of time it should have taken.
# @return [String] the formatted time.
def format_time( time, expected = 30 )
timef = time.uncolorize.to_f
case
when timef > expected * 2 then time.to_s.uncolorize.red
when timef > expected then time.to_s.uncolorize.yellow
else time
end
end
# ==========================================================================
# @!group Configuration
# Set up log message formatting for this formatter.
#
# @yield and executes the block where self is this formatter.
# @return [Formatter] self.
#
# @example
# Formatter.new.configure do
# with :defaults
# timestamp
# severity(:error){ |msg| msg.red }
# severity(:fatal){ |msg| "\b#{msg}".red }
# end
def configure( &block )
instance_eval( &block )
self
end
# Format the severity indicator tagged before each line. To format the
# actual message itself use {#severity}.
#
# @overload severity_tag( level, proc )
# @param [String,Symbol] level to format.
# @param [#call(level)] proc that receives the log level and returns the
# reformatted level.
#
# @overload severity_tag( level )
# @param [String,Symbol] level to format.
# @yieldparam level [String] the log level to reformat.
# @yieldreturn [String] the reformatted level.
#
# @return [Formatter] self.
#
# @example
# configure do
# severity_tag(:warn){|level| level.yellow }
# severity_tag(:error){|level| level.red }
# end
def severity_tag( level, proc = nil, &block )
proc ||= block
configuration[:severity_tags][ level.to_s.upcase ] = proc
self
end
# Provide default formatting for messages of the given severity when
# a {#match} is not found.
#
# @overload severity( level, proc )
# @param [String,Symbol] level to format.
# @param [#call(msg)] proc that receives the message and returns the
# reformatted message.
# @overload severity( level )
# @param [String,Symbol] level to format.
# @yieldparam msg [String] the message to reformat.
# @yieldreturn [String] the reformatted message.
#
# @return [Formatter] self.
#
# @example
# configure do
# severity(:fatal){ |msg| msg.white_on_red }
# end
def severity( level, proc = nil, &block )
proc ||= block
configuration[:severities][ level.to_s.upcase ] = proc
self
end
# Resets any previously configured formatting settings.
# @return [Formatter] self.
def reset_config!
@configuration = {
severity_tags: {},
severities: {},
matchers: {},
silencers: []
}
self
end
# Re-format any log messages that match the given `pattern`.
#
# @overload match( pattern, proc)
# @param [Regexp] pattern to match against the log message.
# @param [#call(message,last_match)] proc a callable object that receives
# the message and the last match and re-formats the message.
#
# @overload match( pattern )
# @param [Regexp] pattern to match against the log message.
# @yieldparam message [String] the matched log message.
# @yieldparam last_match [MatchData] the regex matches.
# @yieldreturn [String] the re-formatted message.
#
# @example
# configure do
# match /GET (?<address>.*)/ do |message,last_match|
# "GETTING -> #{last_match['address'].green}"
# end
# end
# @return [Formatter] self.
def match( pattern, proc = nil, &block )
proc ||= block
configuration[:matchers][pattern] = proc
self
end
# When a log message matches the given `pattern` don't log it.
#
# @param [Regexp] pattern to match.
#
# @return [Formatter] self.
#
# @example
# configure do
# silence /assets\/bootstrap/
# end
def silence( pattern )
configuration[:silencers] << pattern
self
end
# Use configuration defined in the given module.
#
# @param [Symobl,#configure] mod the name of the shog module to use or an
# object that responds to `#configure`.
#
# @return [Formatter] self.
#
# When `mod` is a symobl, it loads one of the modules from
# {Shog::Formatters} and uses any configuration options sepcified in that
# module.
#
# Otherwise `mod` must respond to `#configure` taking a single argument -
# this formatter.
#
# @example Built-in Formatters
# configure do
# with :defaults
# with :requests
# end
#
# @example Custom Shared Formatters
# module MyFormatters
# def self.configure( formatter )
# formatter.configure do
# timestamp
# end
# end
# end
#
# configure do
# with MyFormatters
# end
def with( mod )
unless mod.is_a? Module
mod = "Shog::Formatters::#{mod.to_s.camelize}".constantize
end
mod.configure self
self
end
# Include timestamp in logged messages.
# @param [Boolean] enable or disable timestamping of log messages.
# @return [Formatter] self.
def timestamp( enable = true )
configuration[:timestamp] = enable
self
end
# Include the progname in logged messages.
# @param [Boolean] enable or disable tagging with the prog name of log messages.
# @return [Formatter] self.
def progname( enable = true )
configuration[:progname] = enable
self
end
# @!endgroup
private
attr_accessor :configuration
def _matched( msg )
msg = msg.uncolorize
if matched = configuration[:matchers].find do |pattern,_|
pattern === msg
end
[matched.last, Regexp.last_match]
end
end
def _tagged( val, config_key )
return unless configuration[config_key]
"[#{val}]"
end
def _silence?( msg )
configuration[:silencers].any?{|p| p === msg }
end
end
end