logsly 1.2.0 → 1.3.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.
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
+