logger 1.2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/logger.rb +750 -0
  2. data/test/logger/test_logger.rb +504 -0
  3. metadata +50 -0
@@ -0,0 +1,750 @@
1
+ # logger.rb - simple logging utility
2
+ # Copyright (C) 2000-2003, 2005, 2008 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # Author:: NAKAMURA, Hiroshi <nakahiro@sarion.co.jp>
5
+ # Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair
6
+ # License::
7
+ # You can redistribute it and/or modify it under the same terms of Ruby's
8
+ # license; either the dual license version in 2003, or any later version.
9
+ # Revision:: $Id$
10
+ #
11
+ # See Logger for documentation.
12
+
13
+
14
+ require 'monitor'
15
+
16
+
17
+ # == Description
18
+ #
19
+ # The Logger class provides a simple but sophisticated logging utility that
20
+ # anyone can use because it's included in the Ruby 1.8.x standard library.
21
+ #
22
+ # The HOWTOs below give a code-based overview of Logger's usage, but the basic
23
+ # concept is as follows. You create a Logger object (output to a file or
24
+ # elsewhere), and use it to log messages. The messages will have varying
25
+ # levels (+info+, +error+, etc), reflecting their varying importance. The
26
+ # levels, and their meanings, are:
27
+ #
28
+ # +FATAL+:: an unhandleable error that results in a program crash
29
+ # +ERROR+:: a handleable error condition
30
+ # +WARN+:: a warning
31
+ # +INFO+:: generic (useful) information about system operation
32
+ # +DEBUG+:: low-level information for developers
33
+ #
34
+ # So each message has a level, and the Logger itself has a level, which acts
35
+ # as a filter, so you can control the amount of information emitted from the
36
+ # logger without having to remove actual messages.
37
+ #
38
+ # For instance, in a production system, you may have your logger(s) set to
39
+ # +INFO+ (or +WARN+ if you don't want the log files growing large with
40
+ # repetitive information). When you are developing it, though, you probably
41
+ # want to know about the program's internal state, and would set them to
42
+ # +DEBUG+.
43
+ #
44
+ # === Example
45
+ #
46
+ # A simple example demonstrates the above explanation:
47
+ #
48
+ # log = Logger.new(STDOUT)
49
+ # log.level = Logger::WARN
50
+ #
51
+ # log.debug("Created logger")
52
+ # log.info("Program started")
53
+ # log.warn("Nothing to do!")
54
+ #
55
+ # begin
56
+ # File.each_line(path) do |line|
57
+ # unless line =~ /^(\w+) = (.*)$/
58
+ # log.error("Line in wrong format: #{line}")
59
+ # end
60
+ # end
61
+ # rescue => err
62
+ # log.fatal("Caught exception; exiting")
63
+ # log.fatal(err)
64
+ # end
65
+ #
66
+ # Because the Logger's level is set to +WARN+, only the warning, error, and
67
+ # fatal messages are recorded. The debug and info messages are silently
68
+ # discarded.
69
+ #
70
+ # === Features
71
+ #
72
+ # There are several interesting features that Logger provides, like
73
+ # auto-rolling of log files, setting the format of log messages, and
74
+ # specifying a program name in conjunction with the message. The next section
75
+ # shows you how to achieve these things.
76
+ #
77
+ #
78
+ # == HOWTOs
79
+ #
80
+ # === How to create a logger
81
+ #
82
+ # The options below give you various choices, in more or less increasing
83
+ # complexity.
84
+ #
85
+ # 1. Create a logger which logs messages to STDERR/STDOUT.
86
+ #
87
+ # logger = Logger.new(STDERR)
88
+ # logger = Logger.new(STDOUT)
89
+ #
90
+ # 2. Create a logger for the file which has the specified name.
91
+ #
92
+ # logger = Logger.new('logfile.log')
93
+ #
94
+ # 3. Create a logger for the specified file.
95
+ #
96
+ # file = File.open('foo.log', File::WRONLY | File::APPEND)
97
+ # # To create new (and to remove old) logfile, add File::CREAT like;
98
+ # # file = open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
99
+ # logger = Logger.new(file)
100
+ #
101
+ # 4. Create a logger which ages logfile once it reaches a certain size. Leave
102
+ # 10 "old log files" and each file is about 1,024,000 bytes.
103
+ #
104
+ # logger = Logger.new('foo.log', 10, 1024000)
105
+ #
106
+ # 5. Create a logger which ages logfile daily/weekly/monthly.
107
+ #
108
+ # logger = Logger.new('foo.log', 'daily')
109
+ # logger = Logger.new('foo.log', 'weekly')
110
+ # logger = Logger.new('foo.log', 'monthly')
111
+ #
112
+ # === How to log a message
113
+ #
114
+ # Notice the different methods (+fatal+, +error+, +info+) being used to log
115
+ # messages of various levels. Other methods in this family are +warn+ and
116
+ # +debug+. +add+ is used below to log a message of an arbitrary (perhaps
117
+ # dynamic) level.
118
+ #
119
+ # 1. Message in block.
120
+ #
121
+ # logger.fatal { "Argument 'foo' not given." }
122
+ #
123
+ # 2. Message as a string.
124
+ #
125
+ # logger.error "Argument #{ @foo } mismatch."
126
+ #
127
+ # 3. With progname.
128
+ #
129
+ # logger.info('initialize') { "Initializing..." }
130
+ #
131
+ # 4. With severity.
132
+ #
133
+ # logger.add(Logger::FATAL) { 'Fatal error!' }
134
+ #
135
+ # === How to close a logger
136
+ #
137
+ # logger.close
138
+ #
139
+ # === Setting severity threshold
140
+ #
141
+ # 1. Original interface.
142
+ #
143
+ # logger.sev_threshold = Logger::WARN
144
+ #
145
+ # 2. Log4r (somewhat) compatible interface.
146
+ #
147
+ # logger.level = Logger::INFO
148
+ #
149
+ # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
150
+ #
151
+ #
152
+ # == Format
153
+ #
154
+ # Log messages are rendered in the output stream in a certain format by
155
+ # default. The default format and a sample are shown below:
156
+ #
157
+ # Log format:
158
+ # SeverityID, [Date Time mSec #pid] SeverityLabel -- ProgName: message
159
+ #
160
+ # Log sample:
161
+ # I, [Wed Mar 03 02:34:24 JST 1999 895701 #19074] INFO -- Main: info.
162
+ #
163
+ # You may change the date and time format in this manner:
164
+ #
165
+ # logger.datetime_format = "%Y-%m-%d %H:%M:%S"
166
+ # # e.g. "2004-01-03 00:54:26"
167
+ #
168
+ # You may change the overall format with Logger#formatter= method.
169
+ #
170
+ # logger.formatter = proc { |severity, datetime, progname, msg|
171
+ # "#{datetime}: #{msg}\n"
172
+ # }
173
+ # # e.g. "Thu Sep 22 08:51:08 GMT+9:00 2005: hello world"
174
+ #
175
+
176
+
177
+ class Logger
178
+ VERSION = "1.2.7.1"
179
+ id, name, rev = %w$Id$
180
+ if name
181
+ name = name.chomp(",v")
182
+ else
183
+ name = File.basename(__FILE__)
184
+ end
185
+ rev ||= "v#{VERSION}"
186
+ ProgName = "#{name}/#{rev}"
187
+
188
+ class Error < RuntimeError; end
189
+ class ShiftingError < Error; end # not used after 1.2.7. just for compat.
190
+
191
+ # Logging severity.
192
+ module Severity
193
+ DEBUG = 0
194
+ INFO = 1
195
+ WARN = 2
196
+ ERROR = 3
197
+ FATAL = 4
198
+ UNKNOWN = 5
199
+ end
200
+ include Severity
201
+
202
+ # Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
203
+ attr_accessor :level
204
+
205
+ # Logging program name.
206
+ attr_accessor :progname
207
+
208
+ # Logging date-time format (string passed to +strftime+).
209
+ def datetime_format=(datetime_format)
210
+ @default_formatter.datetime_format = datetime_format
211
+ end
212
+
213
+ def datetime_format
214
+ @default_formatter.datetime_format
215
+ end
216
+
217
+ # Logging formatter. formatter#call is invoked with 4 arguments; severity,
218
+ # time, progname and msg for each log. Bear in mind that time is a Time and
219
+ # msg is an Object that user passed and it could not be a String. It is
220
+ # expected to return a logdev#write-able Object. Default formatter is used
221
+ # when no formatter is set.
222
+ attr_accessor :formatter
223
+
224
+ alias sev_threshold level
225
+ alias sev_threshold= level=
226
+
227
+ # Returns +true+ iff the current severity level allows for the printing of
228
+ # +DEBUG+ messages.
229
+ def debug?; @level <= DEBUG; end
230
+
231
+ # Returns +true+ iff the current severity level allows for the printing of
232
+ # +INFO+ messages.
233
+ def info?; @level <= INFO; end
234
+
235
+ # Returns +true+ iff the current severity level allows for the printing of
236
+ # +WARN+ messages.
237
+ def warn?; @level <= WARN; end
238
+
239
+ # Returns +true+ iff the current severity level allows for the printing of
240
+ # +ERROR+ messages.
241
+ def error?; @level <= ERROR; end
242
+
243
+ # Returns +true+ iff the current severity level allows for the printing of
244
+ # +FATAL+ messages.
245
+ def fatal?; @level <= FATAL; end
246
+
247
+ #
248
+ # === Synopsis
249
+ #
250
+ # Logger.new(name, shift_age = 7, shift_size = 1048576)
251
+ # Logger.new(name, shift_age = 'weekly')
252
+ #
253
+ # === Args
254
+ #
255
+ # +logdev+::
256
+ # The log device. This is a filename (String) or IO object (typically
257
+ # +STDOUT+, +STDERR+, or an open file).
258
+ # +shift_age+::
259
+ # Number of old log files to keep, *or* frequency of rotation (+daily+,
260
+ # +weekly+ or +monthly+).
261
+ # +shift_size+::
262
+ # Maximum logfile size (only applies when +shift_age+ is a number).
263
+ #
264
+ # === Description
265
+ #
266
+ # Create an instance.
267
+ #
268
+ def initialize(logdev, shift_age = 0, shift_size = 1048576)
269
+ @progname = nil
270
+ @level = DEBUG
271
+ @default_formatter = Formatter.new
272
+ @formatter = nil
273
+ @logdev = nil
274
+ if logdev
275
+ @logdev = LogDevice.new(logdev, :shift_age => shift_age,
276
+ :shift_size => shift_size)
277
+ end
278
+ end
279
+
280
+ #
281
+ # === Synopsis
282
+ #
283
+ # Logger#add(severity, message = nil, progname = nil) { ... }
284
+ #
285
+ # === Args
286
+ #
287
+ # +severity+::
288
+ # Severity. Constants are defined in Logger namespace: +DEBUG+, +INFO+,
289
+ # +WARN+, +ERROR+, +FATAL+, or +UNKNOWN+.
290
+ # +message+::
291
+ # The log message. A String or Exception.
292
+ # +progname+::
293
+ # Program name string. Can be omitted. Treated as a message if no +message+ and
294
+ # +block+ are given.
295
+ # +block+::
296
+ # Can be omitted. Called to get a message string if +message+ is nil.
297
+ #
298
+ # === Return
299
+ #
300
+ # +true+ if successful, +false+ otherwise.
301
+ #
302
+ # When the given severity is not high enough (for this particular logger), log
303
+ # no message, and return +true+.
304
+ #
305
+ # === Description
306
+ #
307
+ # Log a message if the given severity is high enough. This is the generic
308
+ # logging method. Users will be more inclined to use #debug, #info, #warn,
309
+ # #error, and #fatal.
310
+ #
311
+ # <b>Message format</b>: +message+ can be any object, but it has to be
312
+ # converted to a String in order to log it. Generally, +inspect+ is used
313
+ # if the given object is not a String.
314
+ # A special case is an +Exception+ object, which will be printed in detail,
315
+ # including message, class, and backtrace. See #msg2str for the
316
+ # implementation if required.
317
+ #
318
+ # === Bugs
319
+ #
320
+ # * Logfile is not locked.
321
+ # * Append open does not need to lock file.
322
+ # * But on the OS which supports multi I/O, records possibly be mixed.
323
+ #
324
+ def add(severity, message = nil, progname = nil, &block)
325
+ severity ||= UNKNOWN
326
+ if @logdev.nil? or severity < @level
327
+ return true
328
+ end
329
+ progname ||= @progname
330
+ if message.nil?
331
+ if block_given?
332
+ message = yield
333
+ else
334
+ message = progname
335
+ progname = @progname
336
+ end
337
+ end
338
+ @logdev.write(
339
+ format_message(format_severity(severity), Time.now, progname, message))
340
+ true
341
+ end
342
+ alias log add
343
+
344
+ #
345
+ # Dump given message to the log device without any formatting. If no log
346
+ # device exists, return +nil+.
347
+ #
348
+ def <<(msg)
349
+ unless @logdev.nil?
350
+ @logdev.write(msg)
351
+ end
352
+ end
353
+
354
+ #
355
+ # Log a +DEBUG+ message.
356
+ #
357
+ # See #info for more information.
358
+ #
359
+ def debug(progname = nil, &block)
360
+ add(DEBUG, nil, progname, &block)
361
+ end
362
+
363
+ #
364
+ # Log an +INFO+ message.
365
+ #
366
+ # The message can come either from the +progname+ argument or the +block+. If
367
+ # both are provided, then the +block+ is used as the message, and +progname+
368
+ # is used as the program name.
369
+ #
370
+ # === Examples
371
+ #
372
+ # logger.info("MainApp") { "Received connection from #{ip}" }
373
+ # # ...
374
+ # logger.info "Waiting for input from user"
375
+ # # ...
376
+ # logger.info { "User typed #{input}" }
377
+ #
378
+ # You'll probably stick to the second form above, unless you want to provide a
379
+ # program name (which you can do with <tt>Logger#progname=</tt> as well).
380
+ #
381
+ # === Return
382
+ #
383
+ # See #add.
384
+ #
385
+ def info(progname = nil, &block)
386
+ add(INFO, nil, progname, &block)
387
+ end
388
+
389
+ #
390
+ # Log a +WARN+ message.
391
+ #
392
+ # See #info for more information.
393
+ #
394
+ def warn(progname = nil, &block)
395
+ add(WARN, nil, progname, &block)
396
+ end
397
+
398
+ #
399
+ # Log an +ERROR+ message.
400
+ #
401
+ # See #info for more information.
402
+ #
403
+ def error(progname = nil, &block)
404
+ add(ERROR, nil, progname, &block)
405
+ end
406
+
407
+ #
408
+ # Log a +FATAL+ message.
409
+ #
410
+ # See #info for more information.
411
+ #
412
+ def fatal(progname = nil, &block)
413
+ add(FATAL, nil, progname, &block)
414
+ end
415
+
416
+ #
417
+ # Log an +UNKNOWN+ message. This will be printed no matter what the logger
418
+ # level.
419
+ #
420
+ # See #info for more information.
421
+ #
422
+ def unknown(progname = nil, &block)
423
+ add(UNKNOWN, nil, progname, &block)
424
+ end
425
+
426
+ #
427
+ # Close the logging device.
428
+ #
429
+ def close
430
+ @logdev.close if @logdev
431
+ end
432
+
433
+ private
434
+
435
+ # Severity label for logging. (max 5 char)
436
+ SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY)
437
+
438
+ def format_severity(severity)
439
+ SEV_LABEL[severity] || 'ANY'
440
+ end
441
+
442
+ def format_message(severity, datetime, progname, msg)
443
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
444
+ end
445
+
446
+
447
+ class Formatter
448
+ Format = "%s, [%s#%d] %5s -- %s: %s\n"
449
+
450
+ attr_accessor :datetime_format
451
+
452
+ def initialize
453
+ @datetime_format = nil
454
+ end
455
+
456
+ def call(severity, time, progname, msg)
457
+ Format % [severity[0..0], format_datetime(time), $$, severity, progname,
458
+ msg2str(msg)]
459
+ end
460
+
461
+ private
462
+
463
+ def format_datetime(time)
464
+ if @datetime_format.nil?
465
+ time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
466
+ else
467
+ time.strftime(@datetime_format)
468
+ end
469
+ end
470
+
471
+ def msg2str(msg)
472
+ case msg
473
+ when ::String
474
+ msg
475
+ when ::Exception
476
+ "#{ msg.message } (#{ msg.class })\n" <<
477
+ (msg.backtrace || []).join("\n")
478
+ else
479
+ msg.inspect
480
+ end
481
+ end
482
+ end
483
+
484
+
485
+ class LogDevice
486
+ attr_reader :dev
487
+ attr_reader :filename
488
+
489
+ class LogDeviceMutex
490
+ include MonitorMixin
491
+ end
492
+
493
+ def initialize(log = nil, opt = {})
494
+ @dev = @filename = @shift_age = @shift_size = nil
495
+ @mutex = LogDeviceMutex.new
496
+ if log.respond_to?(:write) and log.respond_to?(:close)
497
+ @dev = log
498
+ else
499
+ @dev = open_logfile(log)
500
+ @dev.sync = true
501
+ @filename = log
502
+ @shift_age = opt[:shift_age] || 7
503
+ @shift_size = opt[:shift_size] || 1048576
504
+ end
505
+ end
506
+
507
+ def write(message)
508
+ begin
509
+ @mutex.synchronize do
510
+ if @shift_age and @dev.respond_to?(:stat)
511
+ begin
512
+ check_shift_log
513
+ rescue
514
+ warn("log shifting failed. #{$!}")
515
+ end
516
+ end
517
+ begin
518
+ @dev.write(message)
519
+ rescue
520
+ warn("log writing failed. #{$!}")
521
+ end
522
+ end
523
+ rescue Exception => ignored
524
+ warn("log writing failed. #{ignored}")
525
+ end
526
+ end
527
+
528
+ def close
529
+ begin
530
+ @mutex.synchronize do
531
+ @dev.close rescue nil
532
+ end
533
+ rescue Exception => ignored
534
+ @dev.close rescue nil
535
+ end
536
+ end
537
+
538
+ private
539
+
540
+ def open_logfile(filename)
541
+ if (FileTest.exist?(filename))
542
+ open(filename, (File::WRONLY | File::APPEND))
543
+ else
544
+ create_logfile(filename)
545
+ end
546
+ end
547
+
548
+ def create_logfile(filename)
549
+ logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
550
+ logdev.sync = true
551
+ add_log_header(logdev)
552
+ logdev
553
+ end
554
+
555
+ def add_log_header(file)
556
+ file.write(
557
+ "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
558
+ )
559
+ end
560
+
561
+ SiD = 24 * 60 * 60
562
+
563
+ def check_shift_log
564
+ if @shift_age.is_a?(Integer)
565
+ # Note: always returns false if '0'.
566
+ if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
567
+ shift_log_age
568
+ end
569
+ else
570
+ now = Time.now
571
+ period_end = previous_period_end(now)
572
+ if @dev.stat.mtime <= period_end
573
+ shift_log_period(period_end)
574
+ end
575
+ end
576
+ end
577
+
578
+ def shift_log_age
579
+ (@shift_age-3).downto(0) do |i|
580
+ if FileTest.exist?("#{@filename}.#{i}")
581
+ File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
582
+ end
583
+ end
584
+ @dev.close rescue nil
585
+ File.rename("#{@filename}", "#{@filename}.0")
586
+ @dev = create_logfile(@filename)
587
+ return true
588
+ end
589
+
590
+ def shift_log_period(period_end)
591
+ postfix = period_end.strftime("%Y%m%d") # YYYYMMDD
592
+ age_file = "#{@filename}.#{postfix}"
593
+ if FileTest.exist?(age_file)
594
+ # try to avoid filename crash caused by Timestamp change.
595
+ idx = 0
596
+ # .99 can be overriden; avoid too much file search with 'loop do'
597
+ while idx < 100
598
+ idx += 1
599
+ age_file = "#{@filename}.#{postfix}.#{idx}"
600
+ break unless FileTest.exist?(age_file)
601
+ end
602
+ end
603
+ @dev.close rescue nil
604
+ File.rename("#{@filename}", age_file)
605
+ @dev = create_logfile(@filename)
606
+ return true
607
+ end
608
+
609
+ def previous_period_end(now)
610
+ case @shift_age
611
+ when /^daily$/
612
+ eod(now - 1 * SiD)
613
+ when /^weekly$/
614
+ eod(now - ((now.wday + 1) * SiD))
615
+ when /^monthly$/
616
+ eod(now - now.mday * SiD)
617
+ else
618
+ now
619
+ end
620
+ end
621
+
622
+ def eod(t)
623
+ Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
624
+ end
625
+ end
626
+
627
+
628
+ #
629
+ # == Description
630
+ #
631
+ # Application -- Add logging support to your application.
632
+ #
633
+ # == Usage
634
+ #
635
+ # 1. Define your application class as a sub-class of this class.
636
+ # 2. Override 'run' method in your class to do many things.
637
+ # 3. Instantiate it and invoke 'start'.
638
+ #
639
+ # == Example
640
+ #
641
+ # class FooApp < Application
642
+ # def initialize(foo_app, application_specific, arguments)
643
+ # super('FooApp') # Name of the application.
644
+ # end
645
+ #
646
+ # def run
647
+ # ...
648
+ # log(WARN, 'warning', 'my_method1')
649
+ # ...
650
+ # @log.error('my_method2') { 'Error!' }
651
+ # ...
652
+ # end
653
+ # end
654
+ #
655
+ # status = FooApp.new(....).start
656
+ #
657
+ class Application
658
+ include Logger::Severity
659
+
660
+ # Name of the application given at initialize.
661
+ attr_reader :appname
662
+
663
+ #
664
+ # == Synopsis
665
+ #
666
+ # Application.new(appname = '')
667
+ #
668
+ # == Args
669
+ #
670
+ # +appname+:: Name of the application.
671
+ #
672
+ # == Description
673
+ #
674
+ # Create an instance. Log device is +STDERR+ by default. This can be
675
+ # changed with #set_log.
676
+ #
677
+ def initialize(appname = nil)
678
+ @appname = appname
679
+ @log = Logger.new(STDERR)
680
+ @log.progname = @appname
681
+ @level = @log.level
682
+ end
683
+
684
+ #
685
+ # Start the application. Return the status code.
686
+ #
687
+ def start
688
+ status = -1
689
+ begin
690
+ log(INFO, "Start of #{ @appname }.")
691
+ status = run
692
+ rescue
693
+ log(FATAL, "Detected an exception. Stopping ... #{$!} (#{$!.class})\n" << $@.join("\n"))
694
+ ensure
695
+ log(INFO, "End of #{ @appname }. (status: #{ status.to_s })")
696
+ end
697
+ status
698
+ end
699
+
700
+ # Logger for this application. See the class Logger for an explanation.
701
+ def logger
702
+ @log
703
+ end
704
+
705
+ #
706
+ # Sets the logger for this application. See the class Logger for an explanation.
707
+ #
708
+ def logger=(logger)
709
+ @log = logger
710
+ @log.progname = @appname
711
+ @log.level = @level
712
+ end
713
+
714
+ #
715
+ # Sets the log device for this application. See <tt>Logger.new</tt> for an explanation
716
+ # of the arguments.
717
+ #
718
+ def set_log(logdev, shift_age = 0, shift_size = 1024000)
719
+ @log = Logger.new(logdev, shift_age, shift_size)
720
+ @log.progname = @appname
721
+ @log.level = @level
722
+ end
723
+
724
+ def log=(logdev)
725
+ set_log(logdev)
726
+ end
727
+
728
+ #
729
+ # Set the logging threshold, just like <tt>Logger#level=</tt>.
730
+ #
731
+ def level=(level)
732
+ @level = level
733
+ @log.level = @level
734
+ end
735
+
736
+ #
737
+ # See Logger#add. This application's +appname+ is used.
738
+ #
739
+ def log(severity, message = nil, &block)
740
+ @log.add(severity, message, @appname, &block) if @log
741
+ end
742
+
743
+ private
744
+
745
+ def run
746
+ # TODO: should be an NotImplementedError
747
+ raise RuntimeError.new('Method run must be defined in the derived class.')
748
+ end
749
+ end
750
+ end
@@ -0,0 +1,504 @@
1
+ require 'test/unit'
2
+ require 'logger'
3
+ require 'tempfile'
4
+
5
+
6
+ class TestLoggerSeverity < Test::Unit::TestCase
7
+ def test_enum
8
+ logger_levels = Logger.constants
9
+ levels = ["WARN", "UNKNOWN", "INFO", "FATAL", "DEBUG", "ERROR"]
10
+ Logger::Severity.constants.each do |level|
11
+ assert(levels.include?(level.to_s))
12
+ assert(logger_levels.include?(level))
13
+ end
14
+ assert_equal(levels.size, Logger::Severity.constants.size)
15
+ end
16
+ end
17
+
18
+
19
+ class TestLogger < Test::Unit::TestCase
20
+ include Logger::Severity
21
+
22
+ def setup
23
+ @logger = Logger.new(nil)
24
+ @filename = __FILE__ + ".#{$$}"
25
+ end
26
+
27
+ def teardown
28
+ unless $DEBUG
29
+ File.unlink(@filename) if File.exist?(@filename)
30
+ end
31
+ end
32
+
33
+ class Log
34
+ attr_reader :label, :datetime, :pid, :severity, :progname, :msg
35
+ def initialize(line)
36
+ /\A(\w+), \[([^#]*)#(\d+)\]\s+(\w+) -- (\w*): ([\x0-\xff]*)/ =~ line
37
+ @label, @datetime, @pid, @severity, @progname, @msg = $1, $2, $3, $4, $5, $6
38
+ end
39
+ end
40
+
41
+ def log_add(logger, severity, msg, progname = nil, &block)
42
+ log(logger, :add, severity, msg, progname, &block)
43
+ end
44
+
45
+ def log(logger, msg_id, *arg, &block)
46
+ Log.new(log_raw(logger, msg_id, *arg, &block))
47
+ end
48
+
49
+ def log_raw(logger, msg_id, *arg, &block)
50
+ logdev = Tempfile.new(File.basename(__FILE__) + '.log')
51
+ logger.instance_eval { @logdev = Logger::LogDevice.new(logdev) }
52
+ logger.__send__(msg_id, *arg, &block)
53
+ logdev.open
54
+ msg = logdev.read
55
+ logdev.close
56
+ msg
57
+ end
58
+
59
+ def test_level
60
+ @logger.level = UNKNOWN
61
+ assert_equal(UNKNOWN, @logger.level)
62
+ @logger.level = INFO
63
+ assert_equal(INFO, @logger.level)
64
+ @logger.sev_threshold = ERROR
65
+ assert_equal(ERROR, @logger.sev_threshold)
66
+ @logger.sev_threshold = WARN
67
+ assert_equal(WARN, @logger.sev_threshold)
68
+ assert_equal(WARN, @logger.level)
69
+
70
+ @logger.level = DEBUG
71
+ assert(@logger.debug?)
72
+ assert(@logger.info?)
73
+ @logger.level = INFO
74
+ assert(!@logger.debug?)
75
+ assert(@logger.info?)
76
+ assert(@logger.warn?)
77
+ @logger.level = WARN
78
+ assert(!@logger.info?)
79
+ assert(@logger.warn?)
80
+ assert(@logger.error?)
81
+ @logger.level = ERROR
82
+ assert(!@logger.warn?)
83
+ assert(@logger.error?)
84
+ assert(@logger.fatal?)
85
+ @logger.level = FATAL
86
+ assert(!@logger.error?)
87
+ assert(@logger.fatal?)
88
+ @logger.level = UNKNOWN
89
+ assert(!@logger.error?)
90
+ assert(!@logger.fatal?)
91
+ end
92
+
93
+ def test_progname
94
+ assert_nil(@logger.progname)
95
+ @logger.progname = "name"
96
+ assert_equal("name", @logger.progname)
97
+ end
98
+
99
+ def test_datetime_format
100
+ dummy = STDERR
101
+ logger = Logger.new(dummy)
102
+ log = log_add(logger, INFO, "foo")
103
+ assert_match(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\s*\d+ $/, log.datetime)
104
+ logger.datetime_format = "%d%b%Y@%H:%M:%S"
105
+ log = log_add(logger, INFO, "foo")
106
+ assert_match(/^\d\d\w\w\w\d\d\d\d@\d\d:\d\d:\d\d$/, log.datetime)
107
+ logger.datetime_format = ""
108
+ log = log_add(logger, INFO, "foo")
109
+ assert_match(/^$/, log.datetime)
110
+ end
111
+
112
+ def test_formatter
113
+ dummy = STDERR
114
+ logger = Logger.new(dummy)
115
+ # default
116
+ log = log(logger, :info, "foo")
117
+ assert_equal("foo\n", log.msg)
118
+ # config
119
+ logger.formatter = proc { |severity, timestamp, progname, msg|
120
+ "#{severity}:#{msg}\n\n"
121
+ }
122
+ line = log_raw(logger, :info, "foo")
123
+ assert_equal("INFO:foo\n\n", line)
124
+ # recover
125
+ logger.formatter = nil
126
+ log = log(logger, :info, "foo")
127
+ assert_equal("foo\n", log.msg)
128
+ # again
129
+ o = Object.new
130
+ def o.call(severity, timestamp, progname, msg)
131
+ "<<#{severity}-#{msg}>>\n"
132
+ end
133
+ logger.formatter = o
134
+ line = log_raw(logger, :info, "foo")
135
+ assert_equal("<<INFO-foo>>\n", line)
136
+ end
137
+
138
+ def test_initialize
139
+ logger = Logger.new(STDERR)
140
+ assert_nil(logger.progname)
141
+ assert_equal(DEBUG, logger.level)
142
+ assert_nil(logger.datetime_format)
143
+ end
144
+
145
+ def test_add
146
+ logger = Logger.new(nil)
147
+ logger.progname = "my_progname"
148
+ assert(logger.add(INFO))
149
+ log = log_add(logger, nil, "msg")
150
+ assert_equal("ANY", log.severity)
151
+ assert_equal("my_progname", log.progname)
152
+ logger.level = WARN
153
+ assert(logger.log(INFO))
154
+ assert_nil(log_add(logger, INFO, "msg").msg)
155
+ log = log_add(logger, WARN, nil) { "msg" }
156
+ assert_equal("msg\n", log.msg)
157
+ log = log_add(logger, WARN, "") { "msg" }
158
+ assert_equal("\n", log.msg)
159
+ assert_equal("my_progname", log.progname)
160
+ log = log_add(logger, WARN, nil, "progname?")
161
+ assert_equal("progname?\n", log.msg)
162
+ assert_equal("my_progname", log.progname)
163
+ end
164
+
165
+ def test_level_log
166
+ logger = Logger.new(nil)
167
+ logger.progname = "my_progname"
168
+ log = log(logger, :debug, "custom_progname") { "msg" }
169
+ assert_equal("msg\n", log.msg)
170
+ assert_equal("custom_progname", log.progname)
171
+ assert_equal("DEBUG", log.severity)
172
+ assert_equal("D", log.label)
173
+ #
174
+ log = log(logger, :debug) { "msg_block" }
175
+ assert_equal("msg_block\n", log.msg)
176
+ assert_equal("my_progname", log.progname)
177
+ log = log(logger, :debug, "msg_inline")
178
+ assert_equal("msg_inline\n", log.msg)
179
+ assert_equal("my_progname", log.progname)
180
+ #
181
+ log = log(logger, :info, "custom_progname") { "msg" }
182
+ assert_equal("msg\n", log.msg)
183
+ assert_equal("custom_progname", log.progname)
184
+ assert_equal("INFO", log.severity)
185
+ assert_equal("I", log.label)
186
+ #
187
+ log = log(logger, :warn, "custom_progname") { "msg" }
188
+ assert_equal("msg\n", log.msg)
189
+ assert_equal("custom_progname", log.progname)
190
+ assert_equal("WARN", log.severity)
191
+ assert_equal("W", log.label)
192
+ #
193
+ log = log(logger, :error, "custom_progname") { "msg" }
194
+ assert_equal("msg\n", log.msg)
195
+ assert_equal("custom_progname", log.progname)
196
+ assert_equal("ERROR", log.severity)
197
+ assert_equal("E", log.label)
198
+ #
199
+ log = log(logger, :fatal, "custom_progname") { "msg" }
200
+ assert_equal("msg\n", log.msg)
201
+ assert_equal("custom_progname", log.progname)
202
+ assert_equal("FATAL", log.severity)
203
+ assert_equal("F", log.label)
204
+ #
205
+ log = log(logger, :unknown, "custom_progname") { "msg" }
206
+ assert_equal("msg\n", log.msg)
207
+ assert_equal("custom_progname", log.progname)
208
+ assert_equal("ANY", log.severity)
209
+ assert_equal("A", log.label)
210
+ end
211
+
212
+ def test_close
213
+ r, w = IO.pipe
214
+ assert(!w.closed?)
215
+ logger = Logger.new(w)
216
+ logger.close
217
+ assert(w.closed?)
218
+ r.close
219
+ end
220
+
221
+ class MyError < StandardError
222
+ end
223
+
224
+ class MyMsg
225
+ def inspect
226
+ "my_msg"
227
+ end
228
+ end
229
+
230
+ def test_format
231
+ logger = Logger.new(nil)
232
+ log = log_add(logger, INFO, "msg\n")
233
+ assert_equal("msg\n\n", log.msg)
234
+ begin
235
+ raise MyError.new("excn")
236
+ rescue MyError => e
237
+ log = log_add(logger, INFO, e)
238
+ assert_match(/^excn \(TestLogger::MyError\)/, log.msg)
239
+ # expects backtrace is dumped across multi lines. 10 might be changed.
240
+ assert(log.msg.split(/\n/).size >= 10)
241
+ end
242
+ log = log_add(logger, INFO, MyMsg.new)
243
+ assert_equal("my_msg\n", log.msg)
244
+ end
245
+
246
+ def test_lshift
247
+ r, w = IO.pipe
248
+ logger = Logger.new(w)
249
+ logger << "msg"
250
+ read_ready, = IO.select([r], nil, nil, 0.1)
251
+ w.close
252
+ msg = r.read
253
+ r.close
254
+ assert_equal("msg", msg)
255
+ #
256
+ r, w = IO.pipe
257
+ logger = Logger.new(w)
258
+ logger << "msg2\n\n"
259
+ read_ready, = IO.select([r], nil, nil, 0.1)
260
+ w.close
261
+ msg = r.read
262
+ r.close
263
+ assert_equal("msg2\n\n", msg)
264
+ end
265
+ end
266
+
267
+ class TestLogDevice < Test::Unit::TestCase
268
+ class LogExcnRaiser
269
+ def write(*arg)
270
+ raise 'disk is full'
271
+ end
272
+
273
+ def close
274
+ end
275
+
276
+ def stat
277
+ Object.new
278
+ end
279
+ end
280
+
281
+ def setup
282
+ @filename = __FILE__ + ".#{$$}"
283
+ end
284
+
285
+ def teardown
286
+ unless $DEBUG
287
+ File.unlink(@filename) if File.exist?(@filename)
288
+ end
289
+ end
290
+
291
+ def d(log, opt = {})
292
+ Logger::LogDevice.new(log, opt)
293
+ end
294
+
295
+ def test_initialize
296
+ logdev = d(STDERR)
297
+ assert_equal(STDERR, logdev.dev)
298
+ assert_nil(logdev.filename)
299
+ assert_raises(TypeError) do
300
+ d(nil)
301
+ end
302
+ #
303
+ logdev = d(@filename)
304
+ begin
305
+ assert(File.exist?(@filename))
306
+ assert(logdev.dev.sync)
307
+ assert_equal(@filename, logdev.filename)
308
+ logdev.write('hello')
309
+ ensure
310
+ logdev.close
311
+ end
312
+ # create logfile whitch is already exist.
313
+ logdev = d(@filename)
314
+ logdev.write('world')
315
+ logfile = File.read(@filename)
316
+ assert_equal(2, logfile.split(/\n/).size)
317
+ assert_match(/^helloworld$/, logfile)
318
+ end
319
+
320
+ def test_write
321
+ r, w = IO.pipe
322
+ logdev = d(w)
323
+ logdev.write("msg2\n\n")
324
+ read_ready, = IO.select([r], nil, nil, 0.1)
325
+ w.close
326
+ msg = r.read
327
+ r.close
328
+ assert_equal("msg2\n\n", msg)
329
+ #
330
+ logdev = d(LogExcnRaiser.new)
331
+ begin
332
+ assert_nothing_raised do
333
+ logdev.write('hello')
334
+ end
335
+ ensure
336
+ logdev.close
337
+ end
338
+ end
339
+
340
+ def test_close
341
+ r, w = IO.pipe
342
+ logdev = d(w)
343
+ logdev.write("msg2\n\n")
344
+ read_ready, = IO.select([r], nil, nil, 0.1)
345
+ assert(!w.closed?)
346
+ logdev.close
347
+ assert(w.closed?)
348
+ r.close
349
+ end
350
+
351
+ def test_shifting_size
352
+ logfile = File.basename(__FILE__) + '_1.log'
353
+ logfile0 = logfile + '.0'
354
+ logfile1 = logfile + '.1'
355
+ logfile2 = logfile + '.2'
356
+ logfile3 = logfile + '.3'
357
+ File.unlink(logfile) if File.exist?(logfile)
358
+ File.unlink(logfile0) if File.exist?(logfile0)
359
+ File.unlink(logfile1) if File.exist?(logfile1)
360
+ File.unlink(logfile2) if File.exist?(logfile2)
361
+ logger = Logger.new(logfile, 4, 100)
362
+ logger.error("0" * 15)
363
+ assert(File.exist?(logfile))
364
+ assert(!File.exist?(logfile0))
365
+ logger.error("0" * 15)
366
+ assert(File.exist?(logfile0))
367
+ assert(!File.exist?(logfile1))
368
+ logger.error("0" * 15)
369
+ assert(File.exist?(logfile1))
370
+ assert(!File.exist?(logfile2))
371
+ logger.error("0" * 15)
372
+ assert(File.exist?(logfile2))
373
+ assert(!File.exist?(logfile3))
374
+ logger.error("0" * 15)
375
+ assert(!File.exist?(logfile3))
376
+ logger.error("0" * 15)
377
+ assert(!File.exist?(logfile3))
378
+ logger.close
379
+ File.unlink(logfile)
380
+ File.unlink(logfile0)
381
+ File.unlink(logfile1)
382
+ File.unlink(logfile2)
383
+
384
+ logfile = File.basename(__FILE__) + '_2.log'
385
+ logfile0 = logfile + '.0'
386
+ logfile1 = logfile + '.1'
387
+ logfile2 = logfile + '.2'
388
+ logfile3 = logfile + '.3'
389
+ logger = Logger.new(logfile, 4, 150)
390
+ logger.error("0" * 15)
391
+ assert(File.exist?(logfile))
392
+ assert(!File.exist?(logfile0))
393
+ logger.error("0" * 15)
394
+ assert(!File.exist?(logfile0))
395
+ logger.error("0" * 15)
396
+ assert(File.exist?(logfile0))
397
+ assert(!File.exist?(logfile1))
398
+ logger.error("0" * 15)
399
+ assert(!File.exist?(logfile1))
400
+ logger.error("0" * 15)
401
+ assert(File.exist?(logfile1))
402
+ assert(!File.exist?(logfile2))
403
+ logger.error("0" * 15)
404
+ assert(!File.exist?(logfile2))
405
+ logger.error("0" * 15)
406
+ assert(File.exist?(logfile2))
407
+ assert(!File.exist?(logfile3))
408
+ logger.error("0" * 15)
409
+ assert(!File.exist?(logfile3))
410
+ logger.error("0" * 15)
411
+ assert(!File.exist?(logfile3))
412
+ logger.error("0" * 15)
413
+ assert(!File.exist?(logfile3))
414
+ logger.close
415
+ File.unlink(logfile)
416
+ File.unlink(logfile0)
417
+ File.unlink(logfile1)
418
+ File.unlink(logfile2)
419
+ end
420
+
421
+ def test_shifting_age_variants
422
+ logger = Logger.new(@filename, 'daily')
423
+ logger.info('daily')
424
+ logger.close
425
+ logger = Logger.new(@filename, 'weekly')
426
+ logger.info('weekly')
427
+ logger.close
428
+ logger = Logger.new(@filename, 'monthly')
429
+ logger.info('monthly')
430
+ logger.close
431
+ end
432
+
433
+ def test_shifting_age
434
+ # shift_age other than 'daily', 'weekly', and 'monthly' means 'everytime'
435
+ yyyymmdd = Time.now.strftime("%Y%m%d")
436
+ filename1 = @filename + ".#{yyyymmdd}"
437
+ filename2 = @filename + ".#{yyyymmdd}.1"
438
+ filename3 = @filename + ".#{yyyymmdd}.2"
439
+ begin
440
+ logger = Logger.new(@filename, 'now')
441
+ assert(File.exist?(@filename))
442
+ assert(!File.exist?(filename1))
443
+ assert(!File.exist?(filename2))
444
+ assert(!File.exist?(filename3))
445
+ logger.info("0" * 15)
446
+ assert(File.exist?(@filename))
447
+ assert(File.exist?(filename1))
448
+ assert(!File.exist?(filename2))
449
+ assert(!File.exist?(filename3))
450
+ logger.warn("0" * 15)
451
+ assert(File.exist?(@filename))
452
+ assert(File.exist?(filename1))
453
+ assert(File.exist?(filename2))
454
+ assert(!File.exist?(filename3))
455
+ logger.error("0" * 15)
456
+ assert(File.exist?(@filename))
457
+ assert(File.exist?(filename1))
458
+ assert(File.exist?(filename2))
459
+ assert(File.exist?(filename3))
460
+ ensure
461
+ [filename1, filename2, filename3].each do |filename|
462
+ File.unlink(filename) if File.exist?(filename)
463
+ end
464
+ end
465
+ end
466
+ end
467
+
468
+
469
+ class TestLoggerApplication < Test::Unit::TestCase
470
+ def setup
471
+ @app = Logger::Application.new('appname')
472
+ @filename = __FILE__ + ".#{$$}"
473
+ end
474
+
475
+ def teardown
476
+ unless $DEBUG
477
+ File.unlink(@filename) if File.exist?(@filename)
478
+ end
479
+ end
480
+
481
+ def test_initialize
482
+ app = Logger::Application.new('appname')
483
+ assert_equal('appname', app.appname)
484
+ end
485
+
486
+ def test_start
487
+ @app.set_log(@filename)
488
+ @app.level = Logger::UNKNOWN
489
+ @app.start # logs FATAL log
490
+ assert_equal(1, File.read(@filename).split(/\n/).size)
491
+ end
492
+
493
+ def test_logger
494
+ @app.level = Logger::WARN
495
+ @app.set_log(@filename)
496
+ assert_equal(Logger::WARN, @app.logger.level)
497
+ @app.logger = logger = Logger.new(STDOUT)
498
+ assert_equal(logger, @app.logger)
499
+ assert_equal(Logger::WARN, @app.logger.level)
500
+ @app.log = @filename
501
+ assert(logger != @app.logger)
502
+ assert_equal(Logger::WARN, @app.logger.level)
503
+ end
504
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logger
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.7.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - NAKAMURA, Hiroshi
9
+ - SHIBATA Hiroshi
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2019-01-11 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: Provides a simple logging utility for outputting messages.
16
+ email:
17
+ - nahi@ruby-lang.org
18
+ - hsbt@ruby-lang.org
19
+ executables: []
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - lib/logger.rb
24
+ - test/logger/test_logger.rb
25
+ homepage: https://github.com/ruby/logger
26
+ licenses:
27
+ - BSD-2-Clause
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 1.8.23.2
47
+ signing_key:
48
+ specification_version: 3
49
+ summary: Provides a simple logging utility for outputting messages.
50
+ test_files: []