diff --git a/lib/logger.rb b/lib/logger.rb
index 7d55f62..62ab20d 100644
--- a/lib/logger.rb
+++ b/lib/logger.rb
@@ -19,216 +19,352 @@
require_relative 'logger/severity'
require_relative 'logger/errors'
-# == Description
+# \Class \Logger provides a simple but sophisticated logging utility that
+# you can use to create one or more
+# {event logs}[https://en.wikipedia.org/wiki/Logging_(software)#Event_logs]
+# for your program.
+# Each such log contains a chronological sequence of entries
+# that provides a record of the program's activities.
#
-# The Logger class provides a simple but sophisticated logging utility that
-# you can use to output messages.
+# == About the Examples
#
-# The messages have associated levels, such as +INFO+ or +ERROR+ that indicate
-# their importance. You can then give the Logger a level, and only messages
-# at that level or higher will be printed.
+# All examples on this page assume that \Logger has been required:
#
-# The levels are:
+# require 'logger'
#
-# +UNKNOWN+:: An unknown message that should always be logged.
-# +FATAL+:: An unhandleable error that results in a program crash.
-# +ERROR+:: A handleable error condition.
-# +WARN+:: A warning.
-# +INFO+:: Generic (useful) information about system operation.
-# +DEBUG+:: Low-level information for developers.
+# == Synopsis
#
-# For instance, in a production system, you may have your Logger set to
-# +INFO+ or even +WARN+.
-# When you are developing the system, however, you probably
-# want to know about the program's internal state, and would set the Logger to
-# +DEBUG+.
+# Create a log with Logger.new:
#
-# *Note*: Logger does not escape or sanitize any messages passed to it.
-# Developers should be aware of when potentially malicious data (user-input)
-# is passed to Logger, and manually escape the untrusted data:
+# # Single log file.
+# logger = Logger.new('t.log')
+# # Size-based rotated log: 3 10-megabyte files.
+# logger = Logger.new('t.log', 3, 10485760)
+# # Period-based rotated log: daily (also allowed: 'weekly', 'monthly').
+# logger = Logger.new('t.log', 'daily')
#
-# logger.info("User-input: #{input.dump}")
-# logger.info("User-input: %p" % input)
+# Add entries (level, message) with Logger#add:
#
-# You can use #formatter= for escaping all data.
+# logger.add(Logger::DEBUG, 'Maximal debugging info')
+# logger.add(Logger::INFO, 'Non-error information')
+# logger.add(Logger::WARN, 'Non-error warning')
+# logger.add(Logger::ERROR, 'Non-fatal error')
+# logger.add(Logger::FATAL, 'Fatal error')
+# logger.add(Logger::UNKNOWN, 'Most severe')
#
-# original_formatter = Logger::Formatter.new
-# logger.formatter = proc { |severity, datetime, progname, msg|
-# original_formatter.call(severity, datetime, progname, msg.dump)
-# }
-# logger.info(input)
+# There are also these shorthand methods:
#
-# === Example
+# logger.debug('Maximal debugging info')
+# logger.info('Non-error information')
+# logger.warn('Non-error warning')
+# logger.error('Non-fatal error')
+# logger.fatal('Fatal error')
+# logger.unknown('Most severe')
#
-# This creates a Logger that outputs to the standard output stream, with a
-# level of +WARN+:
+# For each method in the two groups immediately above,
+# you can omit the string message and provide a block instead.
+# Doing so can have two benefits:
#
-# require 'logger'
+# - Context: the block can evaluate the entire program context
+# and create a context-dependent message.
+# - Performance: the block is not evaluated unless the log level
+# permits the entry actually to be written:
+#
+# logger.error { my_slow_message_generator }
+#
+# Contrast this with the string form, where the string is
+# always evaluated, regardless of the log level:
+#
+# logger.error("#{my_slow_message_generator}")
+#
+# Close the log with Logger#close:
+#
+# logger.close
+#
+# == Log Stream
+#
+# When you create a \Logger instance, you specify an IO stream
+# for the logger's output, usually either an open File object
+# or an IO object such as $stdout or $stderr.
#
-# logger = Logger.new(STDOUT)
-# logger.level = Logger::WARN
+# == Entries
#
-# logger.debug("Created logger")
-# logger.info("Program started")
-# logger.warn("Nothing to do!")
+# When you call instance method #add (or its alias #log),
+# an entry may (or may not) be written to the log;
+# see {Log Level}[rdoc-ref:Logger@Log+Level]
#
-# path = "a_non_existent_file"
+# An entry always has:
#
-# begin
-# File.foreach(path) do |line|
-# unless line =~ /^(\w+) = (.*)$/
-# logger.error("Line in wrong format: #{line.chomp}")
-# end
-# end
-# rescue => err
-# logger.fatal("Caught exception; exiting")
-# logger.fatal(err)
-# end
+# - A severity (the required argument to #add).
+# - An automatically created timestamp.
#
-# Because the Logger's level is set to +WARN+, only the warning, error, and
-# fatal messages are recorded. The debug and info messages are silently
-# discarded.
+# And may also have:
#
-# === Features
+# - A message.
+# - A program name.
#
-# There are several interesting features that Logger provides, like
-# auto-rolling of log files, setting the format of log messages, and
-# specifying a program name in conjunction with the message. The next section
-# shows you how to achieve these things.
+# Example:
#
+# logger = Logger.new($stdout)
+# logger.add(Logger::INFO, 'msg', 'progname')
+# # => I, [2022-05-07T17:21:46.536234 #20536] INFO -- progname: msg
#
-# == HOWTOs
+# The default format for an entry is:
#
-# === How to create a logger
+# "%s, [%s #%d] %5s -- %s: %s\n"
#
-# The options below give you various choices, in more or less increasing
-# complexity.
+# where the values to be formatted are:
#
-# 1. Create a logger which logs messages to STDERR/STDOUT.
+# - \Severity (one letter).
+# - Timestamp.
+# - Timezone.
+# - \Severity (word).
+# - Program name.
+# - Message.
#
-# logger = Logger.new(STDERR)
-# logger = Logger.new(STDOUT)
+# You can use a different entry format by:
#
-# 2. Create a logger for the file which has the specified name.
+# - Calling #add with a block (affects only the one entry).
+# - Setting a format proc with method
+# {formatter=}[Logger.html#attribute-i-formatter]
+# (affects following entries).
#
-# logger = Logger.new('logfile.log')
+# === \Severity
#
-# 3. Create a logger for the specified file.
+# The severity of a log entry, which is specified in the call to #add,
+# does two things:
#
-# file = File.open('foo.log', File::WRONLY | File::APPEND)
-# # To create new logfile, add File::CREAT like:
-# # file = File.open('foo.log', File::WRONLY | File::APPEND | File::CREAT)
-# logger = Logger.new(file)
+# - Determines whether the entry is selected for inclusion in the log;
+# see {Log Level}[rdoc-ref:Logger@Log+Level].
+# - Indicates to any log reader (whether a person or a program)
+# the relative importance of the entry.
#
-# 4. Create a logger which ages the logfile once it reaches a certain size.
-# Leave 10 "old" log files where each file is about 1,024,000 bytes.
+# === Timestamp
#
-# logger = Logger.new('foo.log', 10, 1024000)
+# The timestamp for a log entry is generated automatically
+# when the entry is created (by a call to #add).
#
-# 5. Create a logger which ages the logfile daily/weekly/monthly.
+# The logged timestamp is formatted by method
+# {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime]
+# using this format string:
#
-# logger = Logger.new('foo.log', 'daily')
-# logger = Logger.new('foo.log', 'weekly')
-# logger = Logger.new('foo.log', 'monthly')
+# '%Y-%m-%dT%H:%M:%S.%6N'
#
-# === How to log a message
+# Example:
#
-# Notice the different methods (+fatal+, +error+, +info+) being used to log
-# messages of various levels? Other methods in this family are +warn+ and
-# +debug+. +add+ is used below to log a message of an arbitrary (perhaps
-# dynamic) level.
+# logger = Logger.new($stdout)
+# logger.add(Logger::INFO)
+# # => I, [2022-05-07T17:04:32.318331 #20536] INFO -- : nil
+#
+# You can set a different format using method #datetime_format=.
+#
+# === Message
+#
+# The message is an optional argument to method #add:
+#
+# logger = Logger.new($stdout)
+# logger.add(Logger::INFO, 'My message')
+# # => I, [2022-05-07T18:15:37.647581 #20536] INFO -- : My message
+#
+# The message object may be a string, or an object that can be converted
+# to a string.
+#
+# *Note*: \Logger does not escape or sanitize any messages passed to it.
+# Developers should be aware that malicious data (user input)
+# may be passed to \Logger, and should explicitly escape untrusted data.
+#
+# You can use a custom formatter to escape message data;
+# this formatter uses
+# {String#dump}[https://docs.ruby-lang.org/en/master/String.html#method-i-dump]
+# to escape the message string:
+#
+# original_formatter = logger.formatter || Logger::Formatter.new
+# logger.formatter = proc { |sev, time, progname, msg|
+# original_formatter.call(sev, time, progname, msg.dump)
+# }
+# logger.info(input)
#
-# 1. Message in a block.
+# === Program Name
#
-# logger.fatal { "Argument 'foo' not given." }
+# The program name is an optional argument to method #add:
#
-# 2. Message as a string.
+# logger = Logger.new($stdout)
+# logger.add(Logger::INFO, 'My message', 'mung')
+# # => I, [2022-05-07T18:17:38.084716 #20536] INFO -- mung: My message
#
-# logger.error "Argument #{@foo} mismatch."
+# The default program name for a new logger may be set in the call to
+# Logger.new via optional keyword argument +progname+:
#
-# 3. With progname.
+# logger = Logger.new('t.log', progname: 'mung')
#
-# logger.info('initialize') { "Initializing..." }
+# The default program name for an existing logger may be set
+# by a call to method #progname=:
#
-# 4. With severity.
+# logger.progname = 'mung'
#
-# logger.add(Logger::FATAL) { 'Fatal error!' }
+# The current program name may be retrieved with method
+# {progname}[Logger.html#attribute-i-progname]:
#
-# The block form allows you to create potentially complex log messages,
-# but to delay their evaluation until and unless the message is
-# logged. For example, if we have the following:
+# == Log Level
#
-# logger.debug { "This is a " + potentially + " expensive operation" }
+# The log level setting determines whether an entry is actually
+# written to the log, based on the entry's severity.
#
-# If the logger's level is +INFO+ or higher, no debug messages will be logged,
-# and the entire block will not even be evaluated. Compare to this:
+# These are the defined severities (least severe to most severe):
#
-# logger.debug("This is a " + potentially + " expensive operation")
+# logger = Logger.new($stdout)
+# logger.add(Logger::DEBUG, 'Maximal debugging info')
+# # => D, [2022-05-07T17:57:41.776220 #20536] DEBUG -- : Maximal debugging info
+# logger.add(Logger::INFO, 'Non-error information')
+# # => I, [2022-05-07T17:59:14.349167 #20536] INFO -- : Non-error information
+# logger.add(Logger::WARN, 'Non-error warning')
+# # => W, [2022-05-07T18:00:45.337538 #20536] WARN -- : Non-error warning
+# logger.add(Logger::ERROR, 'Non-fatal error')
+# # => E, [2022-05-07T18:02:41.592912 #20536] ERROR -- : Non-fatal error
+# logger.add(Logger::FATAL, 'Fatal error')
+# # => F, [2022-05-07T18:05:24.703931 #20536] FATAL -- : Fatal error
+# logger.add(Logger::UNKNOWN, 'Most severe')
+# # => A, [2022-05-07T18:07:54.657491 #20536] ANY -- : Most severe
#
-# Here, the string concatenation is done every time, even if the log
-# level is not set to show the debug message.
+# The default initial level setting is Logger::DEBUG, the lowest level,
+# which means that all entries are to be written, regardless of severity:
#
-# === How to close a logger
+# logger = Logger.new($stdout)
+# logger.level # => 0
+# logger.add(0, "My message")
+# # => D, [2022-05-11T15:10:59.773668 #20536] DEBUG -- : My message
#
-# logger.close
+# You can specify a different setting in a new logger
+# using keyword argument +level+ with an appropriate value:
#
-# === Setting severity threshold
+# logger = Logger.new($stdout, level: Logger::ERROR)
+# logger = Logger.new($stdout, level: 'error')
+# logger = Logger.new($stdout, level: :error)
+# logger.level # => 3
#
-# 1. Original interface.
+# With this level, entries with severity Logger::ERROR and higher
+# are written, while those with lower severities are not written:
#
-# logger.sev_threshold = Logger::WARN
+# logger = Logger.new($stdout, level: Logger::ERROR)
+# logger.add(3)
+# # => E, [2022-05-11T15:17:20.933362 #20536] ERROR -- : nil
+# logger.add(2) # Silent.
#
-# 2. Log4r (somewhat) compatible interface.
+# You can set the log level for an existing logger
+# with method #level=:
#
-# logger.level = Logger::INFO
+# logger.level = Logger::ERROR
#
-# # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
+# There are also these shorthand methods for setting the level:
#
-# 3. Symbol or String (case insensitive)
+# logger.debug! # => 0
+# logger.info! # => 1
+# logger.warn! # => 2
+# logger.error! # => 3
+# logger.fatal! # => 4
#
-# logger.level = :info
-# logger.level = 'INFO'
+# You can retrieve the log level with method
+# {level}[Logger.html#attribute-i-level]:
#
-# # :debug < :info < :warn < :error < :fatal < :unknown
+# logger.level = 3
+# logger.level # => 3
#
-# 4. Constructor
+# There are also these methods for determining whether a given
+# level is to be written:
#
-# Logger.new(logdev, level: Logger::INFO)
-# Logger.new(logdev, level: :info)
-# Logger.new(logdev, level: 'INFO')
+# logger.level = 3
+# logger.debug? # => false
+# logger.info? # => false
+# logger.warn? # => false
+# logger.error? # => true
+# logger.fatal? # => true
#
-# == Format
+# == Log File Rotation
#
-# Log messages are rendered in the output stream in a certain format by
-# default. The default format and a sample are shown below:
+# By default, a log file is a single file that grows indefinitely
+# (until explicitly closed); there is no file rotation.
#
-# Log format:
-# SeverityID, [DateTime #pid] SeverityLabel -- ProgName: message
+# To keep log files to a manageable size,
+# you can use _log_ _file_ _rotation_, which uses multiple log files:
#
-# Log sample:
-# I, [1999-03-03T02:34:24.895701 #19074] INFO -- Main: info.
+# - Each log file has entries for a non-overlapping
+# time interval.
+# - Only the most recent log file is open and active;
+# the others are closed and inactive.
#
-# You may change the date and time format via #datetime_format=.
+# === Size-Based Rotation
#
-# logger.datetime_format = '%Y-%m-%d %H:%M:%S'
-# # e.g. "2004-01-03 00:54:26"
+# For size-based log file rotation, call Logger.new with:
#
-# or via the constructor.
+# - Argument +logdev+ as a file path.
+# - Argument +shift_age+ with a positive integer:
+# the number of log files to be in the rotation.
+# - Argument +shift_size+ as a positive integer:
+# the maximum size (in bytes) of each log file;
+# defaults to 1048576 (1 megabyte).
#
-# Logger.new(logdev, datetime_format: '%Y-%m-%d %H:%M:%S')
+# Examples:
#
-# Or, you may change the overall format via the #formatter= method.
+# logger = Logger.new('t.log', 3) # Three 1-megabyte files.
+# logger = Logger.new('t.log', 5, 10485760) # Five 10-megabyte files.
#
-# logger.formatter = proc do |severity, datetime, progname, msg|
-# "#{datetime}: #{msg}\n"
-# end
-# # e.g. "2005-09-22 08:51:08 +0900: hello world"
+# For these examples, suppose:
#
-# or via the constructor.
+# logger = Logger.new('t.log', 3)
#
-# Logger.new(logdev, formatter: proc {|severity, datetime, progname, msg|
-# "#{datetime}: #{msg}\n"
-# })
+# Logging begins in the new log file, +t.log+;
+# the log file is "full" and ready for rotation
+# when a new entry would cause its size to exceed +shift_size+.
+#
+# The first time +t.log+ is full:
+#
+# - +t.log+ is closed and renamed to +t.log.0+.
+# - A new file +t.log+ is opened.
+#
+# The second time +t.log+ is full:
+#
+# - +t.log.0 is renamed as +t.log.1+.
+# - +t.log+ is closed and renamed to +t.log.0+.
+# - A new file +t.log+ is opened.
+#
+# Each subsequent time that +t.log+ is full,
+# the log files are rotated:
+#
+# - +t.log.1+ is removed.
+# - +t.log.0 is renamed as +t.log.1+.
+# - +t.log+ is closed and renamed to +t.log.0+.
+# - A new file +t.log+ is opened.
+#
+# === Periodic Rotation
+#
+# For periodic rotation, call Logger.new with:
+#
+# - Argument +logdev+ as a file path.
+# - Argument +shift_age+ as a string period indicator.
+#
+# Examples:
+#
+# logger = Logger.new('t.log', 'daily') # Rotate log files daily.
+# logger = Logger.new('t.log', 'weekly') # Rotate log files weekly.
+# logger = Logger.new('t.log', 'monthly') # Rotate log files monthly.
+#
+# Example:
+#
+# logger = Logger.new('t.log', 'daily')
+#
+# When the given period expires:
+#
+# - The base log file, +t.log+ is closed and renamed
+# with a date-based suffix such as +t.log.20220509+.
+# - A new log file +t.log+ is opened.
+# - Nothing is removed.
+#
+# The default format for the suffix is '%Y%m%d',
+# which produces a suffix similar to the one above.
+# You can set a different format using create-time option
+# +shift_period_suffix+;
+# see details and suggestions at
+# {Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime].
#
class Logger
_, name, rev = %w$Id$
@@ -340,12 +476,21 @@ def fatal!; self.level = FATAL; end
#
# :call-seq:
- # Logger.new(logdev, shift_age = 0, shift_size = 1048576)
- # Logger.new(logdev, shift_age = 'weekly')
- # Logger.new(logdev, level: :info)
- # Logger.new(logdev, progname: 'progname')
- # Logger.new(logdev, formatter: formatter)
- # Logger.new(logdev, datetime_format: '%Y-%m-%d %H:%M:%S')
+ # Logger.new(logdev, shift_age = 0, shift_size = 1048576, **options)
+ # Logger.new(logdev, shift_age = 'weekly', **options)
+ #
+ # With the single argument +logdev+,
+ # returns a new logger with all default options:
+ #
+ # Logger.new('t.log') # => #
+ #
+ # Argument +logdev+ must be one of:
+ #
+ # - A string filepath: entries are to be written
+ # to the file at that path.
+ # - An IO stream (typically +$stdout+, +$stderr+, or an open file):
+ # entries are to be written to the given stream.
+ # - +nil+ or +File::NULL+: no entries are to be written.
#
# === Args
#
diff --git a/lib/logger/errors.rb b/lib/logger/errors.rb
index e8925e1..8858179 100644
--- a/lib/logger/errors.rb
+++ b/lib/logger/errors.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-# not used after 1.2.7. just for compat.
class Logger
+ # not used after 1.2.7. just for compat.
class Error < RuntimeError # :nodoc:
end
class ShiftingError < Error # :nodoc: