watcher 1.1.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.
- data/History.txt +21 -0
- data/License.txt +27 -0
- data/Manifest.txt +25 -0
- data/PostInstall.txt +7 -0
- data/README.txt +105 -0
- data/Rakefile +4 -0
- data/TODO +23 -0
- data/config/hoe.rb +75 -0
- data/config/requirements.rb +15 -0
- data/lib/watcher/version.rb +9 -0
- data/lib/watcher.rb +596 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +82 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/website.rake +17 -0
- data/test/test_helper.rb +2 -0
- data/test/test_watcher.rb +11 -0
- data/test/watcher-example.rb +50 -0
- data/website/index.html +141 -0
- data/website/index.txt +83 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.html.erb +48 -0
- data.tar.gz.sig +0 -0
- metadata +122 -0
- metadata.gz.sig +0 -0
data/lib/watcher.rb
ADDED
@@ -0,0 +1,596 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- Ruby -*-
|
3
|
+
# Watcher
|
4
|
+
#
|
5
|
+
# Summary:: Watcher provides advanced integrated exception handling and
|
6
|
+
# logging functionality to your Ruby programs.
|
7
|
+
# Author:: Karrick McDermott (karrick@karrick.org)
|
8
|
+
# Date:: 2008-07-04
|
9
|
+
# Copyright:: Copyright (c) 2008 by Karrick McDermott. All rights reserved.
|
10
|
+
# License:: Simplified BSD License.
|
11
|
+
|
12
|
+
######################################################################
|
13
|
+
class Watcher
|
14
|
+
VERSION = '1.1.0'
|
15
|
+
# Tracks the number of times an Exception has invoked the :raise fail
|
16
|
+
# action.
|
17
|
+
# * Fail actions to log an error (:log), invoke a Proc (lambda {...}), and
|
18
|
+
# retry (:retry) are ignored because flow of the program is allowed to
|
19
|
+
# continue for each of those, whereas a :raise will abort processing of
|
20
|
+
# subsequent tasks until caught.
|
21
|
+
# * If a task raises an Exception, but the task is enclosed in another task
|
22
|
+
# that merely logs the Exception, then the @errors attribute is still
|
23
|
+
# incremented. This is useful for signalling when a warning condition
|
24
|
+
# occured, but the program is able to continue the processing of
|
25
|
+
# subsequent tasks.
|
26
|
+
attr_reader :errors
|
27
|
+
|
28
|
+
# Tracks the number of times an Exception has been raised, regardless of
|
29
|
+
# whether the Exception was re-thrown.
|
30
|
+
attr_reader :warnings
|
31
|
+
|
32
|
+
# Array of actions to perform when an Exception is thrown by code in the
|
33
|
+
# yield block.
|
34
|
+
# * The Watcher instance maintains a default @fail_actions Array for use
|
35
|
+
# when a given invocation does not specify a different set of actions to
|
36
|
+
# perform.
|
37
|
+
# * The default value is an Array of two actions, [:log, :raise], which
|
38
|
+
# notes the error in the logs, and re-throws the Exception.
|
39
|
+
attr_accessor :fail_actions
|
40
|
+
|
41
|
+
# A String to include inside the log entry when an Exception is thrown.
|
42
|
+
# * This may be required in some circumstances where a separate log
|
43
|
+
# monitoring program is scanning log files looking for a string such as,
|
44
|
+
# ERROR or FAILURE.
|
45
|
+
# * The default value is nil.
|
46
|
+
attr_accessor :fail_symbol
|
47
|
+
|
48
|
+
# The number of spaces to indent a child level task under its parent task.
|
49
|
+
# * A Boolean value of false or an Integer value of 0 turns off indentation.
|
50
|
+
# * Any other value will set how many characters will be used for indenting
|
51
|
+
# the logs.
|
52
|
+
# * This may only be set at the time the Watcher object is created.
|
53
|
+
# * The default value is 3.
|
54
|
+
attr_reader :indent
|
55
|
+
|
56
|
+
# A String of characters to use when joining multiple error lines into a
|
57
|
+
# single line for output to the log.
|
58
|
+
# * If nil, the Exception message is reported verbatim. If not nil, the
|
59
|
+
# value must respond to the <tt>to_s</tt> method, which is used to join
|
60
|
+
# the Exception message lines.
|
61
|
+
# * The default value is ' (LF) '.
|
62
|
+
attr_accessor :merge_newlines
|
63
|
+
|
64
|
+
# Determines whether Watcher will display the task completion log events.
|
65
|
+
# * The default value is false.
|
66
|
+
attr_accessor :show_completion
|
67
|
+
|
68
|
+
# Holds a Proc object assigned the task of formatting the system time to a
|
69
|
+
# String for display.
|
70
|
+
# * You can easily change how the times are displayed by passing a Proc
|
71
|
+
# object or a block to this attribute.
|
72
|
+
# * If you pass a block, be sure to wrap it with lambda.
|
73
|
+
#
|
74
|
+
# <tt>$watcher.time_format( lambda { Time.now.utc } )</tt>
|
75
|
+
#
|
76
|
+
# * The default value is a Proc object that returns a String representing
|
77
|
+
# local time in the standard system format.
|
78
|
+
attr_accessor :time_formatter
|
79
|
+
|
80
|
+
# Determines level of reporting that Watcher object sends to the log file.
|
81
|
+
# * Possible values are the Symbols :debug, :info, :warn, :error, and
|
82
|
+
# :fatal.
|
83
|
+
# * The default value is :info.
|
84
|
+
attr_accessor :verbosity
|
85
|
+
|
86
|
+
# Provide ability for Watcher to prioritize levels of verbosity.
|
87
|
+
# * It wasn't designed to be directly used by client code, but I suppose it
|
88
|
+
# could be modified by consumer code in this script if the situation
|
89
|
+
# warranted.
|
90
|
+
@@verbosity_levels = {:debug=>0, :info=>1, :warn=>2, :error=>3, :fatal=>4}
|
91
|
+
|
92
|
+
# Stores the Strings used by Watcher to use when writting events to the log
|
93
|
+
# files.
|
94
|
+
# * Provided as a means to customize the output, say for language
|
95
|
+
# localization purposes.
|
96
|
+
# * <b>Warning</b>, if you substitute your own Hash for @verbosity_strings
|
97
|
+
# that does not have keys for the five levels of verbosity used by
|
98
|
+
# Watcher, you may cause Watcher to start throwing exceptions with it
|
99
|
+
# attempts to coerce the NilClass into a String.
|
100
|
+
# * The default value of @verbosity_strings is a Hash that maps the
|
101
|
+
# verbosity levels to a reasonable String value.
|
102
|
+
attr_accessor :verbosity_strings
|
103
|
+
|
104
|
+
# We override the default accessibility for the :new method because we want
|
105
|
+
# to ensure there is only one Watcher object created.
|
106
|
+
private_class_method :new
|
107
|
+
@@watcher = nil
|
108
|
+
|
109
|
+
# #Watcher.create is the only way to instantiate a new Watcher object.
|
110
|
+
# * You cannot use the #new method directly as you can with regular classes.
|
111
|
+
# * Repeated calls to #Watcher.create simply return the same Watcher object.
|
112
|
+
# * When a Watcher object is created, the default class initialization
|
113
|
+
# values are usually perfectly suitable for the task at hand. This is
|
114
|
+
# more-so true when just starting on a new project, when the default is to
|
115
|
+
# send logging information to STDERR, which is usually what you want when
|
116
|
+
# developing a program using the interactive method, such as with irb.
|
117
|
+
# * It is easy to change any of the class attributes either at object
|
118
|
+
# instantiation, or while the program is being executed. During
|
119
|
+
# instantiation simply create a Hash object with a key Symbol for every
|
120
|
+
# attribute you wish to change, whose value is your desired value. For
|
121
|
+
# instance, to create a globabl variable, aptly named <tt>$watcher</tt>:
|
122
|
+
#
|
123
|
+
# <tt>
|
124
|
+
# $watcher = Watcher.create({:fail_actions=>:warn, :verbosity=>:info})
|
125
|
+
# </tt>
|
126
|
+
#
|
127
|
+
# * Each Watcher object instance stores an IO object for buffering data to
|
128
|
+
# the output log. This can either be a regular file, STDERR, STDOUT, or
|
129
|
+
# perhaps a FIFO or a pipe. If you do not want to use STDERR or a
|
130
|
+
# regular file, simply make the connection, and pass it to your Watcher
|
131
|
+
# instance.
|
132
|
+
# * The default value of @output is an IO instance of STDERR.
|
133
|
+
def Watcher.create(attributes={})
|
134
|
+
@@watcher ||= new(attributes)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Used to identify a low-level task during development.
|
138
|
+
# * This is most frequently invoked without a block, in which case it simply
|
139
|
+
# appends the task start and stop messages to the log if the verbosity
|
140
|
+
# level is at :debug.
|
141
|
+
# * Use this when in lieu of a group of 'puts' statements littered through
|
142
|
+
# your source code.
|
143
|
+
# * See detailed decription for #monitor for how this method works.
|
144
|
+
def debug(task, fail_actions=@fail_actions)
|
145
|
+
monitor(task,:debug,block_given?,fail_actions) { yield }
|
146
|
+
end
|
147
|
+
|
148
|
+
# Usually the method of choice for your general logging purpose
|
149
|
+
# needs.
|
150
|
+
# * I typcially use it at every level of the stack frame to track the
|
151
|
+
# progress of the program.
|
152
|
+
# * I recommend using this for every level of program logic that you desire
|
153
|
+
# to track.
|
154
|
+
# * See detailed decription for #monitor for how this method works.
|
155
|
+
def info(task, fail_actions=@fail_actions)
|
156
|
+
monitor(task,:info,block_given?,fail_actions) { yield }
|
157
|
+
end
|
158
|
+
|
159
|
+
# Each Watcher object instance can optionally send a copy of its
|
160
|
+
# log file to a given email account.
|
161
|
+
# * You might want to call this if there were any errors while completing
|
162
|
+
# your tasks.
|
163
|
+
def send_email(email,subject)
|
164
|
+
@output.flush # flush the output buffer
|
165
|
+
system "mail -s '#{subject}' #{email} < #{@output_path}"
|
166
|
+
|
167
|
+
# reopen the output buffer for append mode
|
168
|
+
@output = File.open(@output_path, 'a')
|
169
|
+
unless @output # Redirect to STDERR if open failed.
|
170
|
+
@output = STDERR
|
171
|
+
@debug_out << "could not open [#{output}]. " + e.message + "\n" \
|
172
|
+
if WATCHER_DEBUG
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
########################################
|
177
|
+
private
|
178
|
+
########################################
|
179
|
+
|
180
|
+
# The Watcher.initialize method is called by the Watcher.new method as
|
181
|
+
# normal, however because the Watcher.new method is a private method, the
|
182
|
+
# only way to create a new Watcher object is to use the Watcher.create
|
183
|
+
# method, which in turn invokes Watcher.new, which--finally--invokes
|
184
|
+
# Watcher.initialize.
|
185
|
+
def initialize(attributes={}) #:nodoc: #:notnew:
|
186
|
+
# Watcher debugging facility
|
187
|
+
if WATCHER_DEBUG
|
188
|
+
# Open our dump file, and let Ruby environment close it when we exit.
|
189
|
+
begin
|
190
|
+
# mode 'w' truncates file if not empty.
|
191
|
+
@debug_out = File.open(WATCHER_DEBUG_OUTPUT_FILENAME,'w')
|
192
|
+
rescue Exception => e
|
193
|
+
# if we couldn't open it, just send debugging data to STDERR,
|
194
|
+
# and insert a message indicating the failure.
|
195
|
+
@debug_out = STDERR
|
196
|
+
@debug_out << "could not open [#{WATCHER_DEBUG_OUTPUT_FILENAME}]. " \
|
197
|
+
+ e.message + "\n"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Watcher debugging facility
|
202
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
|
203
|
+
@debug_out << "=== Explicitly requested attributes: \n"
|
204
|
+
attributes.keys.each do |k|
|
205
|
+
@debug_out << "\t:#{k}=>#{attributes[k].inspect}\n"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Here we define the default values for a newly created Watcher object.
|
210
|
+
# * If passed nil on :output, must specifically delete Hash key.
|
211
|
+
# * Merge the input attributes Hash given to Watcher.create with a
|
212
|
+
# local inline Hash below.
|
213
|
+
# * Any keys provided by the user simply cause the associated key
|
214
|
+
# values provided by the consumer code to overwrite the values
|
215
|
+
# provided in the literal Hash below. Isn't Ruby fun?
|
216
|
+
attributes.delete(:output) if attributes[:output] == nil
|
217
|
+
attributes={:fail_actions=>[:log,:raise], :fail_symbol=>nil, :indent=>3,
|
218
|
+
:merge_newlines=>' (LF) ', :output=>STDERR, :show_completion=>false,
|
219
|
+
:time_formatter=>lambda { Time.now.to_s }, :verbosity=>:info,
|
220
|
+
:verbosity_strings=>{:debug=>' DEBUG:',:info=>' INFO :',
|
221
|
+
:warn=>' WARN :',:error=>' ERROR:',
|
222
|
+
:fatal=>' FATAL:'}
|
223
|
+
}.merge(attributes)
|
224
|
+
|
225
|
+
# Watcher debugging facility
|
226
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
|
227
|
+
@debug_out << "=== Merged attributes: \n" << "\n"
|
228
|
+
attributes.keys.each do |k|
|
229
|
+
@debug_out << "\t:#{k}=>#{attributes[k].inspect}\n"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Here the product of the merges Hashes is used to populate the
|
234
|
+
# instantiated Watcher object attributes.
|
235
|
+
# Nothing to see here, move on... Unless you want to improve
|
236
|
+
# Watcher...
|
237
|
+
@current_indent=0
|
238
|
+
@errors=0
|
239
|
+
@warnings=0
|
240
|
+
@fail_actions=attributes[:fail_actions]
|
241
|
+
@fail_symbol=attributes[:fail_symbol]
|
242
|
+
@indent=attributes[:indent]
|
243
|
+
@merge_newlines=attributes[:merge_newlines]
|
244
|
+
@output=attributes[:output]
|
245
|
+
@show_completion=attributes[:show_completion]
|
246
|
+
@time_formatter=attributes[:time_formatter]
|
247
|
+
@verbosity=attributes[:verbosity]
|
248
|
+
@verbosity_strings=attributes[:verbosity_strings]
|
249
|
+
|
250
|
+
# Watcher debugging facility
|
251
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
|
252
|
+
@debug_out << "=== Instance attributes: \n" << "\n"
|
253
|
+
@debug_out << " @current_indent\t\t=#{@current_indent}" << "\n"
|
254
|
+
@debug_out << " @fail_actions\t\t=#{@fail_actions.inspect}" << "\n"
|
255
|
+
@debug_out << " @fail_symbol\t\t=\"#{@fail_symbol}\"" << "\n"
|
256
|
+
@debug_out << " @indent\t\t\t=#{@indent}" << "\n"
|
257
|
+
@debug_out << " @merge_newlines\t\t=#{@merge_newlines}" << "\n"
|
258
|
+
@debug_out << " @output\t\t\t=#{@output}" << "\n"
|
259
|
+
@debug_out << " @show_completion\t\t=#{@show_completion}" << "\n"
|
260
|
+
@debug_out << " @time_formatter\t\t=#{@time_formatter}" << "\n"
|
261
|
+
@debug_out << " @verbosity\t\t\t=:#{@verbosity}" << "\n"
|
262
|
+
@debug_out << " @verbosity_strings\t\t=" \
|
263
|
+
+ "#{@verbosity_strings.inspect}\n"
|
264
|
+
end
|
265
|
+
|
266
|
+
# Open the log file if not STDERR nor STDOUR
|
267
|
+
if @output != STDERR and @output != STDOUT
|
268
|
+
@output_path = File.expand_path(@output)
|
269
|
+
# mode 'w' truncates file if not empty.
|
270
|
+
@output = File.open(@output_path, 'w')
|
271
|
+
unless @output # Redirect to STDERR if open failed.
|
272
|
+
@output = STDERR
|
273
|
+
@debug_out << "could not open [#{output}]. " + e.message + "\n" \
|
274
|
+
if WATCHER_DEBUG
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end # initialize
|
278
|
+
|
279
|
+
# Returns a String representing the indentation characters to use
|
280
|
+
# for the event.
|
281
|
+
# * A new event increments the indentation level.
|
282
|
+
# * A done event and an error event decrements the indentation level.
|
283
|
+
# * A nil event represents a note, with no potential child-tasks. (No
|
284
|
+
# indentation change is required.)
|
285
|
+
def get_log_indentation_string(event=nil)
|
286
|
+
if @indent.class != Fixnum # don't indent unless numberical value
|
287
|
+
case event
|
288
|
+
when nil then result = '='
|
289
|
+
when :new then result = '>'
|
290
|
+
when :done then result = '<'
|
291
|
+
when :error then result = '*'
|
292
|
+
else
|
293
|
+
result = '='
|
294
|
+
@debug_out << " =WD= Invalid event Symbol = #{event.inspect}\n" \
|
295
|
+
if WATCHER_DEBUG
|
296
|
+
end
|
297
|
+
else # we got a Fixnum, so use indented log output
|
298
|
+
result = ' ' # plan to place at least one space after the time mark
|
299
|
+
# If WATCHER_DEBUG, we want to use periods instead of spaces to indent
|
300
|
+
space = '.'
|
301
|
+
unless WATCHER_DEBUG
|
302
|
+
# create some run-time protection from bad input
|
303
|
+
# TODO: Validate data when attributes accessors invoked
|
304
|
+
@current_indent = 0 if @current_indent < 0
|
305
|
+
@indent = 3 if @indent < 3
|
306
|
+
space = ' ' # if not debugging Watcher, go ahead and use spaces
|
307
|
+
end
|
308
|
+
|
309
|
+
#--
|
310
|
+
# If @indent is >= 3, arrows are created at each indent level to show
|
311
|
+
# the parent-child relationship between tasks.
|
312
|
+
# * head refers to the head of a log event arrow, and
|
313
|
+
# * tail refers to the tail of a log event arrow.
|
314
|
+
#++
|
315
|
+
case event
|
316
|
+
when nil
|
317
|
+
# same as :new but do not increment the level at the end
|
318
|
+
@current_indent.times do
|
319
|
+
result += space * (@indent-1)
|
320
|
+
end
|
321
|
+
result += 'I' # tail
|
322
|
+
result += '=' * (@indent-2)
|
323
|
+
result += '>' # head
|
324
|
+
when :new
|
325
|
+
# increate indent after make this String
|
326
|
+
@current_indent.times do
|
327
|
+
result += space * (@indent-1)
|
328
|
+
end
|
329
|
+
result += 'N' # tail
|
330
|
+
result += '-' * (@indent-2)
|
331
|
+
result += '>' # head
|
332
|
+
@current_indent += 1
|
333
|
+
when :done
|
334
|
+
# decrease indent before make this String
|
335
|
+
@current_indent -= 1
|
336
|
+
@current_indent.times do
|
337
|
+
result += space * (@indent-1)
|
338
|
+
end
|
339
|
+
result += 'C' # head
|
340
|
+
result += '-' * (@indent-1)
|
341
|
+
when :error
|
342
|
+
# decrease indent before make this String
|
343
|
+
@current_indent -= 1
|
344
|
+
@current_indent.times do
|
345
|
+
result += space * (@indent-1)
|
346
|
+
end
|
347
|
+
result += 'E' # head
|
348
|
+
result += '-' * (@indent-2)
|
349
|
+
else
|
350
|
+
# invalid event code invoked from within Watcher.monitor
|
351
|
+
@debug_out << " =WD= Invalid event Symbol = #{event.inspect}\n" \
|
352
|
+
if WATCHER_DEBUG
|
353
|
+
end
|
354
|
+
end
|
355
|
+
result
|
356
|
+
end
|
357
|
+
|
358
|
+
# Get the time String, but protect against if plug-in Proc causes an
|
359
|
+
# Exception.
|
360
|
+
def get_protected_log_time_string
|
361
|
+
begin
|
362
|
+
@time_formatter.call
|
363
|
+
rescue
|
364
|
+
Time.now.utc.to_s + " == WARNING: Time Formatter raised Exception! == "
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# When either #debug or #info is invoked, it ends up passing the
|
369
|
+
# batton to the private #monitor method.
|
370
|
+
# * #monitor first sets up some local variables, including setting up a
|
371
|
+
# variable to store the effective fail_actions for reasons decribed below,
|
372
|
+
# then optionally makes a log entry concerning the task you desire to
|
373
|
+
# execute. This log entry only takes place if the verbosity level of the
|
374
|
+
# task is equal to or exceeds the Watcher object's stored verbosity
|
375
|
+
# minimum level, @verbosity.
|
376
|
+
# * After preparations are complete, Watcher checks whether you sent the
|
377
|
+
# #debug or #info method a block to execute. If you did not send a block,
|
378
|
+
# Watcher simply returns control to your code at the point immediately
|
379
|
+
# following where you invoked Watcher.
|
380
|
+
# * If you did pass a block to execute, Watcher sets up a
|
381
|
+
# <tt>begin...rescue...end</tt> Exception handling block and invokes your
|
382
|
+
# block inside that protective environment.
|
383
|
+
# * If code invoked by that block triggers an Exception, Watcher passes
|
384
|
+
# control to the #protect method, which orchestrates a recovery path based
|
385
|
+
# on the consumer provided fail_actions Array. If the #debug or #info
|
386
|
+
# methods were not given a specific set of actions to perform if an
|
387
|
+
# Exception occurs, then the Watcher object uses its object instance
|
388
|
+
# attribute @fail_actions to provide the list of actions to complete. For
|
389
|
+
# information how this works, see the documentation for the #protect
|
390
|
+
# method.
|
391
|
+
def monitor(task,run_level,block_was_given,fail_actions) #:doc:
|
392
|
+
# Watcher debugging facility
|
393
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_FAILACTIONS
|
394
|
+
@debug_out << "fail_actions = #{fail_actions.inspect}\n"
|
395
|
+
end
|
396
|
+
|
397
|
+
# assume no exception for this task
|
398
|
+
success_flag = true
|
399
|
+
|
400
|
+
# validate input upon initial invocation, not when handling Exception...
|
401
|
+
# abort task if input parameters are not clean
|
402
|
+
if not task.respond_to?(:to_s)
|
403
|
+
@output << get_protected_log_time_string
|
404
|
+
@output << "WATCHER: the 'task' must be printable!\n"
|
405
|
+
return # abort task
|
406
|
+
elsif not @@verbosity_levels.has_key?(run_level)
|
407
|
+
@output << get_protected_log_time_string + "WATCHER: The given " \
|
408
|
+
+ "run_level, #{runlevel.inspect}, is not a member of " \
|
409
|
+
+ "Watcher::@@verbosity_levels. Task = [#{task}]\n"
|
410
|
+
return # abort task
|
411
|
+
else
|
412
|
+
# always validate the requested fail actions by helper method
|
413
|
+
protect(task, fail_actions) # e=nil, therefore just validate
|
414
|
+
end
|
415
|
+
|
416
|
+
# Watcher debugging facility
|
417
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_LEVELS
|
418
|
+
@debug_out << get_protected_log_time_string \
|
419
|
+
+ " =WD= task level = :#{run_level}, " \
|
420
|
+
+ "verbosity level = :#{@verbosity}\n"
|
421
|
+
end
|
422
|
+
|
423
|
+
# log the event if the task run level >= verbosity
|
424
|
+
if @@verbosity_levels[run_level] >= @@verbosity_levels[@verbosity]
|
425
|
+
output = get_protected_log_time_string
|
426
|
+
# if no new block given, then task event = nil, (don't indent)
|
427
|
+
output << get_log_indentation_string(block_was_given ? :new : nil)
|
428
|
+
output << task << "\n"
|
429
|
+
@debug_out << output if WATCHER_DEBUG and WATCHER_DEBUG_OUTPUT
|
430
|
+
@output << output
|
431
|
+
end
|
432
|
+
|
433
|
+
# okay, preparations complete, if a block was given, do it!
|
434
|
+
if block_was_given # block_given? from #debug or #info
|
435
|
+
begin
|
436
|
+
# Create a Continuation object. A :retry fail action will come back
|
437
|
+
# to this point in code. A classic "begin, rescue, retry" does not
|
438
|
+
# work, because the retry would occur from an invoked method, protect.
|
439
|
+
callcc{|@continuation|}
|
440
|
+
yield
|
441
|
+
# Log the event if the task run level is greater or equal to the
|
442
|
+
# verbosity
|
443
|
+
# * The task complete log entry only takes place after your block
|
444
|
+
# returns.
|
445
|
+
# * If you did not send a task block to complete, Watcher will
|
446
|
+
# conditionally log the event based on the verbosity level, then
|
447
|
+
# exit back to your code just after you invoked Watcher.
|
448
|
+
# * PROGRAMMER NOTE: I moved the code from the ensure block to just
|
449
|
+
# after the yield code in the begin block.
|
450
|
+
# * The reason for this is that there are really two logical ways
|
451
|
+
# to complete a task: (1) successfully, and (2) unsuccessfully.
|
452
|
+
# 1. This code represents the path by successful code, while the
|
453
|
+
# rescue block represents the patch by unsuccessful code.
|
454
|
+
# 2. Because the ensure block is always taking place, a failed block
|
455
|
+
# of consumer code would log the error, then enter the ensure
|
456
|
+
# block and log the task complete. That gives the wrong
|
457
|
+
# impression.
|
458
|
+
if @@verbosity_levels[run_level] >= @@verbosity_levels[@verbosity]
|
459
|
+
# Only display if user wants logging of compeltion events
|
460
|
+
# * Although we don't show the line when @show_completion is false,
|
461
|
+
# we must still call #get_log_indentation_string(:done) to adjust
|
462
|
+
# the @current_indent attribute properly.
|
463
|
+
output << get_protected_log_time_string
|
464
|
+
output << get_log_indentation_string(:done) << task << "\n"
|
465
|
+
if @show_completion
|
466
|
+
@debug_out << output if WATCHER_DEBUG and WATCHER_DEBUG_OUTPUT
|
467
|
+
@output << output
|
468
|
+
end
|
469
|
+
end
|
470
|
+
rescue
|
471
|
+
# catch any Exceptions thrown by the executed block
|
472
|
+
success_flag = protect(task, fail_actions, $!)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# return whether no exceptions occured
|
477
|
+
success_flag
|
478
|
+
end
|
479
|
+
|
480
|
+
# Iterate through fail_actions parameter for a task, and for each action,
|
481
|
+
# either perform or simply ensure it's a valid action.
|
482
|
+
# * #protect is called by #monitor when your code invokes #debug or #info in
|
483
|
+
# order to validate the fail_actions parameter.
|
484
|
+
# * #protect is also called by #monitor when the associated code block
|
485
|
+
# triggers an Exception. It receives an Array of actions to perform,
|
486
|
+
# <tt>fail_actions</tt>, and executes each one in turn.
|
487
|
+
# * Perhaps the most complex--however reasonable--example would be if you
|
488
|
+
# wanted to log the event, then invoke some snippet of code to attempt to
|
489
|
+
# resolve the problem, then retry the block. This can be done by
|
490
|
+
# specifying the following fail actions. <b>(Be careful, however, as this
|
491
|
+
# can easily lead to an infinite loop if the recovery Proc does not
|
492
|
+
# resolve the cause of the original Exception!)</b>
|
493
|
+
# * This is meant to provide you a bit of flexibility on how to handle
|
494
|
+
# conditions, however a complex Array of fail_actions may defeat your goal
|
495
|
+
# of code simplification.
|
496
|
+
def protect(task, fail_actions, e=nil) #:doc:
|
497
|
+
# * If e is nil, then return true if valid group, false or raise
|
498
|
+
# exception if invalid fail action found.
|
499
|
+
# * If e is an Exception, perform required actions.
|
500
|
+
|
501
|
+
# only increase warning if also performing actions
|
502
|
+
if e != nil
|
503
|
+
if e.kind_of?(Exception)
|
504
|
+
@warnings += 1
|
505
|
+
else
|
506
|
+
err_msg = "invalid exception instance (Watcher.monitor error)"
|
507
|
+
raise ArgumentError.new(err_msg)
|
508
|
+
end
|
509
|
+
@debug_out << "e = #{e.inspect}\n" if WATCHER_DEBUG
|
510
|
+
end
|
511
|
+
|
512
|
+
if fail_actions.class == Symbol or fail_actions.class == Proc
|
513
|
+
# NOTE: we pass the fail_actions (plural) parameter
|
514
|
+
protect_action(task, fail_actions, e)
|
515
|
+
elsif fail_actions.class == Array
|
516
|
+
fail_actions.each do |fail_action|
|
517
|
+
# NOTE: we pass the fail_action (singular) loop iteration variable
|
518
|
+
protect_action(task, fail_action, e)
|
519
|
+
end
|
520
|
+
else
|
521
|
+
err_msg = "unsupported fail action for task = [#{task}] " \
|
522
|
+
+ "(must be Symbol or Array of Symbols and Procs)"
|
523
|
+
raise ArgumentError.new(err_msg)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
# Either perform action, or simply validate it's valid
|
528
|
+
def protect_action(task, fail_action, e)
|
529
|
+
# * If e is nil, then validate the action against the possible
|
530
|
+
# actions. Raise ArgumentError Exception if invalid action found.
|
531
|
+
# * If e is a kind of Exception, perform required action.
|
532
|
+
|
533
|
+
# only time-stamp if also performing actions
|
534
|
+
if e != nil
|
535
|
+
# create the time stamp and indentation portion of the log entry
|
536
|
+
log_base = get_protected_log_time_string \
|
537
|
+
+ get_log_indentation_string(:error)
|
538
|
+
@debug_out << "e = #{e.inspect}\n" if WATCHER_DEBUG
|
539
|
+
end
|
540
|
+
|
541
|
+
if fail_action.class == Proc
|
542
|
+
if e != nil
|
543
|
+
# return result from user-specified code
|
544
|
+
return fail_action.call(e) # send the Exception instance
|
545
|
+
end
|
546
|
+
elsif fail_action.class == Symbol
|
547
|
+
case fail_action
|
548
|
+
when :log
|
549
|
+
if e != nil
|
550
|
+
output = log_base + '*' + task + '*' + ' ==> '
|
551
|
+
output << "#{@fail_symbol} ==> " if @fail_symbol
|
552
|
+
if @merge_newlines != false
|
553
|
+
output << e.message.split(/\n/).join(@merge_newlines)
|
554
|
+
else # no merge, but multiple lines
|
555
|
+
output << e.message
|
556
|
+
end
|
557
|
+
@output << output << "\n"
|
558
|
+
end
|
559
|
+
when :raise
|
560
|
+
if e != nil
|
561
|
+
@errors += 1
|
562
|
+
raise
|
563
|
+
end
|
564
|
+
when :retry # FIXME: this is not well tested
|
565
|
+
@continuation.call if e != nil
|
566
|
+
else
|
567
|
+
err_msg = "unsupported fail action Symbol for task = [#{task}] " \
|
568
|
+
+ "(must be one of [:log, :raise, :retry])"
|
569
|
+
raise ArgumentError.new(err_msg)
|
570
|
+
end
|
571
|
+
else # only Symbols and Procs can be designated fail actions
|
572
|
+
raise ArgumentError.new("invalid fail action (Watcher.protect error)")
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# Set to true when Watcher should execute itself in debugging mode.
|
577
|
+
WATCHER_DEBUG=false
|
578
|
+
|
579
|
+
# Name of file that Watcher creates for its debugging output.
|
580
|
+
WATCHER_DEBUG_OUTPUT_FILENAME='watcher-debug.log'
|
581
|
+
|
582
|
+
# Set to true when debugging Watcher instance attribute settings.
|
583
|
+
WATCHER_DEBUG_ATTRIBUTES=false
|
584
|
+
|
585
|
+
# Set to true when debugging Watcher handling of fail actions.
|
586
|
+
WATCHER_DEBUG_FAILACTIONS=false
|
587
|
+
|
588
|
+
# Set to true when debugging Watcher verbosity levels.
|
589
|
+
WATCHER_DEBUG_LEVELS=false
|
590
|
+
|
591
|
+
# Set to true when debugging Watcher.monitor.
|
592
|
+
WATCHER_DEBUG_MONITOR=false
|
593
|
+
|
594
|
+
# Set to true when debugging Watcher's log strings.
|
595
|
+
WATCHER_DEBUG_OUTPUT=false
|
596
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/watcher.rb'}"
|
9
|
+
puts "Loading watcher gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|