logger 1.2.7.1

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 (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: []