logsly 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/lib/logsly/colors.rb +2 -2
  4. data/lib/logsly/logging182/appender.rb +290 -0
  5. data/lib/logsly/logging182/appenders/buffering.rb +398 -0
  6. data/lib/logsly/logging182/appenders/console.rb +81 -0
  7. data/lib/logsly/logging182/appenders/email.rb +178 -0
  8. data/lib/logsly/logging182/appenders/file.rb +85 -0
  9. data/lib/logsly/logging182/appenders/growl.rb +200 -0
  10. data/lib/logsly/logging182/appenders/io.rb +84 -0
  11. data/lib/logsly/logging182/appenders/rolling_file.rb +338 -0
  12. data/lib/logsly/logging182/appenders/string_io.rb +92 -0
  13. data/lib/logsly/logging182/appenders/syslog.rb +215 -0
  14. data/lib/logsly/logging182/appenders.rb +64 -0
  15. data/lib/logsly/logging182/color_scheme.rb +248 -0
  16. data/lib/logsly/logging182/config/configurator.rb +187 -0
  17. data/lib/logsly/logging182/config/yaml_configurator.rb +190 -0
  18. data/lib/logsly/logging182/diagnostic_context.rb +332 -0
  19. data/lib/logsly/logging182/layout.rb +132 -0
  20. data/lib/logsly/logging182/layouts/basic.rb +38 -0
  21. data/lib/logsly/logging182/layouts/parseable.rb +256 -0
  22. data/lib/logsly/logging182/layouts/pattern.rb +568 -0
  23. data/lib/logsly/logging182/layouts.rb +9 -0
  24. data/lib/logsly/logging182/log_event.rb +44 -0
  25. data/lib/logsly/logging182/logger.rb +509 -0
  26. data/lib/logsly/logging182/proxy.rb +59 -0
  27. data/lib/logsly/logging182/rails_compat.rb +36 -0
  28. data/lib/logsly/logging182/repository.rb +231 -0
  29. data/lib/logsly/logging182/root_logger.rb +60 -0
  30. data/lib/logsly/logging182/stats.rb +277 -0
  31. data/lib/logsly/logging182/utils.rb +231 -0
  32. data/lib/logsly/logging182.rb +559 -0
  33. data/lib/logsly/outputs.rb +5 -5
  34. data/lib/logsly/version.rb +1 -1
  35. data/lib/logsly.rb +6 -6
  36. data/logsly.gemspec +4 -2
  37. data/test/unit/colors_tests.rb +3 -3
  38. data/test/unit/logsly_tests.rb +14 -14
  39. data/test/unit/outputs_tests.rb +34 -24
  40. metadata +45 -6
@@ -0,0 +1,509 @@
1
+
2
+ module Logsly::Logging182
3
+
4
+ # The +Logger+ class is the primary interface to the +Logsly::Logging182+ framework.
5
+ # It provides the logging methods that will be called from user methods,
6
+ # and it generates logging events that are sent to the appenders (the
7
+ # appenders take care of sending the log events to the logging
8
+ # destinations -- files, sockets, etc).
9
+ #
10
+ # +Logger+ instances are obtained from the +Repository+ and should
11
+ # not be directly created by users.
12
+ #
13
+ # Example:
14
+ #
15
+ # log = Logsly::Logging182.logger['my logger']
16
+ # log.add_appenders( Logsly::Logging182.appenders.stdout ) # append to STDOUT
17
+ # log.level = :info # log 'info' and above
18
+ #
19
+ # log.info 'starting foo operation'
20
+ # ...
21
+ # log.info 'finishing foo operation'
22
+ # ...
23
+ # log.fatal 'unknown exception', exception
24
+ #
25
+ class Logger
26
+
27
+ @mutex = Mutex.new # :nodoc:
28
+
29
+ class << self
30
+
31
+ # call-seq:
32
+ # Logger.root
33
+ #
34
+ # Returns the root logger.
35
+ #
36
+ def root
37
+ ::Logsly::Logging182::Repository.instance[:root]
38
+ end
39
+
40
+ # :stopdoc:
41
+
42
+ # Overrides the new method such that only one Logger will be created
43
+ # for any given logger name.
44
+ #
45
+ def new( *args )
46
+ return super if args.empty?
47
+
48
+ repo = ::Logsly::Logging182::Repository.instance
49
+ name = repo.to_key(args.shift)
50
+
51
+ @mutex.synchronize do
52
+ logger = repo[name]
53
+ if logger.nil?
54
+
55
+ master = repo.master_for(name)
56
+ if master
57
+ if repo.has_logger?(master)
58
+ logger = repo[master]
59
+ else
60
+ logger = super(master)
61
+ repo[master] = logger
62
+ repo.children(master).each {|c| c.__send__(:parent=, logger)}
63
+ end
64
+ repo[name] = logger
65
+ else
66
+ logger = super(name)
67
+ repo[name] = logger
68
+ repo.children(name).each {|c| c.__send__(:parent=, logger)}
69
+ end
70
+ end
71
+ logger
72
+ end
73
+ end
74
+ alias :[] :new
75
+
76
+ # This is where the actual logging methods are defined. Two methods
77
+ # are created for each log level. The first is a query method used to
78
+ # determine if that perticular logging level is enabled. The second is
79
+ # the actual logging method that accepts a list of objects to be
80
+ # logged or a block. If a block is given, then the object returned
81
+ # from the block will be logged.
82
+ #
83
+ # Example
84
+ #
85
+ # log = Logsly::Logging182::Logger['my logger']
86
+ # log.level = :warn
87
+ #
88
+ # log.info? # => false
89
+ # log.warn? # => true
90
+ # log.warn 'this is your last warning'
91
+ # log.fatal 'I die!', exception
92
+ #
93
+ # log.debug do
94
+ # # expensive method to construct log message
95
+ # msg
96
+ # end
97
+ #
98
+ def define_log_methods( logger )
99
+ ::Logsly::Logging182::LEVELS.each do |name,num|
100
+ code = "undef :#{name} if method_defined? :#{name}\n"
101
+ code << "undef :#{name}? if method_defined? :#{name}?\n"
102
+
103
+ if logger.level > num
104
+ code << <<-CODE
105
+ def #{name}?( ) false end
106
+ def #{name}( data = nil ) false end
107
+ CODE
108
+ else
109
+ code << <<-CODE
110
+ def #{name}?( ) true end
111
+ def #{name}( data = nil )
112
+ data = yield if block_given?
113
+ log_event(::Logsly::Logging182::LogEvent.new(@name, #{num}, data, @trace))
114
+ true
115
+ end
116
+ CODE
117
+ end
118
+
119
+ logger._meta_eval(code, __FILE__, __LINE__)
120
+ end
121
+ logger
122
+ end
123
+ # :startdoc:
124
+
125
+ end # class << self
126
+
127
+ attr_reader :name, :parent, :additive, :trace
128
+
129
+ # call-seq:
130
+ # Logger.new( name )
131
+ # Logger[name]
132
+ #
133
+ # Returns the logger identified by _name_.
134
+ #
135
+ # When _name_ is a +String+ or a +Symbol+ it will be used "as is" to
136
+ # retrieve the logger. When _name_ is a +Class+ the class name will be
137
+ # used to retrieve the logger. When _name_ is an object the name of the
138
+ # object's class will be used to retrieve the logger.
139
+ #
140
+ # Example:
141
+ #
142
+ # obj = MyClass.new
143
+ #
144
+ # log1 = Logger.new(obj)
145
+ # log2 = Logger.new(MyClass)
146
+ # log3 = Logger['MyClass']
147
+ #
148
+ # log1.object_id == log2.object_id # => true
149
+ # log2.object_id == log3.object_id # => true
150
+ #
151
+ def initialize( name )
152
+ case name
153
+ when String
154
+ raise(ArgumentError, "logger must have a name") if name.empty?
155
+ else raise(ArgumentError, "logger name must be a String") end
156
+
157
+ repo = ::Logsly::Logging182::Repository.instance
158
+ _setup(name, :parent => repo.parent(name))
159
+ end
160
+
161
+ # call-seq:
162
+ # log <=> other
163
+ #
164
+ # Compares this logger by name to another logger. The normal return codes
165
+ # for +String+ objects apply.
166
+ #
167
+ def <=>( other )
168
+ case other
169
+ when self; 0
170
+ when ::Logsly::Logging182::RootLogger; 1
171
+ when ::Logsly::Logging182::Logger; @name <=> other.name
172
+ else raise ArgumentError, 'expecting a Logger instance' end
173
+ end
174
+
175
+ # call-seq:
176
+ # log << "message"
177
+ #
178
+ # Log the given message without any formatting and without performing any
179
+ # level checks. The message is logged to all appenders. The message is
180
+ # passed up the logger tree if this logger's additivity is +true+.
181
+ #
182
+ def <<( msg )
183
+ @appenders.each {|a| a << msg}
184
+ @parent << msg if @additive
185
+ end
186
+ alias :write :<<
187
+
188
+ # call-seq:
189
+ # add( severity, message = nil ) {block}
190
+ #
191
+ # Log a message if the given severity is high enough. This is the generic
192
+ # logging method. Users will be more inclined to use #debug, #info, #warn,
193
+ # #error, and #fatal.
194
+ #
195
+ # <b>Message format</b>: +message+ can be any object, but it has to be
196
+ # converted to a String in order to log it. The Logsly::Logging182::format_as
197
+ # method is used to determine how objects chould be converted to
198
+ # strings. Generally, +inspect+ is used.
199
+ #
200
+ # A special case is an +Exception+ object, which will be printed in
201
+ # detail, including message, class, and backtrace.
202
+ #
203
+ # If a _message_ is not given, then the return value from the block is
204
+ # used as the message to log. This is useful when creating the actual
205
+ # message is an expensive operation. This allows the logger to check the
206
+ # severity against the configured level before actually constructing the
207
+ # message.
208
+ #
209
+ # This method returns +true+ if the message was logged, and +false+ is
210
+ # returned if the message was not logged.
211
+ #
212
+ def add( lvl, data = nil, progname = nil )
213
+ lvl = Integer(lvl)
214
+ return false if lvl < level
215
+
216
+ data = yield if block_given?
217
+ log_event(::Logsly::Logging182::LogEvent.new(@name, lvl, data, @trace))
218
+ true
219
+ end
220
+
221
+ # call-seq:
222
+ # additive = true
223
+ #
224
+ # Sets the additivity of the logger. Acceptable values are +true+,
225
+ # 'true', +false+, 'false', or +nil+. In this case +nil+ does not
226
+ # change the additivity
227
+ #
228
+ def additive=( val )
229
+ @additive = case val
230
+ when true, 'true'; true
231
+ when false, 'false'; false
232
+ when nil; @additive
233
+ else raise ArgumentError, 'expecting a boolean' end
234
+ end
235
+
236
+ # call-seq:
237
+ # trace = true
238
+ #
239
+ # Sets the tracing of the logger. Acceptable values are +true+,
240
+ # 'true', +false+, 'false', or +nil+. In this case +nil+ does not
241
+ # change the tracing.
242
+ #
243
+ def trace=( val )
244
+ @trace = case val
245
+ when true, 'true'; true
246
+ when false, 'false'; false
247
+ when nil; @trace
248
+ else raise ArgumentError, 'expecting a boolean' end
249
+ end
250
+
251
+ # call-seq:
252
+ # level => integer
253
+ #
254
+ # Returns an integer which is the defined log level for this logger.
255
+ #
256
+ def level
257
+ return @level unless @level.nil?
258
+ @parent.level
259
+ end
260
+
261
+ # call-seq:
262
+ # level = :all
263
+ #
264
+ # Set the level for this logger. The level can be either a +String+, a
265
+ # +Symbol+, or a +Fixnum+. An +ArgumentError+ is raised if this is not
266
+ # the case.
267
+ #
268
+ # There are two special levels -- "all" and "off". The former will
269
+ # enable log messages from this logger. The latter will disable all log
270
+ # messages from this logger.
271
+ #
272
+ # Setting the logger level to +nil+ will cause the parent's logger level
273
+ # to be used.
274
+ #
275
+ # Example:
276
+ #
277
+ # log.level = :debug
278
+ # log.level = "INFO"
279
+ # log.level = 4
280
+ # log.level = 'off'
281
+ # log.level = :all
282
+ #
283
+ # These produce an +ArgumentError+
284
+ #
285
+ # log.level = Object
286
+ # log.level = -1
287
+ # log.level = 1_000_000_000_000
288
+ #
289
+ def level=( level )
290
+ @level =
291
+ if level.nil? then level
292
+ else
293
+ lvl = case level
294
+ when String, Symbol; ::Logsly::Logging182::level_num(level)
295
+ when Fixnum; level
296
+ else
297
+ raise ArgumentError,
298
+ "level must be a String, Symbol, or Integer"
299
+ end
300
+ if lvl.nil? or lvl < 0 or lvl > ::Logsly::Logging182::LEVELS.length
301
+ raise ArgumentError, "unknown level was given '#{level}'"
302
+ end
303
+ lvl
304
+ end
305
+
306
+ define_log_methods(true)
307
+ self.level
308
+ end
309
+
310
+ # Returns the list of appenders.
311
+ #
312
+ def appenders
313
+ @appenders.dup
314
+ end
315
+
316
+ # call-seq:
317
+ # appenders = app
318
+ #
319
+ # Clears the current list of appenders and replaces them with _app_,
320
+ # where _app_ can be either a single appender or an array of appenders.
321
+ #
322
+ def appenders=( args )
323
+ @appenders.clear
324
+ add_appenders(*args) unless args.nil?
325
+ end
326
+
327
+ # call-seq:
328
+ # add_appenders( appenders )
329
+ #
330
+ # Add the given _appenders_ to the list of appenders, where _appenders_
331
+ # can be either a single appender or an array of appenders.
332
+ #
333
+ def add_appenders( *args )
334
+ args.flatten.each do |arg|
335
+ o = arg.kind_of?(::Logsly::Logging182::Appender) ? arg : ::Logsly::Logging182::Appenders[arg.to_s]
336
+ raise ArgumentError, "unknown appender #{arg.inspect}" if o.nil?
337
+ @appenders << o unless @appenders.include?(o)
338
+ end
339
+ self
340
+ end
341
+
342
+ # call-seq:
343
+ # remove_appenders( appenders )
344
+ #
345
+ # Remove the given _appenders_ from the list of appenders. The appenders
346
+ # to remove can be identified either by name using a +String+ or by
347
+ # passing the appender instance. _appenders_ can be a single appender or
348
+ # an array of appenders.
349
+ #
350
+ def remove_appenders( *args )
351
+ args.flatten.each do |arg|
352
+ @appenders.delete_if do |a|
353
+ case arg
354
+ when String; arg == a.name
355
+ when ::Logsly::Logging182::Appender; arg.object_id == a.object_id
356
+ else
357
+ raise ArgumentError, "#{arg.inspect} is not a 'Logsly::Logging182::Appender'"
358
+ end
359
+ end
360
+ end
361
+ self
362
+ end
363
+
364
+ # call-seq:
365
+ # clear_appenders
366
+ #
367
+ # Remove all appenders from this logger.
368
+ #
369
+ def clear_appenders( ) @appenders.clear end
370
+
371
+ # call-seq:
372
+ # inspect => string
373
+ #
374
+ # Returns a string representation of the logger.
375
+ #
376
+ def inspect
377
+ "<%s:0x%x name=\"%s\">" % [self.class.name, self.object_id, self.name]
378
+ end
379
+
380
+
381
+ protected
382
+
383
+ # call-seq:
384
+ # parent = ParentLogger
385
+ #
386
+ # Set the parent logger for this logger. This method will be invoked by
387
+ # the +Repository+ class when a parent or child is added to the
388
+ # hierarchy.
389
+ #
390
+ def parent=( parent ) @parent = parent end
391
+
392
+ # call-seq:
393
+ # log_event( event )
394
+ #
395
+ # Send the given _event_ to the appenders for logging, and pass the
396
+ # _event_ up to the parent if additive mode is enabled. The log level has
397
+ # already been checked before this method is called.
398
+ #
399
+ def log_event( event )
400
+ @appenders.each {|a| a.append(event)}
401
+ @parent.log_event(event) if @additive
402
+ end
403
+
404
+ # call-seq:
405
+ # define_log_methods( force = false )
406
+ #
407
+ # Define the logging methods for this logger based on the configured log
408
+ # level. If the level is nil, then we will ask our parent for it's level
409
+ # and define log levels accordingly. The force flag will skip this
410
+ # check.
411
+ #
412
+ # Recursively call this method on all our children loggers.
413
+ #
414
+ def define_log_methods( force = false )
415
+ return if @level and !force
416
+
417
+ ::Logsly::Logging182::Logger.define_log_methods(self)
418
+ ::Logsly::Logging182::Repository.instance.children(name).each do |c|
419
+ c.define_log_methods
420
+ end
421
+ self
422
+ end
423
+
424
+ # :stopdoc:
425
+ public
426
+
427
+ # call-seq:
428
+ # _meta_eval( code )
429
+ #
430
+ # Evaluates the given string of _code_ if the singleton class of this
431
+ # Logger object.
432
+ #
433
+ def _meta_eval( code, file = nil, line = nil )
434
+ meta = class << self; self end
435
+ meta.class_eval code, file, line
436
+ end
437
+
438
+ # call-seq:
439
+ # _setup( name, opts = {} )
440
+ #
441
+ # Configures internal variables for the logger. This method can be used
442
+ # to avoid storing the logger in the repository.
443
+ #
444
+ def _setup( name, opts = {} )
445
+ @name = name
446
+ @parent = opts.getopt(:parent)
447
+ @appenders = opts.getopt(:appenders, [])
448
+ @additive = opts.getopt(:additive, true)
449
+ @trace = opts.getopt(:trace, false)
450
+ @level = opts.getopt(:level)
451
+ ::Logsly::Logging182::Logger.define_log_methods(self)
452
+ end
453
+
454
+ # call-seq:
455
+ # _dump_configuration( io = STDOUT, indent = 0 )
456
+ #
457
+ # An internal method that is used to dump this logger's configuration to
458
+ # the given _io_ stream. The configuration includes the logger's name,
459
+ # level, additivity, and trace settings. The configured appenders are
460
+ # also printed to the _io_ stream.
461
+ #
462
+ def _dump_configuration( io = STDOUT, indent = 0 )
463
+ str, spacer, base = '', ' ', 50
464
+ indent_str = indent == 0 ? '' : ' ' * indent
465
+
466
+ str << indent_str
467
+ str << self.name.reduce(base - indent)
468
+ if (str.length + spacer.length) < base
469
+ str << spacer
470
+ str << '.' * (base - str.length)
471
+ end
472
+ io.write(str.ljust(base))
473
+ io.write(spacer)
474
+
475
+ level_str = @level.nil? ? '' : '*'
476
+ level_str << if level < ::Logsly::Logging182::LEVELS.length
477
+ ::Logsly::Logging182.levelify(::Logsly::Logging182::LNAMES[level])
478
+ else
479
+ 'off'
480
+ end
481
+ level_len = ::Logsly::Logging182::MAX_LEVEL_LENGTH + 1
482
+
483
+ io.write("%#{level_len}s" % level_str)
484
+ io.write(spacer)
485
+
486
+ if self.respond_to?(:additive)
487
+ io.write(additive ? '+A' : '-A')
488
+ else
489
+ io.write(' ')
490
+ end
491
+
492
+ io.write(spacer)
493
+ io.write(trace ? '+T' : '-T')
494
+ io.write("\n")
495
+
496
+ @appenders.each do |appender|
497
+ io.write(indent_str)
498
+ io.write('- ')
499
+ io.write(appender.inspect)
500
+ io.write("\n")
501
+ end
502
+
503
+ return io
504
+ end
505
+ # :startdoc:
506
+
507
+ end # Logger
508
+ end # Logsly::Logging182
509
+
@@ -0,0 +1,59 @@
1
+
2
+ module Logsly::Logging182
3
+
4
+ # Defines a Proxy that will log all method calls on the proxied object. This
5
+ # class uses +method_missing+ on a "blank slate" object to intercept all
6
+ # method calls. The method name being called and the arguments are all
7
+ # logged to the proxied object's logger instance. The log level and other
8
+ # settings for the proxied object are honored by the Proxy instance.
9
+ #
10
+ # If you want, you can also supply your own +method_missing+ code as a block
11
+ # to the constructor.
12
+ #
13
+ # Proxy.new(object) do |name, *args, &block|
14
+ # # code to be executed before the proxied method
15
+ # result = @object.send(name, *args, &block)
16
+ # # code to be executed after the proxied method
17
+ # result # <-- always return the result
18
+ # end
19
+ #
20
+ # The proxied object is available as the "@object" variable. The logger is
21
+ # available as the "@logger" variable.
22
+ #
23
+ class Proxy
24
+
25
+ # :stopdoc:
26
+ KEEPERS = %r/^__|^object_id$|^initialize$/
27
+ instance_methods(true).each { |m| undef_method m unless m[KEEPERS] }
28
+ private_instance_methods(true).each { |m| undef_method m unless m[KEEPERS] }
29
+ # :startdoc:
30
+
31
+ # Create a new proxy for the given _object_. If an optional _block_ is
32
+ # given it will be called before the proxied method. This _block_ will
33
+ # replace the +method_missing+ implementation
34
+ #
35
+ def initialize( object, &block )
36
+ Kernel.raise ArgumentError, "Cannot proxy nil" if nil.equal? object
37
+
38
+ @object = object
39
+ @leader = @object.is_a?(Class) ? "#{@object.name}." : "#{@object.class.name}#"
40
+ @logger = Logsly::Logging182.logger[object]
41
+
42
+ if block
43
+ eigenclass = class << self; self; end
44
+ eigenclass.__send__(:define_method, :method_missing, &block)
45
+ end
46
+ end
47
+
48
+ # All hail the magic of method missing. Here is where we are going to log
49
+ # the method call and then forward to the proxied object. The return value
50
+ # from the proxied objet method call is passed back.
51
+ #
52
+ def method_missing( name, *args, &block )
53
+ @logger << "#@leader#{name}(#{args.inspect[1..-2]})\n"
54
+ @object.send(name, *args, &block)
55
+ end
56
+
57
+ end # Proxy
58
+ end # Logsly::Logging182
59
+
@@ -0,0 +1,36 @@
1
+
2
+ if defined? ActiveSupport
3
+
4
+ module Logsly::Logging182
5
+
6
+ # Rails compatibility module.
7
+ #
8
+ # The ActiveSupport gem adds a few methods to the default Ruby logger, and
9
+ # some Rails extensions expect these methods to exist. Those methods are
10
+ # implemented in this module and included in the Logsly::Logging182::Logger class when
11
+ # the ActiveSupport gem is present.
12
+ #
13
+ module RailsCompat
14
+
15
+ # A no-op implementation of the +formatter+ method.
16
+ def formatter; end
17
+
18
+ # A no-op implementation of the +silence+ method. Setting of log levels
19
+ # should be done during the Logsly::Logging182 configuration. It is the author's
20
+ # opinion that overriding the log level programmatically is a logical
21
+ # error.
22
+ #
23
+ # Please see https://github.com/TwP/logging/issues/11 for a more detail
24
+ # discussion of the issue.
25
+ #
26
+ def silence( *args )
27
+ yield self
28
+ end
29
+
30
+ end # RailsCompat
31
+
32
+ Logger.send :include, RailsCompat
33
+
34
+ end # Logsly::Logging182
35
+ end # if defined?
36
+