summaryrefslogtreecommitdiff
path: root/lib/logger
diff options
context:
space:
mode:
authorColby Swandale <me@colby.fyi>2018-12-12 14:08:35 +1100
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2019-07-15 14:43:07 +0900
commitbbe157f34046ec9ef81e6ad9ed37c86a0d9155e4 (patch)
tree795ef0226ac95ede84fcee787dbe6cec19418463 /lib/logger
parent136196785b10f7cffa1e4d557bc3bcc0e863813a (diff)
[ruby/logger] split logger classes/modules into separate files
https://github.com/ruby/logger/commit/f10ce9fff2
Diffstat (limited to 'lib/logger')
-rw-r--r--lib/logger/errors.rb7
-rw-r--r--lib/logger/formatter.rb35
-rw-r--r--lib/logger/log_device.rb197
-rw-r--r--lib/logger/period.rb41
-rw-r--r--lib/logger/severity.rb17
-rw-r--r--lib/logger/version.rb5
6 files changed, 302 insertions, 0 deletions
diff --git a/lib/logger/errors.rb b/lib/logger/errors.rb
new file mode 100644
index 0000000000..e0fb4c643f
--- /dev/null
+++ b/lib/logger/errors.rb
@@ -0,0 +1,7 @@
+class Logger
+ class Error < RuntimeError # :nodoc:
+ end
+ # not used after 1.2.7. just for compat.
+ class ShiftingError < Error # :nodoc:
+ end
+end
diff --git a/lib/logger/formatter.rb b/lib/logger/formatter.rb
new file mode 100644
index 0000000000..13cb1a8390
--- /dev/null
+++ b/lib/logger/formatter.rb
@@ -0,0 +1,35 @@
+class Logger
+ # Default formatter for log messages.
+ class Formatter
+ Format = "%s, [%s#%d] %5s -- %s: %s\n".freeze
+
+ attr_accessor :datetime_format
+
+ def initialize
+ @datetime_format = nil
+ end
+
+ def call(severity, time, progname, msg)
+ Format % [severity[0..0], format_datetime(time), $$, severity, progname,
+ msg2str(msg)]
+ end
+
+ private
+
+ def format_datetime(time)
+ time.strftime(@datetime_format || "%Y-%m-%dT%H:%M:%S.%6N ".freeze)
+ end
+
+ def msg2str(msg)
+ case msg
+ when ::String
+ msg
+ when ::Exception
+ "#{ msg.message } (#{ msg.class })\n" <<
+ (msg.backtrace || []).join("\n")
+ else
+ msg.inspect
+ end
+ end
+ end
+end
diff --git a/lib/logger/log_device.rb b/lib/logger/log_device.rb
new file mode 100644
index 0000000000..7cfb45b556
--- /dev/null
+++ b/lib/logger/log_device.rb
@@ -0,0 +1,197 @@
+require 'logger/period'
+
+class Logger
+ # Device used for logging messages.
+ class LogDevice
+ include Period
+
+ attr_reader :dev
+ attr_reader :filename
+ include MonitorMixin
+
+ def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil)
+ @dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil
+ mon_initialize
+ set_dev(log)
+ if @filename
+ @shift_age = shift_age || 7
+ @shift_size = shift_size || 1048576
+ @shift_period_suffix = shift_period_suffix || '%Y%m%d'
+
+ unless @shift_age.is_a?(Integer)
+ base_time = @dev.respond_to?(:stat) ? @dev.stat.mtime : Time.now
+ @next_rotate_time = next_rotate_time(base_time, @shift_age)
+ end
+ end
+ end
+
+ def write(message)
+ begin
+ synchronize do
+ if @shift_age and @dev.respond_to?(:stat)
+ begin
+ check_shift_log
+ rescue
+ warn("log shifting failed. #{$!}")
+ end
+ end
+ begin
+ @dev.write(message)
+ rescue
+ warn("log writing failed. #{$!}")
+ end
+ end
+ rescue Exception => ignored
+ warn("log writing failed. #{ignored}")
+ end
+ end
+
+ def close
+ begin
+ synchronize do
+ @dev.close rescue nil
+ end
+ rescue Exception
+ @dev.close rescue nil
+ end
+ end
+
+ def reopen(log = nil)
+ # reopen the same filename if no argument, do nothing for IO
+ log ||= @filename if @filename
+ if log
+ synchronize do
+ if @filename and @dev
+ @dev.close rescue nil # close only file opened by Logger
+ @filename = nil
+ end
+ set_dev(log)
+ end
+ end
+ self
+ end
+
+ private
+
+ def set_dev(log)
+ if log.respond_to?(:write) and log.respond_to?(:close)
+ @dev = log
+ else
+ @dev = open_logfile(log)
+ @dev.sync = true
+ @filename = log
+ end
+ end
+
+ def open_logfile(filename)
+ begin
+ File.open(filename, (File::WRONLY | File::APPEND))
+ rescue Errno::ENOENT
+ create_logfile(filename)
+ end
+ end
+
+ def create_logfile(filename)
+ begin
+ logdev = File.open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL))
+ logdev.flock(File::LOCK_EX)
+ logdev.sync = true
+ add_log_header(logdev)
+ logdev.flock(File::LOCK_UN)
+ rescue Errno::EEXIST
+ # file is created by another process
+ logdev = open_logfile(filename)
+ logdev.sync = true
+ end
+ logdev
+ end
+
+ def add_log_header(file)
+ file.write(
+ "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
+ ) if file.size == 0
+ end
+
+ def check_shift_log
+ if @shift_age.is_a?(Integer)
+ # Note: always returns false if '0'.
+ if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
+ lock_shift_log { shift_log_age }
+ end
+ else
+ now = Time.now
+ if now >= @next_rotate_time
+ @next_rotate_time = next_rotate_time(now, @shift_age)
+ lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) }
+ end
+ end
+ end
+
+ if /mswin|mingw/ =~ RUBY_PLATFORM
+ def lock_shift_log
+ yield
+ end
+ else
+ def lock_shift_log
+ retry_limit = 8
+ retry_sleep = 0.1
+ begin
+ File.open(@filename, File::WRONLY | File::APPEND) do |lock|
+ lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file
+ if File.identical?(@filename, lock) and File.identical?(lock, @dev)
+ yield # log shifting
+ else
+ # log shifted by another process (i-node before locking and i-node after locking are different)
+ @dev.close rescue nil
+ @dev = open_logfile(@filename)
+ @dev.sync = true
+ end
+ end
+ rescue Errno::ENOENT
+ # @filename file would not exist right after #rename and before #create_logfile
+ if retry_limit <= 0
+ warn("log rotation inter-process lock failed. #{$!}")
+ else
+ sleep retry_sleep
+ retry_limit -= 1
+ retry_sleep *= 2
+ retry
+ end
+ end
+ rescue
+ warn("log rotation inter-process lock failed. #{$!}")
+ end
+ end
+
+ def shift_log_age
+ (@shift_age-3).downto(0) do |i|
+ if FileTest.exist?("#{@filename}.#{i}")
+ File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
+ end
+ end
+ @dev.close rescue nil
+ File.rename("#{@filename}", "#{@filename}.0")
+ @dev = create_logfile(@filename)
+ return true
+ end
+
+ def shift_log_period(period_end)
+ suffix = period_end.strftime(@shift_period_suffix)
+ age_file = "#{@filename}.#{suffix}"
+ if FileTest.exist?(age_file)
+ # try to avoid filename crash caused by Timestamp change.
+ idx = 0
+ # .99 can be overridden; avoid too much file search with 'loop do'
+ while idx < 100
+ idx += 1
+ age_file = "#{@filename}.#{suffix}.#{idx}"
+ break unless FileTest.exist?(age_file)
+ end
+ end
+ @dev.close rescue nil
+ File.rename("#{@filename}", age_file)
+ @dev = create_logfile(@filename)
+ return true
+ end
+ end
+end
diff --git a/lib/logger/period.rb b/lib/logger/period.rb
new file mode 100644
index 0000000000..bb8cffc2f8
--- /dev/null
+++ b/lib/logger/period.rb
@@ -0,0 +1,41 @@
+class Logger
+ module Period
+ module_function
+
+ SiD = 24 * 60 * 60
+
+ def next_rotate_time(now, shift_age)
+ case shift_age
+ when 'daily'
+ t = Time.mktime(now.year, now.month, now.mday) + SiD
+ when 'weekly'
+ t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday)
+ when 'monthly'
+ t = Time.mktime(now.year, now.month, 1) + SiD * 32
+ return Time.mktime(t.year, t.month, 1)
+ else
+ return now
+ end
+ if t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero?
+ hour = t.hour
+ t = Time.mktime(t.year, t.month, t.mday)
+ t += SiD if hour > 12
+ end
+ t
+ end
+
+ def previous_period_end(now, shift_age)
+ case shift_age
+ when 'daily'
+ t = Time.mktime(now.year, now.month, now.mday) - SiD / 2
+ when 'weekly'
+ t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2)
+ when 'monthly'
+ t = Time.mktime(now.year, now.month, 1) - SiD / 2
+ else
+ return now
+ end
+ Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
+ end
+ end
+end
diff --git a/lib/logger/severity.rb b/lib/logger/severity.rb
new file mode 100644
index 0000000000..c8179cb017
--- /dev/null
+++ b/lib/logger/severity.rb
@@ -0,0 +1,17 @@
+class Logger
+ # Logging severity.
+ module Severity
+ # Low-level information, mostly for developers.
+ DEBUG = 0
+ # Generic (useful) information about system operation.
+ INFO = 1
+ # A warning.
+ WARN = 2
+ # A handleable error condition.
+ ERROR = 3
+ # An unhandleable error that results in a program crash.
+ FATAL = 4
+ # An unknown message that should always be logged.
+ UNKNOWN = 5
+ end
+end
diff --git a/lib/logger/version.rb b/lib/logger/version.rb
new file mode 100644
index 0000000000..af122736c3
--- /dev/null
+++ b/lib/logger/version.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class Logger
+ VERSION = "1.3.0"
+end