sgeorgi-logging 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/History.txt +262 -0
  2. data/README.rdoc +115 -0
  3. data/Rakefile +32 -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/examples/appenders.rb +47 -0
  10. data/examples/classes.rb +41 -0
  11. data/examples/consolidation.rb +83 -0
  12. data/examples/fork.rb +37 -0
  13. data/examples/formatting.rb +51 -0
  14. data/examples/hierarchies.rb +73 -0
  15. data/examples/layouts.rb +48 -0
  16. data/examples/loggers.rb +29 -0
  17. data/examples/names.rb +43 -0
  18. data/examples/simple.rb +17 -0
  19. data/lib/logging.rb +528 -0
  20. data/lib/logging/appender.rb +260 -0
  21. data/lib/logging/appenders.rb +137 -0
  22. data/lib/logging/appenders/buffering.rb +178 -0
  23. data/lib/logging/appenders/console.rb +60 -0
  24. data/lib/logging/appenders/email.rb +75 -0
  25. data/lib/logging/appenders/file.rb +75 -0
  26. data/lib/logging/appenders/growl.rb +197 -0
  27. data/lib/logging/appenders/io.rb +69 -0
  28. data/lib/logging/appenders/rolling_file.rb +327 -0
  29. data/lib/logging/appenders/string_io.rb +68 -0
  30. data/lib/logging/appenders/syslog.rb +210 -0
  31. data/lib/logging/config/configurator.rb +188 -0
  32. data/lib/logging/config/yaml_configurator.rb +191 -0
  33. data/lib/logging/layout.rb +117 -0
  34. data/lib/logging/layouts.rb +47 -0
  35. data/lib/logging/layouts/basic.rb +32 -0
  36. data/lib/logging/layouts/parseable.rb +211 -0
  37. data/lib/logging/layouts/pattern.rb +311 -0
  38. data/lib/logging/log_event.rb +45 -0
  39. data/lib/logging/logger.rb +504 -0
  40. data/lib/logging/repository.rb +232 -0
  41. data/lib/logging/root_logger.rb +61 -0
  42. data/lib/logging/stats.rb +278 -0
  43. data/lib/logging/utils.rb +201 -0
  44. data/lib/spec/logging_helper.rb +34 -0
  45. data/test/appenders/test_buffered_io.rb +176 -0
  46. data/test/appenders/test_console.rb +66 -0
  47. data/test/appenders/test_email.rb +170 -0
  48. data/test/appenders/test_file.rb +95 -0
  49. data/test/appenders/test_growl.rb +127 -0
  50. data/test/appenders/test_io.rb +129 -0
  51. data/test/appenders/test_rolling_file.rb +209 -0
  52. data/test/appenders/test_syslog.rb +194 -0
  53. data/test/benchmark.rb +86 -0
  54. data/test/config/test_configurator.rb +70 -0
  55. data/test/config/test_yaml_configurator.rb +40 -0
  56. data/test/layouts/test_basic.rb +42 -0
  57. data/test/layouts/test_json.rb +112 -0
  58. data/test/layouts/test_pattern.rb +198 -0
  59. data/test/layouts/test_yaml.rb +121 -0
  60. data/test/setup.rb +43 -0
  61. data/test/test_appender.rb +152 -0
  62. data/test/test_consolidate.rb +46 -0
  63. data/test/test_layout.rb +110 -0
  64. data/test/test_log_event.rb +80 -0
  65. data/test/test_logger.rb +699 -0
  66. data/test/test_logging.rb +267 -0
  67. data/test/test_repository.rb +158 -0
  68. data/test/test_root_logger.rb +81 -0
  69. data/test/test_stats.rb +274 -0
  70. data/test/test_utils.rb +116 -0
  71. data/version.txt +1 -0
  72. metadata +227 -0
@@ -0,0 +1,232 @@
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
+ @masters = []
22
+ @h = {:root => ::Logging::RootLogger.new}
23
+
24
+ # configures the internal logger which is disabled by default
25
+ logger = ::Logging::Logger.allocate
26
+ logger._setup(
27
+ to_key(::Logging),
28
+ :parent => @h[:root],
29
+ :additive => false,
30
+ :level => ::Logging::LEVELS.length # turns this logger off
31
+ )
32
+ @h[logger.name] = logger
33
+ end
34
+
35
+ # call-seq:
36
+ # instance[name]
37
+ #
38
+ # Returns the +Logger+ named _name_.
39
+ #
40
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
41
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
42
+ # used to retrieve the logger. When _name_ is an object the name of the
43
+ # object's class will be used to retrieve the logger.
44
+ #
45
+ # Example:
46
+ #
47
+ # repo = Repository.instance
48
+ # obj = MyClass.new
49
+ #
50
+ # log1 = repo[obj]
51
+ # log2 = repo[MyClass]
52
+ # log3 = repo['MyClass']
53
+ #
54
+ # log1.object_id == log2.object_id # => true
55
+ # log2.object_id == log3.object_id # => true
56
+ #
57
+ def []( key ) @h[to_key(key)] end
58
+
59
+ # call-seq:
60
+ # instance[name] = logger
61
+ #
62
+ # Stores the _logger_ under the given _name_.
63
+ #
64
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
65
+ # store the logger. When _name_ is a +Class+ the class name will be
66
+ # used to store the logger. When _name_ is an object the name of the
67
+ # object's class will be used to store the logger.
68
+ #
69
+ def []=( key, val ) @h[to_key(key)] = val end
70
+
71
+ # call-seq:
72
+ # fetch( name )
73
+ #
74
+ # Returns the +Logger+ named _name_. An +IndexError+ will be raised if
75
+ # the logger does not exist.
76
+ #
77
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
78
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
79
+ # used to retrieve the logger. When _name_ is an object the name of the
80
+ # object's class will be used to retrieve the logger.
81
+ #
82
+ def fetch( key ) @h.fetch(to_key(key)) end
83
+
84
+ # call-seq:
85
+ # has_logger?( name )
86
+ #
87
+ # Returns +true+ if the given logger exists in the repository. Returns
88
+ # +false+ if this is not the case.
89
+ #
90
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
91
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
92
+ # used to retrieve the logger. When _name_ is an object the name of the
93
+ # object's class will be used to retrieve the logger.
94
+ #
95
+ def has_logger?( key ) @h.has_key?(to_key(key)) end
96
+
97
+ # call-seq:
98
+ # parent( key )
99
+ #
100
+ # Returns the parent logger for the logger identified by _key_ where
101
+ # _key_ follows the same identification rules described in
102
+ # <tt>Repository#[]</tt>. A parent is returned regardless of the
103
+ # existence of the logger referenced by _key_.
104
+ #
105
+ # A note about parents -
106
+ #
107
+ # If you have a class A::B::C, then the parent of C is B, and the parent
108
+ # of B is A. Parents are determined by namespace.
109
+ #
110
+ def parent( key )
111
+ name = parent_name(to_key(key))
112
+ return if name.nil?
113
+ @h[name]
114
+ end
115
+
116
+ # call-seq:
117
+ # children( key )
118
+ #
119
+ # Returns an array of the children loggers for the logger identified by
120
+ # _key_ where _key_ follows the same identification rules described in
121
+ # +Repository#[]+. Children are returned regardless of the
122
+ # existence of the logger referenced by _key_.
123
+ #
124
+ def children( parent )
125
+ ary = []
126
+ parent = to_key(parent)
127
+
128
+ @h.each_pair do |child,logger|
129
+ next if :root == child
130
+ ary << logger if parent == parent_name(child)
131
+ end
132
+ return ary.sort
133
+ end
134
+
135
+ # call-seq:
136
+ # to_key( key )
137
+ #
138
+ # Takes the given _key_ and converts it into a form that can be used to
139
+ # retrieve a logger from the +Repository+ hash.
140
+ #
141
+ # When _key_ is a +String+ or a +Symbol+ it will be returned "as is".
142
+ # When _key_ is a +Class+ the class name will be returned. When _key_ is
143
+ # an object the name of the object's class will be returned.
144
+ #
145
+ def to_key( key )
146
+ case key
147
+ when :root, 'root'; :root
148
+ when String; key
149
+ when Symbol; key.to_s
150
+ when Module; key.logger_name
151
+ when Object; key.class.logger_name
152
+ end
153
+ end
154
+
155
+ # Returns the name of the parent for the logger identified by the given
156
+ # _key_. If the _key_ is for the root logger, then +nil+ is returned.
157
+ #
158
+ def parent_name( key )
159
+ return if :root == key
160
+
161
+ a = key.split PATH_DELIMITER
162
+ p = :root
163
+ while a.slice!(-1) and !a.empty?
164
+ k = a.join PATH_DELIMITER
165
+ if @h.has_key? k then p = k; break end
166
+ end
167
+ p
168
+ end
169
+
170
+ # call-seq:
171
+ # add_master( 'First::Name', 'Second::Name', ... )
172
+ #
173
+ # Add the given logger names to the list of consolidation masters. All
174
+ # classes in the given namespace(s) will use these loggers instead of
175
+ # creating their own individual loggers.
176
+ #
177
+ def add_master( *args )
178
+ args.map do |key|
179
+ key = to_key(key)
180
+ @masters << key unless @masters.include? key
181
+ key
182
+ end
183
+ end
184
+
185
+ # call-seq:
186
+ # master_for( key )
187
+ #
188
+ # Retruns the consolidation master name for the given _key_. If there is
189
+ # no consolidation master, then +nil+ is returned.
190
+ #
191
+ def master_for( key )
192
+ return if @masters.empty?
193
+ key = to_key(key)
194
+
195
+ loop do
196
+ break key if @masters.include? key
197
+ break nil if :root == key
198
+
199
+ if index = key.rindex(PATH_DELIMITER)
200
+ key = key.slice(0, index)
201
+ else
202
+ key = :root
203
+ end
204
+ end
205
+ end
206
+
207
+ # :stopdoc:
208
+ def self.reset
209
+ if defined?(@singleton__instance__)
210
+ @singleton__mutex__.synchronize {
211
+ @singleton__instance__ = nil
212
+ }
213
+ else
214
+ @__instance__ = nil
215
+ class << self
216
+ nonce = class << Singleton; self; end
217
+ if defined?(nonce::FirstInstanceCall)
218
+ define_method(:instance, nonce::FirstInstanceCall)
219
+ else
220
+ remove_method(:instance)
221
+ Singleton.__init__(::Logging::Repository)
222
+ end
223
+ end
224
+ end
225
+ return nil
226
+ end
227
+ # :startdoc:
228
+
229
+ end # class Repository
230
+ end # module Logging
231
+
232
+ # 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