ottobar-logging 0.9.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/History.txt +158 -0
  2. data/README.rdoc +102 -0
  3. data/Rakefile +41 -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 +107 -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 +49 -0
  25. data/lib/logging/logger.rb +491 -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/test/appenders/test_buffered_io.rb +183 -0
  31. data/test/appenders/test_console.rb +66 -0
  32. data/test/appenders/test_email.rb +171 -0
  33. data/test/appenders/test_file.rb +93 -0
  34. data/test/appenders/test_growl.rb +128 -0
  35. data/test/appenders/test_io.rb +142 -0
  36. data/test/appenders/test_rolling_file.rb +207 -0
  37. data/test/appenders/test_syslog.rb +191 -0
  38. data/test/benchmark.rb +87 -0
  39. data/test/config/test_configurator.rb +70 -0
  40. data/test/config/test_yaml_configurator.rb +40 -0
  41. data/test/layouts/test_basic.rb +43 -0
  42. data/test/layouts/test_pattern.rb +177 -0
  43. data/test/setup.rb +69 -0
  44. data/test/test_appender.rb +166 -0
  45. data/test/test_layout.rb +107 -0
  46. data/test/test_log_event.rb +80 -0
  47. data/test/test_logger.rb +734 -0
  48. data/test/test_logging.rb +267 -0
  49. data/test/test_repository.rb +126 -0
  50. data/test/test_root_logger.rb +81 -0
  51. data/test/test_stats.rb +274 -0
  52. data/test/test_utils.rb +114 -0
  53. metadata +152 -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