class Logger
.
see details and suggestions atshift_period_suffix
;
You can set a different format using create-time option
which produces a suffix similar to the one above.
The default format for the suffix is '%Y%m%d'
,
- Nothing is removed.
- A new log file t.log
is opened.
with a date-based suffix such as t.log.20220509
.
- The base log file, t.log
is closed and renamed
When the given period expires:
logger = Logger.new(‘t.log’, ‘daily’)
Example:
logger = Logger.new(‘t.log’, ‘monthly’) # Rotate log files monthly.
logger = Logger.new(‘t.log’, ‘weekly’) # Rotate log files weekly.
logger = Logger.new(‘t.log’, ‘daily’) # Rotate log files daily.
Examples:
- Argument shift_age
as a string period indicator.
- Argument logdev
as a file path.
For periodic rotation, call Logger.new with:
=== Periodic Rotation
- A new file t.log
is opened.
- t.log
is closed and renamed to t.log.0
.
- +t.log.0 is renamed as t.log.1
.
- t.log.1
is removed.
the log files are rotated:
Each subsequent time that t.log
is full,
- A new file t.log
is opened.
- t.log
is closed and renamed to t.log.0
.
- +t.log.0 is renamed as t.log.1
.
The second time t.log
is full:
- A new file t.log
is opened.
- t.log
is closed and renamed to t.log.0
.
The first time t.log
is full:
when a new entry would cause its size to exceed shift_size
.
the log file is “full” and ready for rotation
Logging begins in the new log file, t.log
;
logger = Logger.new(‘t.log’, 3)
For these examples, suppose:
logger = Logger.new(‘t.log’, 5, 10485760) # Five 10-megabyte files.
logger = Logger.new(‘t.log’, 3) # Three 1-megabyte files.
Examples:
defaults to 1048576 (1 megabyte).
the maximum size (in bytes) of each log file;
- Argument shift_size
as a positive integer:
the number of log files to be in the rotation.
- Argument shift_age
with a positive integer:
- Argument logdev
as a file path.
For size-based log file rotation, call Logger.new with:
=== Size-Based Rotation
the others are closed and inactive.
- Only the most recent log file is open and active;
time interval.
- Each log file has entries for a non-overlapping
you can use log file rotation, which uses multiple log files:
To keep log files to a manageable size,
(until explicitly closed); there is no file rotation.
By default, a log file is a single file that grows indefinitely
== Log File Rotation
logger.fatal? # => true
logger.error? # => true
logger.warn? # => false
logger.info? # => false
logger.debug? # => false
logger.level = Logger::ERROR
level is to be written:
These methods return whether a given
logger.level # => 3
logger.level = Logger::ERROR
You can retrieve the log level with method #level.
logger.fatal! # => 4
logger.error! # => 3
logger.warn! # => 2
logger.info! # => 1
logger.debug! # => 0
These shorthand methods also set the level:
logger.level = Logger::ERROR
with method #level=:
You can set the log level for an existing logger
logger.add(2) # Silent.
# => E, [2022-05-11T15:17:20.933362 #20536] ERROR – : nil
logger.add(3)
logger = Logger.new($stdout, level: Logger::ERROR)
are written, while those with lower severities are not written:
With this level, entries with severity Logger::ERROR and higher
logger.level # => 3
logger = Logger.new($stdout, level: :error)
logger = Logger.new($stdout, level: ‘error’)
logger = Logger.new($stdout, level: Logger::ERROR)
using keyword argument level
with an appropriate value:
You can specify a different setting in a new logger
# => D, [2022-05-11T15:10:59.773668 #20536] DEBUG – : My message
logger.add(0, “My message”)
logger.level # => 0
logger = Logger.new($stdout)
which means that all entries are to be written, regardless of severity:
The default initial level setting is Logger::DEBUG, the lowest level,
# => A, [2022-05-07T18:07:54.657491 #20536] ANY – : Most severe
logger.add(Logger::UNKNOWN, ‘Most severe’)
# => F, [2022-05-07T18:05:24.703931 #20536] FATAL – : Fatal error
logger.add(Logger::FATAL, ‘Fatal error’)
# => E, [2022-05-07T18:02:41.592912 #20536] ERROR – : Non-fatal error
logger.add(Logger::ERROR, ‘Non-fatal error’)
# => W, [2022-05-07T18:00:45.337538 #20536] WARN – : Non-error warning
logger.add(Logger::WARN, ‘Non-error warning’)
# => I, [2022-05-07T17:59:14.349167 #20536] INFO – : Non-error information
logger.add(Logger::INFO, ‘Non-error information’)
# => D, [2022-05-07T17:57:41.776220 #20536] DEBUG – : Maximal debugging info
logger.add(Logger::DEBUG, ‘Maximal debugging info’)
logger = Logger.new($stdout)
These are the defined severities (least severe to most severe):
written to the log, based on the entry’s severity.
The log level setting determines whether an entry is actually
== Log Level
logger.progname # => “mung”
:
The current program name may be retrieved with method
logger.progname = ‘mung’
by a call to method #progname=:
The default program name for an existing logger may be set
logger = Logger.new(‘t.log’, progname: ‘mung’)
Logger.new via optional keyword argument progname
:
The default program name for a new logger may be set in the call to
# => I, [2022-05-07T18:17:38.084716 #20536] INFO – mung: My message
logger.add(Logger::INFO, ‘My message’, ‘mung’)
logger = Logger.new($stdout)
The program name is an optional argument to an entry method:
=== Program Name
see the example at formatter=.
You can use a custom formatter to escape message data;
may be in the message, and should explicitly escape untrusted data.
Developers should be aware that malicious data (user input)
the message passed to it.
Note: Logger::Formatter does not escape or sanitize
- Anything else: message.inspect
is used.
- An Exception: message.message
is used.
- A string: used as-is.
the message object may be:
For the default entry formatter, Logger::Formatter
,
# => I, [2022-05-07T18:15:37.647581 #20536] INFO – : My message
logger.add(Logger::INFO, ‘My message’)
logger = Logger.new($stdout)
The message is an optional argument to an entry method:
=== Message
You can set a different format using method #datetime_format=.
# => I, [2022-05-07T17:04:32.318331 #20536] INFO – : nil
logger.add(Logger::INFO)
logger = Logger.new($stdout)
Example:
‘%Y-%m-%dT%H:%M:%S.%6N’
using this format string:
The logged timestamp is formatted by method
when the entry is created.
The timestamp for a log entry is generated automatically
=== Timestamp
the relative importance of the entry.
- Indicates to any log reader (whether a person or a program)
see Log Level.
- Determines whether the entry is selected for inclusion in the log;
The severity of a log entry has two effects:
=== Severity
logger.error(“#{my_slow_message_generator}”)
always evaluated, regardless of the log level:
Contrast this with the string form, where the string is
logger.error { my_slow_message_generator }
permits the entry actually to be written:
- Performance: the block is not evaluated unless the log level
and create a context-dependent message.
- Context: the block can evaluate the entire program context
Doing so can have two benefits:
(affects only the one entry).
- Calling any of the methods above with a block
see formatter=.
- Setting a custom format proc (affects following entries);
You can use a different entry format by:
- Message.
- Program name.
- Severity (word).
- Process id.
- Timestamp.
- Severity (one letter).
where the values to be formatted are:
“%s, [%s #%d] %5s – %s: %sn”
The default format for an entry is:
# => I, [2022-05-07T17:21:46.536234 #20536] INFO – mung: My message.
logger.add(Logger::INFO, ‘My message.’, ‘mung’)
logger = Logger.new($stdout)
Example:
- A program name.
- A message.
And may also have:
- An automatically created timestamp.
- A severity (the required argument to #add).
An entry always has:
see {Log Level}[rdoc-ref:Logger@Log+Level]
depending on the entry’s severity and on the log level;
the entry may or may not be written to the log,
When you call any of these methods,
logger.unknown(‘Most severe’)
logger.fatal(‘Fatal error’)
logger.error(‘Non-fatal error’)
logger.warn(‘Non-error warning’)
logger.info(‘Non-error information’)
logger.debug(‘Maximal debugging info’)
These shorthand methods also add entries:
logger.add(Logger::UNKNOWN, ‘Most severe’)
logger.add(Logger::FATAL, ‘Fatal error’)
logger.add(Logger::ERROR, ‘Non-fatal error’)
logger.add(Logger::WARN, ‘Non-error warning’)
logger.add(Logger::INFO, ‘Non-error information’)
logger.add(Logger::DEBUG, ‘Maximal debugging info’)
You can add entries with method Logger#add:
== Entries
logger.close
Close the log with Logger#close:
logger.add(Logger::UNKNOWN, ‘Most severe’)
logger.add(Logger::FATAL, ‘Fatal error’)
logger.add(Logger::ERROR, ‘Non-fatal error’)
logger.add(Logger::WARN, ‘Non-error warning’)
logger.add(Logger::INFO, ‘Non-error information’)
logger.add(Logger::DEBUG, ‘Maximal debugging info’)
Add entries (level, message) with Logger#add:
logger = Logger.new($stdout)
# Log to an IO stream.
logger = Logger.new(‘t.log’, ‘daily’)
# Period-based rotated logging: daily (also allowed: ‘weekly’, ‘monthly’).
logger = Logger.new(‘t.log’, 3, 10485760)
# Size-based rotated logging: 3 10-megabyte files.
logger = Logger.new(‘t.log’)
# Single log file.
Create a log with Logger.new:
== Synopsis
require ‘logger’
All examples on this page assume that Logger has been required:
== About the Examples
that provides a record of the program’s activities.
Each such log contains a chronological sequence of entries
for your program.
you can use to create one or more
Class Logger provides a simple but sophisticated logging utility that
def <<(msg)
My message.
Output:
logger << 'My message.' # => 10
logger = Logger.new($stdout)
or +nil+ if no log device exists:
returns the number of characters written,
Writes the given +msg+ to the log with no formatting;
def <<(msg) @logdev&.write(msg) end
def add(severity, message = nil, progname = nil)
- #unknown.
- #fatal.
- #error.
- #warn.
- #info.
- #debug.
These convenience methods have implicit severity:
E, [2022-05-12T16:26:35.841134 #36328] ERROR -- gnum: No good
E, [2022-05-12T16:25:55.349414 #36328] ERROR -- mung: No good
I, [2022-05-12T16:25:31.469726 #36328] INFO -- mung: mung
Output:
logger.add(Logger::ERROR, 'No good', 'gnum')
logger.add(Logger::ERROR, 'No good')
logger.add(Logger::INFO)
logger = Logger.new($stdout, progname: 'mung')
Examples:
and {Entries}[rdoc-ref:Logger@Entries] for details.
See {Log Level}[rdoc-ref:Logger@Log+Level]
depending on the entry's severity and on the log level.
Creates a log entry, which may or may not be written to the log,
def add(severity, message = nil, progname = nil) severity ||= UNKNOWN if @logdev.nil? or severity < level return true end if progname.nil? progname = @progname end if message.nil? if block_given? message = yield else message = progname progname = @progname end end @logdev.write( format_message(format_severity(severity), Time.now, progname, message)) true end
def close
logger.info('foo') # Prints "log writing failed. closed stream"
logger.close # => nil
logger = Logger.new('t.log')
Closes the logger; returns +nil+:
def close @logdev&.close end
def datetime_format
Returns the date-time format; see #datetime_format=.
def datetime_format @default_formatter.datetime_format end
def datetime_format=(datetime_format)
- +nil+: the logger uses '%Y-%m-%dT%H:%M:%S.%6N'.
{Time#strftime}[https://docs.ruby-lang.org/en/master/Time.html#method-i-strftime].
- A string suitable for use as a format for method
Argument +datetime_format+ should be either of these:
Sets the date-time format.
def datetime_format=(datetime_format) @default_formatter.datetime_format = datetime_format end
def debug(progname = nil, &block)
Equivalent to calling #add with severity Logger::DEBUG.
def debug(progname = nil, &block) add(DEBUG, nil, progname, &block) end
def debug!; self.level = DEBUG; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Sets the log level to Logger::DEBUG.
def debug!; self.level = DEBUG; end
def debug?; level <= DEBUG; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Logger::DEBUG to be written, +false+ otherwise.
Returns +true+ if the log level allows entries with severity
def debug?; level <= DEBUG; end
def error(progname = nil, &block)
Equivalent to calling #add with severity Logger::ERROR.
def error(progname = nil, &block) add(ERROR, nil, progname, &block) end
def error!; self.level = ERROR; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Sets the log level to Logger::ERROR.
def error!; self.level = ERROR; end
def error?; level <= ERROR; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Logger::ERROR to be written, +false+ otherwise.
Returns +true+ if the log level allows entries with severity
def error?; level <= ERROR; end
def fatal(progname = nil, &block)
Equivalent to calling #add with severity Logger::FATAL.
def fatal(progname = nil, &block) add(FATAL, nil, progname, &block) end
def fatal!; self.level = FATAL; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Sets the log level to Logger::FATAL.
def fatal!; self.level = FATAL; end
def fatal?; level <= FATAL; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Logger::FATAL to be written, +false+ otherwise.
Returns +true+ if the log level allows entries with severity
def fatal?; level <= FATAL; end
def format_message(severity, datetime, progname, msg)
def format_message(severity, datetime, progname, msg) (@formatter || @default_formatter).call(severity, datetime, progname, msg) end
def format_severity(severity)
def format_severity(severity) SEV_LABEL[severity] || 'ANY' end
def info(progname = nil, &block)
Equivalent to calling #add with severity Logger::INFO.
def info(progname = nil, &block) add(INFO, nil, progname, &block) end
def info!; self.level = INFO; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Sets the log level to Logger::INFO.
def info!; self.level = INFO; end
def info?; level <= INFO; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Logger::INFO to be written, +false+ otherwise.
Returns +true+ if the log level allows entries with severity
def info?; level <= INFO; end
def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG,
The default is to swallow all exceptions raised.
be reraised if there is an error when writing to the log device.
- +reraise_write_errors+: An array of exception classes, which will
See {Periodic Rotation}[rdoc-ref:Logger@Periodic+Rotation].
for periodic log file rotation; default is '%Y%m%d'.
- +shift_period_suffix+: sets the format for the filename suffix
default is +false+.
- +binmode+: sets whether the logger writes in binary mode;
See #datetime_format=.
default is +nil+.
- +datetime_format+: sets the format for entry timestamp;
See {formatter=}[Logger.html#attribute-i-formatter].
- +formatter+: sets the entry formatter; default is +nil+.
Logger.new('t.log', progname: 'mung')
See {Program Name}[rdoc-ref:Logger@Program+Name]:
- +progname+: sets the default program name; default is +nil+.
Logger.new('t.log', level: Logger::ERROR)
See {Log Level}[rdoc-ref:Logger@Log+Level]:
- +level+: sets the log level; default value is Logger::DEBUG.
The keyword options are:
Logger.new($stdout)
Logger.new('t.log')
Examples:
- +nil+ or +File::NULL+: no entries are to be written.
entries are to be written to the given stream.
- An IO stream (typically +$stdout+, +$stderr+. or an open file):
new entries are appended.
to the file at that path; if the file at that path exists,
- A string filepath: entries are to be written
Argument +logdev+ must be one of:
Logger.new('t.log') # => #
returns a new logger with all default options:
With the single argument +logdev+,
Logger.new(logdev, shift_age = 0, shift_size = 1048576, **options)
:call-seq:
def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG, progname: nil, formatter: nil, datetime_format: nil, binmode: false, shift_period_suffix: '%Y%m%d', reraise_write_errors: []) self.level = level self.progname = progname @default_formatter = Formatter.new self.datetime_format = datetime_format self.formatter = formatter @logdev = nil @level_override = {} if logdev && logdev != File::NULL @logdev = LogDevice.new(logdev, shift_age: shift_age, shift_size: shift_size, shift_period_suffix: shift_period_suffix, binmode: binmode, reraise_write_errors: reraise_write_errors) end end
def level
def level level_override[level_key] || @level end
def level=(severity)
Logger#sev_threshold= is an alias for Logger#level=.
logger.level = :error # => :error
logger.level = 'error' # => "error"
logger.level = 3 # => 3
logger.level = Logger::ERROR # => 3
Argument +severity+ may be an integer, a string, or a symbol:
See {Log Level}[rdoc-ref:Logger@Log+Level].
Sets the log level; returns +severity+.
def level=(severity) @level = Severity.coerce(severity) end
def level_key
def level_key Fiber.current end
def level_override
def level_override @level_override ||= {} end
def reopen(logdev = nil)
# "E, [2022-05-12T14:23:05.847241 #22428] ERROR -- : three\n"]
# "E, [2022-05-12T14:21:27.596726 #22428] ERROR -- : one\n",
# ["# Logfile created on 2022-05-12 14:21:19 -0500 by logger.rb/v1.5.0\n",
# =>
File.readlines('t.log')
logger.close
logger.add(Logger::ERROR, 'three')
logger.reopen
logger.add(Logger::ERROR, 'two') # Prints 'log writing failed. closed stream'
logger.close
logger.add(Logger::ERROR, 'one')
logger = Logger.new('t.log')
Example:
opens the stream for append.
(usually $stdout, $stderr, or an open File object),
- If +logdev+ is an IO stream
- If +logdev+ is a filepath, opens the indicated file for append.
- If +logdev+ is +nil+, reopens the current output stream.
Sets the logger's output stream:
def reopen(logdev = nil) @logdev&.reopen(logdev) self end
def unknown(progname = nil, &block)
Equivalent to calling #add with severity Logger::UNKNOWN.
def unknown(progname = nil, &block) add(UNKNOWN, nil, progname, &block) end
def warn(progname = nil, &block)
Equivalent to calling #add with severity Logger::WARN.
def warn(progname = nil, &block) add(WARN, nil, progname, &block) end
def warn!; self.level = WARN; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Sets the log level to Logger::WARN.
def warn!; self.level = WARN; end
def warn?; level <= WARN; end
See {Log Level}[rdoc-ref:Logger@Log+Level].
Logger::WARN to be written, +false+ otherwise.
Returns +true+ if the log level allows entries with severity
def warn?; level <= WARN; end
def with_level(severity)
logger.debug { "Hello" }
logger.with_level(:debug) do
Adjust the log level during the block execution for the current Fiber only
def with_level(severity) prev, level_override[level_key] = level, Severity.coerce(severity) begin yield ensure if prev level_override[level_key] = prev else level_override.delete(level_key) end end end