watcher 1.1.0 → 1.2.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.tar.gz.sig +0 -0
- data/History.txt +57 -0
- data/Manifest.txt +6 -2
- data/README.txt +19 -17
- data/TODO.txt +33 -0
- data/config/hoe.rb +7 -7
- data/lib/event.rb +177 -0
- data/lib/watcher.rb +304 -375
- data/lib/watcher/version.rb +1 -1
- data/script/txt2html +1 -1
- data/test/tc_event.rb +73 -0
- data/test/test_watcher.rb +98 -11
- data/test/ts_package.rb +11 -0
- data/website/index.html +89 -88
- data/website/index.txt +55 -3
- data/website/template.html.erb +1 -1
- metadata +10 -5
- metadata.gz.sig +0 -0
- data/TODO +0 -23
- data/test/watcher-example.rb +0 -50
data.tar.gz.sig
CHANGED
Binary file
|
data/History.txt
CHANGED
@@ -1,3 +1,60 @@
|
|
1
|
+
== 1.2.0 2008-07-30
|
2
|
+
|
3
|
+
* 6 major enhancements:
|
4
|
+
* Major re-write of entire Watcher code, separating logic into two class
|
5
|
+
files: watcher.rb and event.rb.
|
6
|
+
* Watcher verbosity levels consolidated to three levels: debug, verbose, and
|
7
|
+
always, in order from lowest priority to highest priority. Debug events
|
8
|
+
will rarely be logged, while always events will always be logged.
|
9
|
+
* The Watcher instantiation parameter, :fail_actions Array, is unsupported
|
10
|
+
and replaced by a Hash named :default_fail. Like :fail_actions, this
|
11
|
+
represents the set of actions to occur if no other fail actions are
|
12
|
+
specified when invoking one of Watcher's log event methods. Unlike the
|
13
|
+
:fail_actions Array, this is a Hash, whose possible key/value pairs are
|
14
|
+
described below.
|
15
|
+
* Fail actions logic has been completely rewritten.
|
16
|
+
* Invocation of Watcher.debug, Watcher.verbose, and Watcher.always
|
17
|
+
requires a fail_action parameter. This Hash must contain a :failure
|
18
|
+
key, equal to either :error or :warn.
|
19
|
+
* If :warn, then an exception will merely cause a warning event to
|
20
|
+
be recorded.
|
21
|
+
* If :error, an exception will cause an error event to be recorded, then
|
22
|
+
re-raise the original exception.
|
23
|
+
* Two other keys are permitted in the fail_action parameter. The presence
|
24
|
+
of one necessitates the presence of the other.
|
25
|
+
* :tries -- the total number of tries Watcher will attempt to execute
|
26
|
+
the code in the block.
|
27
|
+
* :proc -- the Proc instance that Watcher will invoke before each
|
28
|
+
retry attempt.
|
29
|
+
* If neither :tries nor :proc is given, Watcher takes action as if
|
30
|
+
:tries was set to 1 and :proc was set to lambda { }. However, Watcher
|
31
|
+
will throw an ArgumentError exception if :tries is specified, and not
|
32
|
+
a Fixnum with a value of at least 2.
|
33
|
+
* NB: The Proc will be invokes no more than (:tries - 1) times. This
|
34
|
+
is very different than Watcher 1.1.0, where the key was :retry, and
|
35
|
+
whose value determined the number of retries that would occur. In
|
36
|
+
the previous version, the number of times the Proc would be invoked
|
37
|
+
was equal to the :retry value. This is no longer the case.
|
38
|
+
* Indentation logic re-written.
|
39
|
+
* Log entries suppressed due to Watcher's verbosity setting are queued up.
|
40
|
+
If an exception is thrown, Watcher will replay those previously
|
41
|
+
suppressed log entries to allow a more detailed analysis of the cause of
|
42
|
+
the exception.
|
43
|
+
* Format of log output is nested in a hierarchical fashion, allowing
|
44
|
+
user to more easily trace the progression of tasks when trouble-shooting
|
45
|
+
program output via its logs.
|
46
|
+
* Unit tests written, although perhaps not yet entirely comprehensive.
|
47
|
+
|
48
|
+
* 3 minor enhancements:
|
49
|
+
* Allows either append, or overwrite mode for logs. Watcher can be
|
50
|
+
created with the :write parameter set to either :append or :overwrite.
|
51
|
+
The default is :overwrite.
|
52
|
+
* Watcher will ensure output stream is an actual file before it attempts to
|
53
|
+
send it via email. If the output stream is not a file, then it logs an
|
54
|
+
error message to the output stream.
|
55
|
+
* Eliminated use of continuations in preference to using the standard retry
|
56
|
+
statement.
|
57
|
+
|
1
58
|
== 1.1.0 2008-07-22
|
2
59
|
|
3
60
|
* 1 major enhancement:
|
data/Manifest.txt
CHANGED
@@ -4,9 +4,10 @@ Manifest.txt
|
|
4
4
|
PostInstall.txt
|
5
5
|
README.txt
|
6
6
|
Rakefile
|
7
|
-
TODO
|
7
|
+
TODO.txt
|
8
8
|
config/hoe.rb
|
9
9
|
config/requirements.rb
|
10
|
+
lib/event.rb
|
10
11
|
lib/watcher.rb
|
11
12
|
lib/watcher/version.rb
|
12
13
|
script/console
|
@@ -17,7 +18,10 @@ setup.rb
|
|
17
18
|
tasks/deployment.rake
|
18
19
|
tasks/environment.rake
|
19
20
|
tasks/website.rake
|
20
|
-
test/
|
21
|
+
test/tc_event.rb
|
22
|
+
test/test_helper.rb
|
23
|
+
test/test_watcher.rb
|
24
|
+
test/ts_package.rb
|
21
25
|
website/index.html
|
22
26
|
website/index.txt
|
23
27
|
website/javascripts/rounded_corners_lite.inc.js
|
data/README.txt
CHANGED
@@ -33,42 +33,44 @@ for the various actions taken.
|
|
33
33
|
|
34
34
|
* Exception handling without the boiler-plate code.
|
35
35
|
* Multiple tiers of verbosity.
|
36
|
-
* Flexible logging capabilities, including to files, STDERR, email.
|
36
|
+
* Flexible logging capabilities, including to files, STDERR, and email.
|
37
37
|
* Nested, and hierarchical log output for descriptive stack-frame level
|
38
38
|
understanding where an exception was thrown.
|
39
39
|
* Ability to change the time format of your logs by supplying an arbitrary
|
40
40
|
Proc to return the time in the format of your choice.
|
41
|
-
* Ability to customize the layout of the logs
|
42
|
-
view, or changing Strings used to indicate events.
|
41
|
+
* Ability to customize the layout of the logs.
|
43
42
|
* Email log files automatically, based on number of errors or warnings.
|
44
43
|
* Ability to invoke arbitrary sequence of events if exception is thrown,
|
45
|
-
|
44
|
+
by means of invocation of Proc object.
|
46
45
|
|
47
46
|
== SYNOPSIS:
|
48
47
|
|
49
48
|
# create Watcher instance with slightly modified defaults
|
50
|
-
$watcher=Watcher.create(:output=>log_file
|
49
|
+
$watcher = Watcher.create(:output => log_file)
|
51
50
|
|
52
|
-
$watcher.
|
51
|
+
$watcher.debug("starting #{File.basename($0)}") do
|
53
52
|
# Look for CONFIG_PATH. If not found, log warning then re-write it
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
params = {:tries => 2, :failure => :error,
|
54
|
+
:proc => lambda { |e| write_config(config_path) }}
|
55
|
+
msg = "ensuring existance of config file [#{config_path}]"
|
56
|
+
$watcher.verbose(msg, params) do
|
57
|
+
raise "file not found" unless File.file?(File.expand_path(config_path))
|
59
58
|
end
|
60
59
|
|
61
60
|
# invoke utility, which uses $watcher as well
|
62
|
-
fsc=FileSystemChecker.new(:config_path=>config_path)
|
63
|
-
fsc.check_file_systems(:all=>all_flag, :remount=>remount_flag)
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
fsc = FileSystemChecker.new(:config_path => config_path)
|
62
|
+
fsc.check_file_systems(:all => all_flag, :remount => remount_flag)
|
63
|
+
end
|
64
|
+
|
65
|
+
if email_flag and ($watcher.errors > 0 or $watcher.warnings > 0)
|
66
|
+
$watcher.send_email(email, 'FAIL: Required filesystems are not mounted')
|
67
67
|
end
|
68
68
|
|
69
69
|
== REQUIREMENTS:
|
70
70
|
|
71
|
-
*
|
71
|
+
* hoe
|
72
|
+
|
73
|
+
* newgem
|
72
74
|
|
73
75
|
== INSTALL:
|
74
76
|
|
data/TODO.txt
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
1. CRITICAL
|
2
|
+
|
3
|
+
a. None.
|
4
|
+
|
5
|
+
2. MINOR
|
6
|
+
|
7
|
+
a. Update source comments in watcher.rb and event.rb
|
8
|
+
|
9
|
+
b. Ensure 78 character max in source files.
|
10
|
+
|
11
|
+
c. Write some non-trivial example code. I have developed and ported
|
12
|
+
several non-trivial utilities from Logger to Watcher, and am extremely
|
13
|
+
happy with the results. Now let me get something ready to package
|
14
|
+
with this gem.
|
15
|
+
|
16
|
+
d. Trap signals to clean up properly.
|
17
|
+
|
18
|
+
e. Log rotation.
|
19
|
+
|
20
|
+
3. LOW
|
21
|
+
|
22
|
+
a. Figure out how to get ri documentation working.
|
23
|
+
|
24
|
+
b. Provide a Watcher.destroy method which closes the Watcher instance and
|
25
|
+
changes the @@watcher class attribute back to nil.
|
26
|
+
|
27
|
+
c. Validate data when attributes accessors invoked.
|
28
|
+
|
29
|
+
d. Use a Ruby MTA, as opposed to the UNIX mail program.
|
30
|
+
|
31
|
+
e. As populate Event instance, remove k,v from Hash as assign, then
|
32
|
+
ensure fail_actions is empty to validate no extra invalid options
|
33
|
+
sent.
|
data/config/hoe.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'watcher/version'
|
2
2
|
|
3
3
|
AUTHOR = 'Karrick McDermott' # can also be an array of Authors
|
4
|
-
EMAIL = "karrick@karrick.
|
4
|
+
EMAIL = "karrick@karrick.org"
|
5
5
|
DESCRIPTION = "Watcher provides advanced integrated exception handling and logging functionality to your Ruby programs."
|
6
6
|
GEM_NAME = 'watcher' # what ppl will type to install your gem
|
7
7
|
RUBYFORGE_PROJECT = 'watcher' # The unix name for your project
|
@@ -13,7 +13,7 @@ EXTRA_DEPENDENCIES = [
|
|
13
13
|
|
14
14
|
@config_file = "~/.rubyforge/user-config.yml"
|
15
15
|
@config = nil
|
16
|
-
RUBYFORGE_USERNAME = "unknown"
|
16
|
+
RUBYFORGE_USERNAME = "unknown"
|
17
17
|
def rubyforge_username
|
18
18
|
unless @config
|
19
19
|
begin
|
@@ -55,21 +55,21 @@ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
|
55
55
|
p.description = DESCRIPTION
|
56
56
|
p.summary = DESCRIPTION
|
57
57
|
p.url = HOMEPATH
|
58
|
-
p.remote_rdoc_dir = '' # Release to root (Watcher is my only project)
|
59
|
-
p.rsync_args << ' --exclude=statsvn/'
|
60
58
|
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
61
59
|
p.test_globs = ["test/**/test_*.rb"]
|
62
60
|
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
|
61
|
+
# p.remote_rdoc_dir = '' # Release to root (Watcher is my only project)
|
62
|
+
# p.rsync_args << ' --exclude=statsvn/'
|
63
63
|
|
64
64
|
# == Optional
|
65
65
|
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
66
66
|
#p.extra_deps = EXTRA_DEPENDENCIES
|
67
67
|
|
68
|
-
|
69
|
-
end
|
68
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
69
|
+
end
|
70
70
|
|
71
71
|
CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
|
72
72
|
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
73
73
|
$hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
74
74
|
$hoe.rsync_args = '-av --delete --ignore-errors'
|
75
|
-
$hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
|
75
|
+
$hoe.spec.post_install_message = File.open(File.dirname(__FILE__) + "/../PostInstall.txt").read rescue ""
|
data/lib/event.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# -*- Ruby -*-
|
3
|
+
# Event
|
4
|
+
#
|
5
|
+
# Summary:: Event instance handles exception recovery, proper hierarchical
|
6
|
+
# format String for the log based on its level in the call-stack.
|
7
|
+
# Author:: Karrick McDermott (karrick@karrick.org)
|
8
|
+
# Date:: 2008-07-22
|
9
|
+
# Copyright:: Copyright (c) 2008 by Karrick McDermott. All rights reserved.
|
10
|
+
# License:: Simplified BSD License.
|
11
|
+
|
12
|
+
######################################################################
|
13
|
+
|
14
|
+
# Add this directory to front of require path.
|
15
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
16
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
17
|
+
|
18
|
+
######################################################################
|
19
|
+
# An Event instance tracks the title of the task, and the required indentation
|
20
|
+
# to display the task in the log. This is required to store an Array of
|
21
|
+
# events that took place--but below the verbosity threshhold--to replay them
|
22
|
+
# into the log at the appropriate indention level should an exception be
|
23
|
+
# thrown.
|
24
|
+
class Event
|
25
|
+
##### Publically read-write attributes
|
26
|
+
|
27
|
+
##### Publically read-only attributes
|
28
|
+
attr_reader :failure # what to do if all tries fail (:warn | :error)
|
29
|
+
attr_reader :hier # shows nested levels
|
30
|
+
attr_reader :proc # Proc to call before a retry
|
31
|
+
attr_reader :tries # number of tries remaining
|
32
|
+
attr_reader :title # event title
|
33
|
+
|
34
|
+
# Get an Event which is the child of this event
|
35
|
+
def child
|
36
|
+
self.dup.child!
|
37
|
+
end
|
38
|
+
|
39
|
+
# Make this event into its child
|
40
|
+
def child!
|
41
|
+
if @hier.count('.') % 2 == 0 # if even number of periods
|
42
|
+
@hier << '.a'
|
43
|
+
else
|
44
|
+
@hier << '.0'
|
45
|
+
end
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get copy of Event hier to the next task at the same level of the call-stack
|
50
|
+
def sibling
|
51
|
+
self.dup.sibling!
|
52
|
+
end
|
53
|
+
|
54
|
+
# Take this Event hier to the next task at the same level of the call-stack
|
55
|
+
def sibling!
|
56
|
+
# Now change the last hier to its successor
|
57
|
+
@hier.sub!(/[^.]+$/) { $&.succ }
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
########## Return String ready for log
|
62
|
+
def to_s
|
63
|
+
@time + ': ' + @hier + ' ### ' + @title
|
64
|
+
end
|
65
|
+
|
66
|
+
########################################
|
67
|
+
private
|
68
|
+
########################################
|
69
|
+
|
70
|
+
# Create a new Event instance with the specified task title, and an optional
|
71
|
+
# prefix for parent event id (used for log replay)
|
72
|
+
def initialize(time, hier, title, fail_actions, relationship)
|
73
|
+
@hier = hier
|
74
|
+
@time = time
|
75
|
+
@title = title
|
76
|
+
|
77
|
+
# validate title
|
78
|
+
unless title.respond_to? :to_s
|
79
|
+
# Defensive programming against Watcher's caller:
|
80
|
+
msg = "Event.initialize: The first argument (title) must respond to" \
|
81
|
+
+ " the :to_s method. Your argument class is #{title.class}." \
|
82
|
+
+ " Aborting task."
|
83
|
+
raise ArgumentError.new(msg)
|
84
|
+
end
|
85
|
+
|
86
|
+
# validate time
|
87
|
+
unless time.respond_to? :to_s
|
88
|
+
# Defensive programming against Watcher's caller:
|
89
|
+
msg = "Event.initialize: The second argument (time) must respond to" \
|
90
|
+
+ " the :to_s method. Your argument class is #{time.class}." \
|
91
|
+
+ " Aborting [#{title}]."
|
92
|
+
raise ArgumentError.new(msg)
|
93
|
+
end
|
94
|
+
|
95
|
+
# validate fail actions
|
96
|
+
if fail_actions.kind_of? Hash
|
97
|
+
# validate fail_actions (throws ArgumentError if bad; let caller catch)
|
98
|
+
@proc = fail_actions[:proc]
|
99
|
+
@tries = fail_actions[:tries]
|
100
|
+
@failure = fail_actions[:failure]
|
101
|
+
validate_fail_actions
|
102
|
+
else
|
103
|
+
# Defensive programming against Watcher's caller:
|
104
|
+
msg = "Event.initialize: The fourth argument (fail_actions) must be" \
|
105
|
+
+ " a Hash, not a #{fail_actions.class}. Aborting [#{title}]."
|
106
|
+
raise ArgumentError.new(msg)
|
107
|
+
end
|
108
|
+
|
109
|
+
# validate hier argument
|
110
|
+
if hier == nil
|
111
|
+
# If no hier event, then this Event has no parent or siblings.
|
112
|
+
@hier = '0'
|
113
|
+
elsif not hier.kind_of? String
|
114
|
+
# Defensive programming against Watcher.monitor
|
115
|
+
msg = "Event.initialize: sixth argument (hier) must be either nil," \
|
116
|
+
+ " or of non-zero length String, not a #{hier.class}. Aborting [#{title}]."
|
117
|
+
raise ArgumentError.new(msg)
|
118
|
+
elsif hier == ''
|
119
|
+
# Defensive programming against Watcher.monitor
|
120
|
+
msg = "Event.initialize: sixth argument (hier) must be either nil," \
|
121
|
+
+ " or of non-zero length String. Aborting [#{title}]."
|
122
|
+
raise ArgumentError.new(msg)
|
123
|
+
else # this event is related to hier
|
124
|
+
case relationship
|
125
|
+
when :child
|
126
|
+
@hier = hier
|
127
|
+
child!
|
128
|
+
when :sibling
|
129
|
+
@hier = hier
|
130
|
+
sibling!
|
131
|
+
else
|
132
|
+
msg = "Event.initialize: fifth argument (relationship) must be one" \
|
133
|
+
+ " of either :child or :sibling"
|
134
|
+
raise ArgumentError.new(msg)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Deprecated, but worked fine at the time it was taken out.
|
140
|
+
# # Take this Event hier to back up one hier from the call-stack
|
141
|
+
# def dec
|
142
|
+
# # knock off the last segment
|
143
|
+
# @hier.sub!(/\.?[^.]+$/, '')
|
144
|
+
# # call sibling to increment parent hier
|
145
|
+
# sibling!
|
146
|
+
# end
|
147
|
+
|
148
|
+
# Validate fail_actions parameter (throws ArgumentError if bad)
|
149
|
+
# * Must include either :warn or :error, but not both in Hash.
|
150
|
+
# * If :tries is specified, it must be a Fixnum, and then :proc must also
|
151
|
+
# be specified, and a valid Proc instance.
|
152
|
+
# * If :tries is not specified, then :proc may not be specified.
|
153
|
+
def validate_fail_actions
|
154
|
+
# Either :warn or :error, not both, and not neither.
|
155
|
+
if @failure != :warn and @failure != :error
|
156
|
+
msg = "Event.validate_fail_actions: Must specify either :warn or :error"
|
157
|
+
raise ArgumentError.new(msg)
|
158
|
+
end
|
159
|
+
|
160
|
+
# If :tries is set, then it must be a Fixnum
|
161
|
+
if @tries != nil
|
162
|
+
if @tries.class != Fixnum or @tries < 2
|
163
|
+
raise ArgumentError.new(':tries must be at least 2 in order to specify a Proc')
|
164
|
+
else # must have a valid Proc
|
165
|
+
if @proc.class != Proc
|
166
|
+
raise ArgumentError.new('must specify a Proc instance when :tries is set')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
else # tries is nil
|
170
|
+
# Ensure :proc not set
|
171
|
+
if @proc != nil
|
172
|
+
raise ArgumentError.new(':tries must be at least 2 in order to specify a Proc')
|
173
|
+
end
|
174
|
+
@tries = 1 # just one try since :proc was nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/watcher.rb
CHANGED
@@ -9,50 +9,46 @@
|
|
9
9
|
# Copyright:: Copyright (c) 2008 by Karrick McDermott. All rights reserved.
|
10
10
|
# License:: Simplified BSD License.
|
11
11
|
|
12
|
+
######################################################################
|
13
|
+
|
14
|
+
# add this directory to front of require path
|
15
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
16
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
17
|
+
|
18
|
+
# Co-located in same directory as watcher.
|
19
|
+
require 'event'
|
20
|
+
|
12
21
|
######################################################################
|
13
22
|
class Watcher
|
14
|
-
|
23
|
+
# Array of actions to perform when an Exception is thrown by code in the
|
24
|
+
# yield block.
|
25
|
+
# * The Watcher instance maintains a default @default_fail Hash for use when
|
26
|
+
# a given invocation does not specify a different set of actions to
|
27
|
+
# perform.
|
28
|
+
# * The default value is a Hash with one key, the value of which causes
|
29
|
+
# Watcher to re-raise the exception if one should occur.
|
30
|
+
attr_accessor :default_fail
|
31
|
+
|
32
|
+
# A String to include inside the log entry when an Exception is thrown.
|
33
|
+
# * This may be required in some circumstances where a separate log
|
34
|
+
# monitoring program is scanning log files looking for a string such as,
|
35
|
+
# ERROR or FAILURE.
|
36
|
+
# * The default value is nil.
|
37
|
+
attr_accessor :error_symbol
|
38
|
+
|
15
39
|
# Tracks the number of times an Exception has invoked the :raise fail
|
16
40
|
# action.
|
17
41
|
# * Fail actions to log an error (:log), invoke a Proc (lambda {...}), and
|
18
42
|
# retry (:retry) are ignored because flow of the program is allowed to
|
19
43
|
# continue for each of those, whereas a :raise will abort processing of
|
20
44
|
# subsequent tasks until caught.
|
21
|
-
# * If a
|
45
|
+
# * If a title raises an Exception, but the title is enclosed in another title
|
22
46
|
# that merely logs the Exception, then the @errors attribute is still
|
23
47
|
# incremented. This is useful for signalling when a warning condition
|
24
48
|
# occured, but the program is able to continue the processing of
|
25
49
|
# subsequent tasks.
|
26
50
|
attr_reader :errors
|
27
51
|
|
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
52
|
# A String of characters to use when joining multiple error lines into a
|
57
53
|
# single line for output to the log.
|
58
54
|
# * If nil, the Exception message is reported verbatim. If not nil, the
|
@@ -61,11 +57,7 @@ class Watcher
|
|
61
57
|
# * The default value is ' (LF) '.
|
62
58
|
attr_accessor :merge_newlines
|
63
59
|
|
64
|
-
#
|
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
|
60
|
+
# Holds a Proc object assigned the title of formatting the system time to a
|
69
61
|
# String for display.
|
70
62
|
# * You can easily change how the times are displayed by passing a Proc
|
71
63
|
# object or a block to this attribute.
|
@@ -78,28 +70,27 @@ class Watcher
|
|
78
70
|
attr_accessor :time_formatter
|
79
71
|
|
80
72
|
# Determines level of reporting that Watcher object sends to the log file.
|
81
|
-
# * Possible values are the Symbols :debug, :
|
73
|
+
# * Possible values are the Symbols :debug, :verbose, :warn, :error, and
|
82
74
|
# :fatal.
|
83
|
-
# * The default value is :
|
75
|
+
# * The default value is :warn.
|
84
76
|
attr_accessor :verbosity
|
85
77
|
|
86
78
|
# Provide ability for Watcher to prioritize levels of verbosity.
|
87
79
|
# * It wasn't designed to be directly used by client code, but I suppose it
|
88
80
|
# could be modified by consumer code in this script if the situation
|
89
81
|
# warranted.
|
90
|
-
@@verbosity_levels = {:debug=>0, :
|
91
|
-
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
# *
|
97
|
-
|
98
|
-
|
99
|
-
#
|
100
|
-
#
|
101
|
-
|
102
|
-
attr_accessor :verbosity_strings
|
82
|
+
@@verbosity_levels = {:debug => 0, :verbose => 1, :always => 2}
|
83
|
+
|
84
|
+
# A String to include inside the log entry when an Exception is thrown.
|
85
|
+
# * This may be required in some circumstances where a separate log
|
86
|
+
# monitoring program is scanning log files looking for a string such as,
|
87
|
+
# ERROR or FAILURE.
|
88
|
+
# * The default value is nil.
|
89
|
+
attr_accessor :warn_symbol
|
90
|
+
|
91
|
+
# Tracks the number of times an Exception has been raised, regardless of
|
92
|
+
# whether the Exception was re-thrown.
|
93
|
+
attr_reader :warnings
|
103
94
|
|
104
95
|
# We override the default accessibility for the :new method because we want
|
105
96
|
# to ensure there is only one Watcher object created.
|
@@ -110,10 +101,11 @@ class Watcher
|
|
110
101
|
# * You cannot use the #new method directly as you can with regular classes.
|
111
102
|
# * Repeated calls to #Watcher.create simply return the same Watcher object.
|
112
103
|
# * When a Watcher object is created, the default class initialization
|
113
|
-
# values are usually perfectly suitable for the
|
104
|
+
# values are usually perfectly suitable for the title at hand. This is
|
114
105
|
# more-so true when just starting on a new project, when the default is to
|
115
|
-
# send logging information to
|
116
|
-
# developing a program using the interactive method, such as with irb
|
106
|
+
# send logging information to STDOUT, which is usually what you want when
|
107
|
+
# developing a program using the interactive method, such as with irb,
|
108
|
+
# or when running a process from the command shell.
|
117
109
|
# * It is easy to change any of the class attributes either at object
|
118
110
|
# instantiation, or while the program is being executed. During
|
119
111
|
# instantiation simply create a Hash object with a key Symbol for every
|
@@ -121,28 +113,28 @@ class Watcher
|
|
121
113
|
# instance, to create a globabl variable, aptly named <tt>$watcher</tt>:
|
122
114
|
#
|
123
115
|
# <tt>
|
124
|
-
# $watcher = Watcher.create(
|
116
|
+
# $watcher = Watcher.create(:default_fail=>:warn, :verbosity=>:warn)
|
125
117
|
# </tt>
|
126
118
|
#
|
127
119
|
# * Each Watcher object instance stores an IO object for buffering data to
|
128
120
|
# 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
|
121
|
+
# perhaps a FIFO or a pipe. If you do not want to use STDOUT or a
|
130
122
|
# regular file, simply make the connection, and pass it to your Watcher
|
131
123
|
# instance.
|
132
|
-
# * The default value of @output is an IO instance of
|
124
|
+
# * The default value of @output is an IO instance of STDOUT.
|
133
125
|
def Watcher.create(attributes={})
|
134
126
|
@@watcher ||= new(attributes)
|
135
127
|
end
|
136
128
|
|
137
|
-
# Used to identify a low-level
|
129
|
+
# Used to identify a low-level title during development.
|
138
130
|
# * This is most frequently invoked without a block, in which case it simply
|
139
|
-
# appends the
|
131
|
+
# appends the title start and stop messages to the log if the verbosity
|
140
132
|
# level is at :debug.
|
141
133
|
# * Use this when in lieu of a group of 'puts' statements littered through
|
142
134
|
# your source code.
|
143
135
|
# * See detailed decription for #monitor for how this method works.
|
144
|
-
def debug(
|
145
|
-
monitor(
|
136
|
+
def debug(title, event_fail=@default_fail)
|
137
|
+
monitor(title, :debug, block_given?, event_fail) { yield }
|
146
138
|
end
|
147
139
|
|
148
140
|
# Usually the method of choice for your general logging purpose
|
@@ -152,24 +144,30 @@ class Watcher
|
|
152
144
|
# * I recommend using this for every level of program logic that you desire
|
153
145
|
# to track.
|
154
146
|
# * See detailed decription for #monitor for how this method works.
|
155
|
-
def
|
156
|
-
monitor(
|
147
|
+
def verbose(title, event_fail=@default_fail)
|
148
|
+
monitor(title, :verbose, block_given?, event_fail) { yield }
|
149
|
+
end
|
150
|
+
|
151
|
+
# Use this sparingly. (User specifies this verbosity level so your program
|
152
|
+
# will not show anything, unless there is an error.)
|
153
|
+
def always(title, event_fail=@default_fail)
|
154
|
+
monitor(title, :always, block_given?, event_fail) { yield }
|
157
155
|
end
|
158
156
|
|
159
157
|
# Each Watcher object instance can optionally send a copy of its
|
160
158
|
# log file to a given email account.
|
161
159
|
# * You might want to call this if there were any errors while completing
|
162
160
|
# your tasks.
|
161
|
+
# * NOTE: It is the responsibility of the calling routine to send the logs
|
162
|
+
# to a file if you intend to send the email.
|
163
|
+
# $watcher = Watcher.create(:output=>log_file, ...)
|
163
164
|
def send_email(email,subject)
|
164
165
|
@output.flush # flush the output buffer
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
@output = STDERR
|
171
|
-
@debug_out << "could not open [#{output}]. " + e.message + "\n" \
|
172
|
-
if WATCHER_DEBUG
|
166
|
+
if File.file?(@output_path)
|
167
|
+
system "mail -s '#{subject}' #{email} < #{@output_path}"
|
168
|
+
else
|
169
|
+
msg = "Cannot send log file as email because no log file was created"
|
170
|
+
raise ArgumentError.new(msg)
|
173
171
|
end
|
174
172
|
end
|
175
173
|
|
@@ -187,8 +185,8 @@ class Watcher
|
|
187
185
|
if WATCHER_DEBUG
|
188
186
|
# Open our dump file, and let Ruby environment close it when we exit.
|
189
187
|
begin
|
190
|
-
# mode 'w' truncates file if not empty.
|
191
|
-
@debug_out = File.open(WATCHER_DEBUG_OUTPUT_FILENAME,'
|
188
|
+
# mode 'a' appends to file, and 'w' truncates file if not empty.
|
189
|
+
@debug_out = File.open(WATCHER_DEBUG_OUTPUT_FILENAME,'a')
|
192
190
|
rescue Exception => e
|
193
191
|
# if we couldn't open it, just send debugging data to STDERR,
|
194
192
|
# and insert a message indicating the failure.
|
@@ -196,14 +194,18 @@ class Watcher
|
|
196
194
|
@debug_out << "could not open [#{WATCHER_DEBUG_OUTPUT_FILENAME}]. " \
|
197
195
|
+ e.message + "\n"
|
198
196
|
end
|
197
|
+
@debug_out << "****************************************\n"
|
198
|
+
@debug_out << Time.now.to_s + "* starting " + File.basename($0) + "\n"
|
199
|
+
@debug_out.flush unless @debug_out == STDERR or @debug_out == STDOUT
|
199
200
|
end
|
200
201
|
|
201
202
|
# Watcher debugging facility
|
202
203
|
if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
|
203
204
|
@debug_out << "=== Explicitly requested attributes: \n"
|
204
205
|
attributes.keys.each do |k|
|
205
|
-
@debug_out << "\t:#{k}
|
206
|
+
@debug_out << "\t:#{k} => #{attributes[k].inspect}\n"
|
206
207
|
end
|
208
|
+
@debug_out.flush unless @debug_out == STDERR or @debug_out == STDOUT
|
207
209
|
end
|
208
210
|
|
209
211
|
# Here we define the default values for a newly created Watcher object.
|
@@ -213,168 +215,122 @@ class Watcher
|
|
213
215
|
# * Any keys provided by the user simply cause the associated key
|
214
216
|
# values provided by the consumer code to overwrite the values
|
215
217
|
# provided in the literal Hash below. Isn't Ruby fun?
|
216
|
-
attributes.delete(:output) if attributes[:output] == nil
|
217
|
-
attributes
|
218
|
-
|
219
|
-
:
|
220
|
-
:
|
221
|
-
|
222
|
-
|
218
|
+
# attributes.delete(:output) if attributes[:output] == nil
|
219
|
+
attributes.delete(:output_path) if attributes[:output_path] == nil
|
220
|
+
attributes={
|
221
|
+
:default_fail => {:failure => :error},
|
222
|
+
:error_symbol => nil, :warn_symbol => nil,
|
223
|
+
:merge_newlines => ' (LF) ', :output_path => STDOUT,
|
224
|
+
:time_formatter => lambda { Time.now.to_s },
|
225
|
+
:verbosity => :always,
|
226
|
+
:write => :overwrite
|
223
227
|
}.merge(attributes)
|
224
228
|
|
225
229
|
# Watcher debugging facility
|
226
230
|
if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
|
227
231
|
@debug_out << "=== Merged attributes: \n" << "\n"
|
228
232
|
attributes.keys.each do |k|
|
229
|
-
@debug_out << "\t:#{k}
|
233
|
+
@debug_out << "\t:#{k} => #{attributes[k].inspect}\n"
|
230
234
|
end
|
235
|
+
@debug_out.flush unless @debug_out == STDERR or @debug_out == STDOUT
|
231
236
|
end
|
232
237
|
|
238
|
+
# initialize public attributes
|
239
|
+
@errors = 0
|
240
|
+
@warnings = 0
|
241
|
+
|
242
|
+
# initialize private attributes
|
243
|
+
@last_hier = nil # no last events at start
|
244
|
+
@event_relationship = :child # first event will be a child of...nil
|
245
|
+
|
246
|
+
# Array of Event instances for working with nested levels.
|
247
|
+
@events = Array.new
|
248
|
+
|
233
249
|
# Here the product of the merges Hashes is used to populate the
|
234
250
|
# instantiated Watcher object attributes.
|
235
251
|
# Nothing to see here, move on... Unless you want to improve
|
236
252
|
# Watcher...
|
237
|
-
@
|
238
|
-
@
|
239
|
-
@
|
240
|
-
@
|
241
|
-
@
|
242
|
-
@
|
243
|
-
@
|
244
|
-
@
|
245
|
-
|
246
|
-
|
247
|
-
@
|
248
|
-
|
253
|
+
@default_fail = attributes[:default_fail]
|
254
|
+
@error_symbol = attributes[:error_symbol]
|
255
|
+
@warn_symbol = attributes[:warn_symbol]
|
256
|
+
@merge_newlines = attributes[:merge_newlines]
|
257
|
+
@output_path = attributes[:output_path]
|
258
|
+
@time_formatter = attributes[:time_formatter]
|
259
|
+
@verbosity = attributes[:verbosity]
|
260
|
+
@write_mode = attributes[:write]
|
261
|
+
|
262
|
+
# after the attribute merge, perform sanity checks
|
263
|
+
if @time_formatter.class != Proc
|
264
|
+
raise ArgumentError(":time_formatter must be a Proc instance")
|
265
|
+
end
|
266
|
+
|
267
|
+
# make sure verbosity makes sense
|
268
|
+
unless @@verbosity_levels.include? @verbosity
|
269
|
+
msg = ":verbosity must be one of ["
|
270
|
+
@@verbosity_levels.keys.each { |k| msg << k.inspect << ' ' }
|
271
|
+
msg << ']'
|
272
|
+
raise ArgumentError.new(msg)
|
273
|
+
end
|
274
|
+
|
275
|
+
# verify file output mode
|
276
|
+
@write_mode = case @write_mode
|
277
|
+
when :append
|
278
|
+
'a'
|
279
|
+
when :overwrite
|
280
|
+
'w'
|
281
|
+
else
|
282
|
+
msg = "Watcher.initialize: :write must be one of either :overwrite" \
|
283
|
+
+ " or :append, not #{@write_mode.inspect}."
|
284
|
+
raise ArgumentError.new(msg)
|
285
|
+
end
|
249
286
|
|
250
287
|
# Watcher debugging facility
|
251
288
|
if WATCHER_DEBUG and WATCHER_DEBUG_ATTRIBUTES
|
252
289
|
@debug_out << "=== Instance attributes: \n" << "\n"
|
253
|
-
@debug_out << " @
|
254
|
-
@debug_out << " @
|
255
|
-
@debug_out << " @
|
256
|
-
@debug_out << " @indent\t\t\t=#{@indent}" << "\n"
|
290
|
+
@debug_out << " @default_fail\t\t=#{@default_fail.inspect}" << "\n"
|
291
|
+
@debug_out << " @error_symbol\t\t=\"#{@error_symbol}\"" << "\n"
|
292
|
+
@debug_out << " @warn_symbol\t\t=\"#{@warn_symbol}\"" << "\n"
|
257
293
|
@debug_out << " @merge_newlines\t\t=#{@merge_newlines}" << "\n"
|
258
|
-
@debug_out << " @
|
259
|
-
@debug_out << " @show_completion\t\t=#{@show_completion}" << "\n"
|
294
|
+
@debug_out << " @output_path\t\t\t=#{@output_path}" << "\n"
|
260
295
|
@debug_out << " @time_formatter\t\t=#{@time_formatter}" << "\n"
|
261
296
|
@debug_out << " @verbosity\t\t\t=:#{@verbosity}" << "\n"
|
262
|
-
@debug_out
|
263
|
-
+ "#{@verbosity_strings.inspect}\n"
|
297
|
+
@debug_out.flush unless @debug_out == STDERR or @debug_out == STDOUT
|
264
298
|
end
|
265
299
|
|
266
300
|
# Open the log file if not STDERR nor STDOUR
|
267
|
-
if @
|
268
|
-
@output_path = File.expand_path(@
|
269
|
-
|
270
|
-
@output
|
271
|
-
|
272
|
-
@
|
273
|
-
|
274
|
-
if WATCHER_DEBUG
|
301
|
+
if @output_path != STDERR and @output_path != STDOUT
|
302
|
+
@output_path = File.expand_path(@output_path)
|
303
|
+
@output = File.open(@output_path, @write_mode)
|
304
|
+
unless @output # Redirect to STDOUT if open failed.
|
305
|
+
@output = STDOUT
|
306
|
+
@debug_out << "could not open [#{output_path}]. " + e.message \
|
307
|
+
+ "\n" if WATCHER_DEBUG
|
275
308
|
end
|
309
|
+
else
|
310
|
+
@output = @output_path
|
276
311
|
end
|
277
312
|
end # initialize
|
278
313
|
|
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
314
|
# Get the time String, but protect against if plug-in Proc causes an
|
359
315
|
# Exception.
|
360
316
|
def get_protected_log_time_string
|
361
|
-
begin
|
317
|
+
@time = begin
|
362
318
|
@time_formatter.call
|
363
319
|
rescue
|
364
|
-
Time.now.
|
320
|
+
Time.now.to_s + " (WARNING! :time_formatter Proc raised #{$!.class} exception) "
|
365
321
|
end
|
366
322
|
end
|
367
323
|
|
368
|
-
# When either #debug or #
|
324
|
+
# When either #debug or #verbose is invoked, it ends up passing the
|
369
325
|
# batton to the private #monitor method.
|
370
326
|
# * #monitor first sets up some local variables, including setting up a
|
371
|
-
# variable to store the effective
|
372
|
-
# then optionally makes a log entry concerning the
|
327
|
+
# variable to store the effective default_fail for reasons decribed below,
|
328
|
+
# then optionally makes a log entry concerning the title you desire to
|
373
329
|
# execute. This log entry only takes place if the verbosity level of the
|
374
|
-
#
|
330
|
+
# title is equal to or exceeds the Watcher object's stored verbosity
|
375
331
|
# minimum level, @verbosity.
|
376
332
|
# * After preparations are complete, Watcher checks whether you sent the
|
377
|
-
# #debug or #
|
333
|
+
# #debug or #verbose method a block to execute. If you did not send a block,
|
378
334
|
# Watcher simply returns control to your code at the point immediately
|
379
335
|
# following where you invoked Watcher.
|
380
336
|
# * If you did pass a block to execute, Watcher sets up a
|
@@ -382,215 +338,188 @@ class Watcher
|
|
382
338
|
# block inside that protective environment.
|
383
339
|
# * If code invoked by that block triggers an Exception, Watcher passes
|
384
340
|
# control to the #protect method, which orchestrates a recovery path based
|
385
|
-
# on the consumer provided
|
341
|
+
# on the consumer provided default_fail Array. If the #debug or #verbose
|
386
342
|
# methods were not given a specific set of actions to perform if an
|
387
343
|
# Exception occurs, then the Watcher object uses its object instance
|
388
|
-
# attribute @
|
344
|
+
# attribute @event_fail to provide the list of actions to complete. For
|
389
345
|
# information how this works, see the documentation for the #protect
|
390
346
|
# method.
|
391
|
-
def monitor(
|
392
|
-
#
|
393
|
-
if
|
394
|
-
|
347
|
+
def monitor(title, run_level, block_was_given, event_fail) #:doc:
|
348
|
+
# Why have @events, an Array of events?
|
349
|
+
# * When new title is started, if its title level meets or exceeds the
|
350
|
+
# Watcher verbosity level, the @events Array is cleared and the event
|
351
|
+
# is sent to the log. The @events Array must be cleared because
|
352
|
+
# preceeding indentation will not otherwise line up.
|
353
|
+
# * When new title is started, if its title level is lower than the Watcher
|
354
|
+
# verbosity level, its event Hash is appended to the @events Array for
|
355
|
+
# later log replay if exception is thrown.
|
356
|
+
# * When title is successfully completed, its matching event Hash is
|
357
|
+
# located (from the right) of the @events Hash, and it and all following
|
358
|
+
# events are removed from the @events Array.
|
359
|
+
# * When title is unsuccessfully completed, a event replay is performed and
|
360
|
+
# all events in the @events Hash are sent to the logs regardless of
|
361
|
+
# their event level. This way an understandable stack-frame replay is
|
362
|
+
# possible to determine the actual cause of the exception.
|
363
|
+
|
364
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_FAIL_ACTIONS
|
365
|
+
@output.puts "WATCHER MONITOR -- FAIL_ACTIONS for [#{title}]"
|
366
|
+
event_fail.each { |k,v| @output << " #{k.inspect} == #{v.inspect}\n" }
|
395
367
|
end
|
396
368
|
|
397
|
-
#
|
398
|
-
|
369
|
+
# Validate input; abort if input parameters are not clean.
|
370
|
+
# (@time, @last_hier, @title, @fail_actions, @relationship)
|
371
|
+
event = Event.new(get_protected_log_time_string, @last_hier, title, \
|
372
|
+
event_fail, @event_relationship)
|
399
373
|
|
400
|
-
#
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
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
|
374
|
+
# Validate input; abort if input parameters are not clean.
|
375
|
+
unless @@verbosity_levels.has_key? run_level
|
376
|
+
# Defensive programming
|
377
|
+
msg = "Watcher.monitor: The second argument (run_level) must be one" \
|
378
|
+
+ ' of ' + @@verbosity_levels.inspect + ". Your argument was" \
|
379
|
+
+ run_level.inspect + ". Aborting [#{title}]"
|
380
|
+
raise ArgumentError.new(msg)
|
414
381
|
end
|
415
382
|
|
416
|
-
#
|
417
|
-
if
|
418
|
-
|
419
|
-
|
420
|
-
|
383
|
+
# Either append this event to @events Array, or send it to the log
|
384
|
+
if @@verbosity_levels[run_level] < @@verbosity_levels[@verbosity]
|
385
|
+
# This event is lower than our minimum verbosity, so store it in our
|
386
|
+
# Array for replay if needed later.
|
387
|
+
@events << event
|
388
|
+
@debug_out << "* HIDDEN: #{event}\n" if WATCHER_DEBUG and WATCHER_DEBUG_VERBOSITY
|
389
|
+
else
|
390
|
+
# forget previous replays since this gets printed
|
391
|
+
@debug_out << "* VISIBLE:#{event.to_s} (*) clearing previous #{@events.size} events from array\n" if WATCHER_DEBUG and WATCHER_DEBUG_VERBOSITY
|
392
|
+
@events = Array.new
|
393
|
+
|
394
|
+
# NOTE: event.to_s retuns time-stamp then its event hier
|
395
|
+
@output << event.to_s + "\n"
|
396
|
+
@output.flush # Do this always, not just 'if WATCHER_DEBUG'
|
421
397
|
end
|
398
|
+
|
399
|
+
# If event has a block, protect and execute the block
|
400
|
+
if block_was_given # block_given? from #debug or #verbose
|
401
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_OUTPUT and false
|
402
|
+
@output << " +monitor_block(#{event.title})"
|
403
|
+
end
|
404
|
+
monitor_block(event) { yield }
|
405
|
+
end # block given
|
422
406
|
|
423
|
-
#
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
@debug_out
|
430
|
-
@output << output
|
407
|
+
# Once this Event is complete, make the next event a sibling of this event
|
408
|
+
@event_relationship = :sibling
|
409
|
+
@last_hier = event.hier.dup
|
410
|
+
|
411
|
+
if WATCHER_DEBUG and WATCHER_DEBUG_VERBOSITY
|
412
|
+
@debug_out << "(L) @last_hier = " + @last_hier + "\n"
|
413
|
+
@debug_out.flush
|
431
414
|
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def monitor_block (event)
|
418
|
+
# During execution of the parameterized block, the following events must
|
419
|
+
# be children events of this event. After the block completes, following
|
420
|
+
# events are sibling events of this event.
|
421
|
+
@event_relationship = :child
|
422
|
+
@last_hier = event.hier.dup
|
423
|
+
|
424
|
+
# Event knows its own max tries value
|
425
|
+
tries_remaining = event.tries
|
432
426
|
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
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, $!)
|
427
|
+
begin
|
428
|
+
tries_remaining -= 1
|
429
|
+
yield
|
430
|
+
rescue # from exception thrown during original yield
|
431
|
+
# Start by sending all queued (hidden) events to the log. (They were
|
432
|
+
# only queued up so we could log them in event of a failure.)
|
433
|
+
# NOTE: @events could be empty...that's okay.
|
434
|
+
@events.each do |e|
|
435
|
+
@output << e.to_s
|
436
|
+
@output << " (flushed)" if WATCHER_DEBUG and WATCHER_DEBUG_OUTPUT
|
437
|
+
@output << "\n"
|
473
438
|
end
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
439
|
+
# After we output all hidden events, we erase the @events Array,
|
440
|
+
# so we don't output them on the next retry failure.
|
441
|
+
@events = Array.new
|
442
|
+
|
443
|
+
@output << get_protected_log_time_string + ': ' \
|
444
|
+
+ event.hier + ' ***'
|
479
445
|
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
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
|
446
|
+
# Place error or warning flag in the log
|
447
|
+
if tries_remaining == 0 and event.failure == :error
|
448
|
+
if @error_symbol != nil and @error_symbol != ''
|
449
|
+
@output << ' ' + @error_symbol
|
450
|
+
end
|
505
451
|
else
|
506
|
-
|
507
|
-
|
452
|
+
if @warn_symbol != nil and @warn_symbol != ''
|
453
|
+
@output << ' ' + @warn_symbol
|
454
|
+
end
|
508
455
|
end
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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)
|
456
|
+
|
457
|
+
# Append to log result of merging newlines from exception message
|
458
|
+
if @merge_newlines != nil and @merge_newlines != ''
|
459
|
+
@debug_out.puts "[#{$!.message}]" if WATCHER_DEBUG and WATCHER_DEBUG_OUTPUT
|
460
|
+
message = $!.message.split(/\n/).join @merge_newlines
|
519
461
|
end
|
520
|
-
|
521
|
-
|
522
|
-
+ "(must be Symbol or Array of Symbols and Procs)"
|
523
|
-
raise ArgumentError.new(err_msg)
|
524
|
-
end
|
525
|
-
end
|
462
|
+
@output << ' ' + message + "\n"
|
463
|
+
@output.flush
|
526
464
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
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
|
465
|
+
if tries_remaining > 0
|
466
|
+
begin
|
467
|
+
# Call the user-supplied recovery Proc with the original exception
|
468
|
+
event.proc.call($!)
|
469
|
+
rescue # from exception during user's recovery proc
|
470
|
+
# If user-supplied Proc caused an Exception, then bail
|
471
|
+
@errors += 1
|
472
|
+
raise $!
|
473
|
+
end
|
540
474
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
output << e.message
|
556
|
-
end
|
557
|
-
@output << output << "\n"
|
475
|
+
@output << get_protected_log_time_string + ': ' \
|
476
|
+
+ event.hier + ' ### '
|
477
|
+
@output << case tries_remaining
|
478
|
+
when 1
|
479
|
+
"(1 try left)\n"
|
480
|
+
else
|
481
|
+
'(' + tries_remaining.to_s + " tries left)\n"
|
482
|
+
end
|
483
|
+
@output.flush
|
484
|
+
|
485
|
+
retry
|
486
|
+
else # no more retries remaining
|
487
|
+
if not $!.kind_of? Exception
|
488
|
+
raise "$! no longer an exception [#{$!.inspect}]"
|
558
489
|
end
|
559
|
-
|
560
|
-
if e != nil
|
490
|
+
if event.failure == :error
|
561
491
|
@errors += 1
|
562
|
-
raise
|
492
|
+
raise $!
|
493
|
+
else
|
494
|
+
@warnings += 1
|
495
|
+
@output.flush
|
563
496
|
end
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
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
|
497
|
+
end # perform a retry attempt
|
498
|
+
ensure
|
499
|
+
# Once this Event is complete, make the next event a sibling of this event
|
500
|
+
@event_relationship = :sibling
|
501
|
+
@last_hier = event.hier.dup
|
502
|
+
end # rescue from top tier exception
|
574
503
|
end
|
575
|
-
|
576
|
-
# Set to true when Watcher should execute itself in debugging mode.
|
577
|
-
WATCHER_DEBUG=false
|
578
504
|
|
579
|
-
#
|
580
|
-
|
505
|
+
# Watcher version
|
506
|
+
VERSION = '1.2.0'
|
581
507
|
|
582
|
-
# Set to true when
|
583
|
-
|
584
|
-
|
585
|
-
# Set to true when debugging Watcher handling of fail actions.
|
586
|
-
WATCHER_DEBUG_FAILACTIONS=false
|
508
|
+
# Set to true when Watcher should execute itself in debugging mode.
|
509
|
+
WATCHER_DEBUG = false
|
587
510
|
|
511
|
+
# Set to true when debugging Watcher failure actions.
|
512
|
+
WATCHER_DEBUG_FAIL_ACTIONS = false
|
513
|
+
|
588
514
|
# Set to true when debugging Watcher verbosity levels.
|
589
|
-
|
515
|
+
WATCHER_DEBUG_VERBOSITY = false
|
590
516
|
|
591
|
-
#
|
592
|
-
|
517
|
+
# Name of file that Watcher creates for its debugging output.
|
518
|
+
WATCHER_DEBUG_OUTPUT_FILENAME = 'watcher-debug.log'
|
593
519
|
|
520
|
+
# Set to true when debugging Watcher instance attribute settings.
|
521
|
+
WATCHER_DEBUG_ATTRIBUTES = false
|
522
|
+
|
594
523
|
# Set to true when debugging Watcher's log strings.
|
595
|
-
WATCHER_DEBUG_OUTPUT=false
|
524
|
+
WATCHER_DEBUG_OUTPUT = false
|
596
525
|
end
|