TwP-logging 0.9.7

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.
Files changed (54) hide show
  1. data/History.txt +169 -0
  2. data/README.rdoc +102 -0
  3. data/Rakefile +42 -0
  4. data/data/bad_logging_1.rb +13 -0
  5. data/data/bad_logging_2.rb +21 -0
  6. data/data/logging.rb +42 -0
  7. data/data/logging.yaml +63 -0
  8. data/data/simple_logging.rb +13 -0
  9. data/lib/logging.rb +408 -0
  10. data/lib/logging/appender.rb +303 -0
  11. data/lib/logging/appenders/buffering.rb +167 -0
  12. data/lib/logging/appenders/console.rb +62 -0
  13. data/lib/logging/appenders/email.rb +75 -0
  14. data/lib/logging/appenders/file.rb +54 -0
  15. data/lib/logging/appenders/growl.rb +197 -0
  16. data/lib/logging/appenders/io.rb +69 -0
  17. data/lib/logging/appenders/rolling_file.rb +291 -0
  18. data/lib/logging/appenders/syslog.rb +201 -0
  19. data/lib/logging/config/configurator.rb +190 -0
  20. data/lib/logging/config/yaml_configurator.rb +195 -0
  21. data/lib/logging/layout.rb +119 -0
  22. data/lib/logging/layouts/basic.rb +34 -0
  23. data/lib/logging/layouts/pattern.rb +296 -0
  24. data/lib/logging/log_event.rb +51 -0
  25. data/lib/logging/logger.rb +490 -0
  26. data/lib/logging/repository.rb +172 -0
  27. data/lib/logging/root_logger.rb +61 -0
  28. data/lib/logging/stats.rb +278 -0
  29. data/lib/logging/utils.rb +130 -0
  30. data/logging.gemspec +41 -0
  31. data/test/appenders/test_buffered_io.rb +183 -0
  32. data/test/appenders/test_console.rb +66 -0
  33. data/test/appenders/test_email.rb +171 -0
  34. data/test/appenders/test_file.rb +93 -0
  35. data/test/appenders/test_growl.rb +128 -0
  36. data/test/appenders/test_io.rb +142 -0
  37. data/test/appenders/test_rolling_file.rb +207 -0
  38. data/test/appenders/test_syslog.rb +194 -0
  39. data/test/benchmark.rb +87 -0
  40. data/test/config/test_configurator.rb +70 -0
  41. data/test/config/test_yaml_configurator.rb +40 -0
  42. data/test/layouts/test_basic.rb +43 -0
  43. data/test/layouts/test_pattern.rb +177 -0
  44. data/test/setup.rb +74 -0
  45. data/test/test_appender.rb +166 -0
  46. data/test/test_layout.rb +110 -0
  47. data/test/test_log_event.rb +80 -0
  48. data/test/test_logger.rb +734 -0
  49. data/test/test_logging.rb +267 -0
  50. data/test/test_repository.rb +126 -0
  51. data/test/test_root_logger.rb +81 -0
  52. data/test/test_stats.rb +274 -0
  53. data/test/test_utils.rb +116 -0
  54. metadata +156 -0
@@ -0,0 +1,172 @@
1
+
2
+ require 'singleton'
3
+
4
+ module Logging
5
+
6
+ # The Repository is a hash that stores references to all Loggers
7
+ # that have been created. It provides methods to determine parent/child
8
+ # relationships between Loggers and to retrieve Loggers from the hash.
9
+ #
10
+ class Repository
11
+ include Singleton
12
+
13
+ PATH_DELIMITER = '::' # :nodoc:
14
+
15
+ # nodoc:
16
+ #
17
+ # This is a singleton class -- use the +instance+ method to obtain the
18
+ # +Repository+ instance.
19
+ #
20
+ def initialize
21
+ @h = {:root => ::Logging::RootLogger.new}
22
+
23
+ # configures the internal logger which is disabled by default
24
+ logger = ::Logging::Logger.allocate
25
+ logger._setup(
26
+ to_key(::Logging),
27
+ :parent => @h[:root],
28
+ :additive => false,
29
+ :level => ::Logging::LEVELS.length # turns this logger off
30
+ )
31
+ @h[logger.name] = logger
32
+ end
33
+
34
+ # call-seq:
35
+ # instance[name]
36
+ #
37
+ # Returns the +Logger+ named _name_.
38
+ #
39
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
40
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
41
+ # used to retrieve the logger. When _name_ is an object the name of the
42
+ # object's class will be used to retrieve the logger.
43
+ #
44
+ # Example:
45
+ #
46
+ # repo = Repository.instance
47
+ # obj = MyClass.new
48
+ #
49
+ # log1 = repo[obj]
50
+ # log2 = repo[MyClass]
51
+ # log3 = repo['MyClass']
52
+ #
53
+ # log1.object_id == log2.object_id # => true
54
+ # log2.object_id == log3.object_id # => true
55
+ #
56
+ def []( key ) @h[to_key(key)] end
57
+
58
+ # call-seq:
59
+ # instance[name] = logger
60
+ #
61
+ # Stores the _logger_ under the given _name_.
62
+ #
63
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
64
+ # store the logger. When _name_ is a +Class+ the class name will be
65
+ # used to store the logger. When _name_ is an object the name of the
66
+ # object's class will be used to store the logger.
67
+ #
68
+ def []=( key, val ) @h[to_key(key)] = val end
69
+
70
+ # call-seq:
71
+ # fetch( name )
72
+ #
73
+ # Returns the +Logger+ named _name_. An +IndexError+ will be raised if
74
+ # the logger does not exist.
75
+ #
76
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
77
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
78
+ # used to retrieve the logger. When _name_ is an object the name of the
79
+ # object's class will be used to retrieve the logger.
80
+ #
81
+ def fetch( key ) @h.fetch(to_key(key)) end
82
+
83
+ # call-seq:
84
+ # has_logger?( name )
85
+ #
86
+ # Returns +true+ if the given logger exists in the repository. Returns
87
+ # +false+ if this is not the case.
88
+ #
89
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
90
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
91
+ # used to retrieve the logger. When _name_ is an object the name of the
92
+ # object's class will be used to retrieve the logger.
93
+ #
94
+ def has_logger?( key ) @h.has_key?(to_key(key)) end
95
+
96
+ # call-seq:
97
+ # parent( key )
98
+ #
99
+ # Returns the parent logger for the logger identified by _key_ where
100
+ # _key_ follows the same identification rules described in
101
+ # <tt>Repository#[]</tt>. A parent is returned regardless of the
102
+ # existence of the logger referenced by _key_.
103
+ #
104
+ # A note about parents -
105
+ #
106
+ # If you have a class A::B::C, then the parent of C is B, and the parent
107
+ # of B is A. Parents are determined by namespace.
108
+ #
109
+ def parent( key )
110
+ name = _parent_name(to_key(key))
111
+ return if name.nil?
112
+ @h[name]
113
+ end
114
+
115
+ # call-seq:
116
+ # children( key )
117
+ #
118
+ # Returns an array of the children loggers for the logger identified by
119
+ # _key_ where _key_ follows the same identification rules described in
120
+ # +Repository#[]+. Children are returned regardless of the
121
+ # existence of the logger referenced by _key_.
122
+ #
123
+ def children( parent )
124
+ ary = []
125
+ parent = to_key(parent)
126
+
127
+ @h.each_pair do |child,logger|
128
+ next if :root == child
129
+ ary << logger if parent == _parent_name(child)
130
+ end
131
+ return ary.sort
132
+ end
133
+
134
+ # call-seq:
135
+ # to_key( key )
136
+ #
137
+ # Takes the given _key_ and converts it into a form that can be used to
138
+ # retrieve a logger from the +Repository+ hash.
139
+ #
140
+ # When _key_ is a +String+ or a +Symbol+ it will be returned "as is".
141
+ # When _key_ is a +Class+ the class name will be returned. When _key_ is
142
+ # an object the name of the object's class will be returned.
143
+ #
144
+ def to_key( key )
145
+ case key
146
+ when :root, 'root'; :root
147
+ when String; key
148
+ when Symbol; key.to_s
149
+ when Module; key.logger_name
150
+ when Object; key.class.logger_name
151
+ end
152
+ end
153
+
154
+ # Returns the name of the parent for the logger identified by the given
155
+ # _key_. If the _key_ is for the root logger, then +nil+ is returned.
156
+ #
157
+ def _parent_name( key )
158
+ return if :root == key
159
+
160
+ a = key.split PATH_DELIMITER
161
+ p = :root
162
+ while a.slice!(-1) and !a.empty?
163
+ k = a.join PATH_DELIMITER
164
+ if @h.has_key? k then p = k; break end
165
+ end
166
+ p
167
+ end
168
+
169
+ end # class Repository
170
+ end # module Logging
171
+
172
+ # EOF
@@ -0,0 +1,61 @@
1
+
2
+ module Logging
3
+
4
+ # The root logger exists to ensure that all loggers have a parent and a
5
+ # defined logging level. If a logger is additive, eventually its log
6
+ # events will propogate up to the root logger.
7
+ #
8
+ class RootLogger < Logger
9
+
10
+ # undefine the methods that the root logger does not need
11
+ %w(additive additive= parent parent=).each do |m|
12
+ undef_method m.intern
13
+ end
14
+
15
+ attr_reader :level
16
+
17
+ # call-seq:
18
+ # RootLogger.new
19
+ #
20
+ # Returns a new root logger instance. This method will be called only
21
+ # once when the +Repository+ singleton instance is created.
22
+ #
23
+ def initialize( )
24
+ ::Logging.init unless ::Logging.const_defined? 'MAX_LEVEL_LENGTH'
25
+
26
+ @name = 'root'
27
+ @appenders = []
28
+ @additive = false
29
+ @trace = false
30
+ @level = 0
31
+ ::Logging::Logger.define_log_methods(self)
32
+ end
33
+
34
+ # call-seq:
35
+ # log <=> other
36
+ #
37
+ # Compares this logger by name to another logger. The normal return codes
38
+ # for +String+ objects apply.
39
+ #
40
+ def <=>( other )
41
+ case other
42
+ when self; 0
43
+ when ::Logging::Logger; -1
44
+ else raise ArgumentError, 'expecting a Logger instance' end
45
+ end
46
+
47
+ # call-seq:
48
+ # level = :all
49
+ #
50
+ # Set the level for the root logger. The functionality of this method is
51
+ # the same as +Logger#level=+, but setting the level to +nil+ for the
52
+ # root logger is not allowed. The level is silently set to :all.
53
+ #
54
+ def level=( level )
55
+ super(level || 0)
56
+ end
57
+
58
+ end # class RootLogger
59
+ end # module Logging
60
+
61
+ # EOF
@@ -0,0 +1,278 @@
1
+ # Simple statistics collection and logging module.
2
+ #
3
+ module Logging::Stats
4
+
5
+ # A very simple little class for doing some basic fast statistics
6
+ # sampling. You feed it either samples of numeric data you want measured
7
+ # or you call Sampler#tick to get it to add a time delta between the last
8
+ # time you called it. When you're done either call sum, sumsq, num, min,
9
+ # max, mean or sd to get the information. The other option is to just
10
+ # call to_s and see everything.
11
+ #
12
+ # It does all of this very fast and doesn't take up any memory since the
13
+ # samples are not stored but instead all the values are calculated on the
14
+ # fly.
15
+ #
16
+ class Sampler
17
+
18
+ attr_reader :name, :sum, :sumsq, :num, :min, :max, :last
19
+
20
+ # Create a new sampler.
21
+ #
22
+ def initialize( name )
23
+ @name = name
24
+ reset
25
+ end
26
+
27
+ # Resets the internal counters so you can start sampling again.
28
+ #
29
+ def reset
30
+ @sum = 0.0
31
+ @sumsq = 0.0
32
+ @num = 0
33
+ @min = 0.0
34
+ @max = 0.0
35
+ @last = nil
36
+ @last_time = Time.now.to_f
37
+ self
38
+ end
39
+
40
+ # Coalesce the statistics from the _other_ sampler into this one. The
41
+ # _other_ sampler is not modified by this method.
42
+ #
43
+ # Coalescing the same two samplers mutliple times should only be done if
44
+ # one of the samplers is reset between calls to this method. Otherwise
45
+ # statistics will be counted multiple times.
46
+ #
47
+ def coalesce( other )
48
+ @sum += other.sum
49
+ @sumsq += other.sumsq
50
+ if other.num > 0
51
+ @min = other.min if @min > other.min
52
+ @max = other.max if @max < other.max
53
+ @last = other.last
54
+ end
55
+ @num += other.num
56
+ end
57
+
58
+ # Adds a sampling to the calculations.
59
+ #
60
+ def sample( s )
61
+ @sum += s
62
+ @sumsq += s * s
63
+ if @num == 0
64
+ @min = @max = s
65
+ else
66
+ @min = s if @min > s
67
+ @max = s if @max < s
68
+ end
69
+ @num += 1
70
+ @last = s
71
+ end
72
+
73
+ # Returns statistics in a common format.
74
+ #
75
+ def to_s
76
+ "[%s]: SUM=%0.6f, SUMSQ=%0.6f, NUM=%d, MEAN=%0.6f, SD=%0.6f, MIN=%0.6f, MAX=%0.6f" % to_a
77
+ end
78
+
79
+ # An array of the values: [name,sum,sumsq,num,mean,sd,min,max]
80
+ #
81
+ def to_a
82
+ [name, sum, sumsq, num, mean, sd, min, max]
83
+ end
84
+
85
+ # Class method that returns the headers that a CSV file would have for the
86
+ # values that this stats object is using.
87
+ #
88
+ def self.keys
89
+ %w[name sum sumsq num mean sd min max]
90
+ end
91
+
92
+ def to_hash
93
+ {:name => name, :sum => sum, :sumsq => sumsq, :num => num,
94
+ :mean => mean, :sd => sd, :min => min, :max => max}
95
+ end
96
+
97
+ # Calculates and returns the mean for the data passed so far.
98
+ #
99
+ def mean
100
+ return 0.0 if num < 1
101
+ sum / num
102
+ end
103
+
104
+ # Calculates the standard deviation of the data so far.
105
+ #
106
+ def sd
107
+ return 0.0 if num < 2
108
+
109
+ # (sqrt( ((s).sumsq - ( (s).sum * (s).sum / (s).num)) / ((s).num-1) ))
110
+ begin
111
+ return Math.sqrt( (sumsq - ( sum * sum / num)) / (num-1) )
112
+ rescue Errno::EDOM
113
+ return 0.0
114
+ end
115
+ end
116
+
117
+ # You can just call tick repeatedly if you need the delta times
118
+ # between a set of sample periods, but many times you actually want
119
+ # to sample how long something takes between a start/end period.
120
+ # Call mark at the beginning and then tick at the end you'll get this
121
+ # kind of measurement. Don't mix mark/tick and tick sampling together
122
+ # or the measurement will be meaningless.
123
+ #
124
+ def mark
125
+ @last_time = Time.now.to_f
126
+ end
127
+
128
+ # Adds a time delta between now and the last time you called this. This
129
+ # will give you the average time between two activities.
130
+ #
131
+ # An example is:
132
+ #
133
+ # t = Sampler.new("do_stuff")
134
+ # 10000.times { do_stuff(); t.tick }
135
+ # t.dump("time")
136
+ #
137
+ def tick
138
+ now = Time.now.to_f
139
+ sample(now - @last_time)
140
+ @last_time = now
141
+ end
142
+ end # class Sampler
143
+
144
+ # The Tracker class provides synchronized access to a collection of
145
+ # related samplers.
146
+ #
147
+ class Tracker
148
+
149
+ attr_reader :stats
150
+
151
+ # Create a new Tracker instance. An optional boolean can be bassed in to
152
+ # change the "threadsafe" value of the tracker. By default all trackers
153
+ # are created to be threadsafe.
154
+ #
155
+ def initialize( threadsafe = true )
156
+ @stats = Hash.new do |h,name|
157
+ h[name] = ::Logging::Stats::Sampler.new(name)
158
+ end
159
+ @mutex = threadsafe ? ReentrantMutex.new : nil
160
+ @runner = nil
161
+ end
162
+
163
+ # Coalesce the samplers from the _other_ tracker into this one. The
164
+ # _other_ tracker is not modified by this method.
165
+ #
166
+ # Coalescing the same two trackers mutliple times should only be done if
167
+ # one of the trackers is reset between calls to this method. Otherwise
168
+ # statistics will be counted multiple times.
169
+ #
170
+ # Only this tracker is locked when the coalescing is happening. It is
171
+ # left to the user to lock the other tracker if that is the desired
172
+ # behavior. This is a deliberate choice in order to prevent deadlock
173
+ # situations where two threads are contending on the same mutex.
174
+ #
175
+ def coalesce( other )
176
+ sync {
177
+ other.stats.each do |name,sampler|
178
+ stats[name].coalesce(sampler)
179
+ end
180
+ }
181
+ end
182
+
183
+ # Add the given _value_ to the named _event_ sampler. The sampler will
184
+ # be created if it does not exist.
185
+ #
186
+ def sample( event, value )
187
+ sync {stats[event].sample(value)}
188
+ end
189
+
190
+ # Mark the named _event_ sampler. The sampler will be created if it does
191
+ # not exist.
192
+ #
193
+ def mark( event )
194
+ sync {stats[event].mark}
195
+ end
196
+
197
+ # Tick the named _event_ sampler. The sampler will be created if it does
198
+ # not exist.
199
+ #
200
+ def tick( event )
201
+ sync {stats[event].tick}
202
+ end
203
+
204
+ # Time the execution of the given block and store the results in the
205
+ # named _event_ sampler. The sampler will be created if it does not
206
+ # exist.
207
+ #
208
+ def time( event )
209
+ sync {stats[event].mark}
210
+ yield
211
+ ensure
212
+ sync {stats[event].tick}
213
+ end
214
+
215
+ # Reset all the samplers managed by this tracker.
216
+ #
217
+ def reset
218
+ sync {stats.each_value {|sampler| sampler.reset}}
219
+ self
220
+ end
221
+
222
+ # Periodically execute the given _block_ at the given _period_. The
223
+ # tracker will be locked while the block is executing.
224
+ #
225
+ # This method is useful for logging statistics at given interval.
226
+ #
227
+ # Example
228
+ #
229
+ # periodically_run( 300 ) {
230
+ # logger = Logging::Logger['stats']
231
+ # tracker.each {|sampler| logger << sampler.to_s}
232
+ # tracker.reset
233
+ # }
234
+ #
235
+ def periodically_run( period, &block )
236
+ raise ArgumentError, 'a runner already exists' unless @runner.nil?
237
+
238
+ @runner = Thread.new do
239
+ start = stop = Time.now.to_f
240
+ loop do
241
+ seconds = period - (stop-start)
242
+ seconds = period if seconds <= 0
243
+ sleep seconds
244
+
245
+ start = Time.now.to_f
246
+ break if Thread.current[:stop] == true
247
+ if @mutex then @mutex.synchronize(&block)
248
+ else block.call end
249
+ stop = Time.now.to_f
250
+ end
251
+ end
252
+ end
253
+
254
+ # Stop the current periodic runner if present.
255
+ #
256
+ def stop
257
+ return if @runner.nil?
258
+ @runner[:stop] = true
259
+ @runner.wakeup if @runner.status
260
+ @runner = nil
261
+ end
262
+
263
+ # call-seq:
264
+ # sync { block }
265
+ #
266
+ # Obtains an exclusive lock, runs the block, and releases the lock when
267
+ # the block completes. This method is re-entrant so that a single thread
268
+ # can call +sync+ multiple times without hanging the thread.
269
+ #
270
+ def sync
271
+ return yield if @mutex.nil?
272
+ @mutex.synchronize {yield}
273
+ end
274
+ end # class Tracker
275
+
276
+ end # module Logging::Stats
277
+
278
+ # EOF