1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* lib/logger.rb (write, shift_log?, shift_log): file shifting race

condition bug fixed.  [ruby-dev:26764]

        * test/logger/test_logger.rb: tests.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@9011 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nahi 2005-08-20 13:43:46 +00:00
parent 3dec8be265
commit f003069ecd
3 changed files with 152 additions and 84 deletions

View file

@ -1,3 +1,10 @@
Sat Aug 20 22:37:13 2005 NAKAMURA, Hiroshi <nakahiro@sarion.co.jp>
* lib/logger.rb (write, shift_log?, shift_log): file shifting race
condition bug fixed. [ruby-dev:26764]
* test/logger/test_logger.rb: tests.
Fri Aug 19 18:13:39 2005 Tanaka Akira <akr@m17n.org> Fri Aug 19 18:13:39 2005 Tanaka Akira <akr@m17n.org>
* lib/time.rb (Time.apply_offset): fix a problem with last day of * lib/time.rb (Time.apply_offset): fix a problem with last day of

View file

@ -1,4 +1,7 @@
# # logger.rb - saimple logging utility
# Copyright (C) 2000-2003, 2005 NAKAMURA, Hiroshi <nakahiro@sarion.co.jp>.
# = logger.rb # = logger.rb
# #
# Simple logging utility. # Simple logging utility.
@ -19,7 +22,6 @@
# #
# The Logger class provides a simple but sophisticated logging utility that # The Logger class provides a simple but sophisticated logging utility that
# anyone can use because it's included in the Ruby 1.8.x standard library. # anyone can use because it's included in the Ruby 1.8.x standard library.
# For more advanced logging, see the "Log4r" package on the RAA.
# #
# The HOWTOs below give a code-based overview of Logger's usage, but the basic # The HOWTOs below give a code-based overview of Logger's usage, but the basic
# concept is as follows. You create a Logger object (output to a file or # concept is as follows. You create a Logger object (output to a file or
@ -76,10 +78,6 @@
# specifying a program name in conjunction with the message. The next section # specifying a program name in conjunction with the message. The next section
# shows you how to achieve these things. # shows you how to achieve these things.
# #
# See http://raa.ruby-lang.org/list.rhtml?name=log4r for Log4r, which contains
# many advanced features like file-based configuration, a wide range of
# logging targets, simultaneous logging, and hierarchical logging.
#
# #
# == HOWTOs # == HOWTOs
# #
@ -174,6 +172,11 @@
# There is currently no supported way to change the overall format, but you may # There is currently no supported way to change the overall format, but you may
# have some luck hacking the Format constant. # have some luck hacking the Format constant.
# #
require 'monitor'
class Logger class Logger
/: (\S+),v (\S+)/ =~ %q$Id$ /: (\S+),v (\S+)/ =~ %q$Id$
ProgName = "#{$1}/#{$2}" ProgName = "#{$1}/#{$2}"
@ -490,6 +493,8 @@ private
# #
def initialize(log = nil, opt = {}) def initialize(log = nil, opt = {})
@dev = @filename = @shift_age = @shift_size = nil @dev = @filename = @shift_age = @shift_size = nil
@mutex = Object.new
@mutex.extend(MonitorMixin)
if log.respond_to?(:write) and log.respond_to?(:close) if log.respond_to?(:write) and log.respond_to?(:close)
@dev = log @dev = log
else else
@ -508,23 +513,26 @@ private
# mixed. # mixed.
# #
def write(message) def write(message)
if shift_log? @mutex.synchronize do
if @shift_age and @dev.respond_to?(:stat)
begin begin
shift_log check_shift_log
rescue rescue
raise Logger::ShiftingError.new("Shifting failed. #{$!}") raise Logger::ShiftingError.new("Shifting failed. #{$!}")
end end
end end
@dev.write(message) @dev.write(message)
end end
end
# #
# Close the logging device. # Close the logging device.
# #
def close def close
@mutex.synchronize do
@dev.close @dev.close
end end
end
private private
@ -551,45 +559,46 @@ private
SiD = 24 * 60 * 60 SiD = 24 * 60 * 60
def shift_log? def check_shift_log
if !@shift_age or !@dev.respond_to?(:stat) if @shift_age.is_a?(Integer)
return false
end
if (@shift_age.is_a?(Integer))
# Note: always returns false if '0'. # Note: always returns false if '0'.
return (@filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)) if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
shift_log_age
end
else else
now = Time.now now = Time.now
limit_time = case @shift_age if @dev.stat.mtime <= previous_period_end(now)
when /^daily$/ shift_log_period(now)
eod(now - 1 * SiD)
when /^weekly$/
eod(now - ((now.wday + 1) * SiD))
when /^monthly$/
eod(now - now.mday * SiD)
else
now
end end
return (@dev.stat.mtime <= limit_time)
end end
end end
def shift_log def shift_log_age
# At first, close the device if opened.
if @dev
@dev.close
@dev = nil
end
if (@shift_age.is_a?(Integer))
(@shift_age-3).downto(0) do |i| (@shift_age-3).downto(0) do |i|
if (FileTest.exist?("#{@filename}.#{i}")) if FileTest.exist?("#{@filename}.#{i}")
File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}") File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
end end
end end
@dev.close
File.rename("#{@filename}", "#{@filename}.0") File.rename("#{@filename}", "#{@filename}.0")
else @dev = create_logfile(@filename)
now = Time.now return true
postfix_time = case @shift_age end
def shift_log_period(now)
postfix = previous_period_end(now).strftime("%Y%m%d") # YYYYMMDD
age_file = "#{@filename}.#{postfix}"
if FileTest.exist?(age_file)
raise RuntimeError.new("'#{ age_file }' already exists.")
end
@dev.close
File.rename("#{@filename}", age_file)
@dev = create_logfile(@filename)
return true
end
def previous_period_end(now)
case @shift_age
when /^daily$/ when /^daily$/
eod(now - 1 * SiD) eod(now - 1 * SiD)
when /^weekly$/ when /^weekly$/
@ -599,16 +608,6 @@ private
else else
now now
end end
postfix = postfix_time.strftime("%Y%m%d") # YYYYMMDD
age_file = "#{@filename}.#{postfix}"
if (FileTest.exist?(age_file))
raise RuntimeError.new("'#{ age_file }' already exists.")
end
File.rename("#{@filename}", age_file)
end
@dev = create_logfile(@filename)
return true
end end
def eod(t) def eod(t)

View file

@ -32,10 +32,10 @@ class TestLogger < Test::Unit::TestCase
end end
def log_add(logger, severity, msg, progname = nil, &block) def log_add(logger, severity, msg, progname = nil, &block)
do_log(logger, :add, severity, msg, progname, &block) log(logger, :add, severity, msg, progname, &block)
end end
def do_log(logger, msg_id, *arg, &block) def log(logger, msg_id, *arg, &block)
logdev = Tempfile.new(File.basename(__FILE__) + '.log') logdev = Tempfile.new(File.basename(__FILE__) + '.log')
logger.instance_eval { @logdev = Logger::LogDevice.new(logdev) } logger.instance_eval { @logdev = Logger::LogDevice.new(logdev) }
logger.__send__(msg_id, *arg, &block) logger.__send__(msg_id, *arg, &block)
@ -90,15 +90,9 @@ class TestLogger < Test::Unit::TestCase
logger = Logger.new(dummy) logger = Logger.new(dummy)
log = log_add(logger, INFO, "foo") log = log_add(logger, INFO, "foo")
assert_match(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\s*\d+ $/, log.datetime) assert_match(/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\s*\d+ $/, log.datetime)
# [ruby-dev:24828] logger.datetime_format = "%d%b%Y@%H:%M:%S"
#logger.datetime_format = "%d%b%Y@%H:%M:%S"
#log = log_add(logger, INFO, "foo")
#assert_match(/^\d\d\w\w\w\d\d\d\d@\d\d:\d\d:\d\d$/, log.datetime)
#
# don't run the next test at 23:59, or just run again if failed.
logger.datetime_format = "@%d%b%Y@"
log = log_add(logger, INFO, "foo") log = log_add(logger, INFO, "foo")
assert_equal(Time.now.strftime("@%d%b%Y@"), log.datetime) assert_match(/^\d\d\w\w\w\d\d\d\d@\d\d:\d\d:\d\d$/, log.datetime)
logger.datetime_format = "" logger.datetime_format = ""
log = log_add(logger, INFO, "foo") log = log_add(logger, INFO, "foo")
assert_match(/^$/, log.datetime) assert_match(/^$/, log.datetime)
@ -134,44 +128,44 @@ class TestLogger < Test::Unit::TestCase
def test_level_log def test_level_log
logger = Logger.new(nil) logger = Logger.new(nil)
logger.progname = "my_progname" logger.progname = "my_progname"
log = do_log(logger, :debug, "custom_progname") { "msg" } log = log(logger, :debug, "custom_progname") { "msg" }
assert_equal("msg\n", log.msg) assert_equal("msg\n", log.msg)
assert_equal("custom_progname", log.progname) assert_equal("custom_progname", log.progname)
assert_equal("DEBUG", log.severity) assert_equal("DEBUG", log.severity)
assert_equal("D", log.label) assert_equal("D", log.label)
# #
log = do_log(logger, :debug) { "msg_block" } log = log(logger, :debug) { "msg_block" }
assert_equal("msg_block\n", log.msg) assert_equal("msg_block\n", log.msg)
assert_equal("my_progname", log.progname) assert_equal("my_progname", log.progname)
log = do_log(logger, :debug, "msg_inline") log = log(logger, :debug, "msg_inline")
assert_equal("msg_inline\n", log.msg) assert_equal("msg_inline\n", log.msg)
assert_equal("my_progname", log.progname) assert_equal("my_progname", log.progname)
# #
log = do_log(logger, :info, "custom_progname") { "msg" } log = log(logger, :info, "custom_progname") { "msg" }
assert_equal("msg\n", log.msg) assert_equal("msg\n", log.msg)
assert_equal("custom_progname", log.progname) assert_equal("custom_progname", log.progname)
assert_equal("INFO", log.severity) assert_equal("INFO", log.severity)
assert_equal("I", log.label) assert_equal("I", log.label)
# #
log = do_log(logger, :warn, "custom_progname") { "msg" } log = log(logger, :warn, "custom_progname") { "msg" }
assert_equal("msg\n", log.msg) assert_equal("msg\n", log.msg)
assert_equal("custom_progname", log.progname) assert_equal("custom_progname", log.progname)
assert_equal("WARN", log.severity) assert_equal("WARN", log.severity)
assert_equal("W", log.label) assert_equal("W", log.label)
# #
log = do_log(logger, :error, "custom_progname") { "msg" } log = log(logger, :error, "custom_progname") { "msg" }
assert_equal("msg\n", log.msg) assert_equal("msg\n", log.msg)
assert_equal("custom_progname", log.progname) assert_equal("custom_progname", log.progname)
assert_equal("ERROR", log.severity) assert_equal("ERROR", log.severity)
assert_equal("E", log.label) assert_equal("E", log.label)
# #
log = do_log(logger, :fatal, "custom_progname") { "msg" } log = log(logger, :fatal, "custom_progname") { "msg" }
assert_equal("msg\n", log.msg) assert_equal("msg\n", log.msg)
assert_equal("custom_progname", log.progname) assert_equal("custom_progname", log.progname)
assert_equal("FATAL", log.severity) assert_equal("FATAL", log.severity)
assert_equal("F", log.label) assert_equal("F", log.label)
# #
log = do_log(logger, :unknown, "custom_progname") { "msg" } log = log(logger, :unknown, "custom_progname") { "msg" }
assert_equal("msg\n", log.msg) assert_equal("msg\n", log.msg)
assert_equal("custom_progname", log.progname) assert_equal("custom_progname", log.progname)
assert_equal("ANY", log.severity) assert_equal("ANY", log.severity)
@ -279,4 +273,72 @@ class TestLogDevice < Test::Unit::TestCase
assert(w.closed?) assert(w.closed?)
r.close r.close
end end
def test_shifting_size
logfile = File.basename(__FILE__) + '_1.log'
logfile0 = logfile + '.0'
logfile1 = logfile + '.1'
logfile2 = logfile + '.2'
logfile3 = logfile + '.3'
File.unlink(logfile) if File.exist?(logfile)
File.unlink(logfile0) if File.exist?(logfile0)
File.unlink(logfile1) if File.exist?(logfile1)
File.unlink(logfile2) if File.exist?(logfile2)
logger = Logger.new(logfile, 4, 100)
logger.error("0" * 15)
assert(File.exist?(logfile))
assert(!File.exist?(logfile0))
logger.error("0" * 15)
assert(File.exist?(logfile0))
assert(!File.exist?(logfile1))
logger.error("0" * 15)
assert(File.exist?(logfile1))
assert(!File.exist?(logfile2))
logger.error("0" * 15)
assert(File.exist?(logfile2))
assert(!File.exist?(logfile3))
logger.error("0" * 15)
assert(!File.exist?(logfile3))
logger.error("0" * 15)
assert(!File.exist?(logfile3))
File.unlink(logfile)
File.unlink(logfile0)
File.unlink(logfile1)
File.unlink(logfile2)
logfile = File.basename(__FILE__) + '_2.log'
logfile0 = logfile + '.0'
logfile1 = logfile + '.1'
logfile2 = logfile + '.2'
logfile3 = logfile + '.3'
logger = Logger.new(logfile, 4, 150)
logger.error("0" * 15)
assert(File.exist?(logfile))
assert(!File.exist?(logfile0))
logger.error("0" * 15)
assert(!File.exist?(logfile0))
logger.error("0" * 15)
assert(File.exist?(logfile0))
assert(!File.exist?(logfile1))
logger.error("0" * 15)
assert(!File.exist?(logfile1))
logger.error("0" * 15)
assert(File.exist?(logfile1))
assert(!File.exist?(logfile2))
logger.error("0" * 15)
assert(!File.exist?(logfile2))
logger.error("0" * 15)
assert(File.exist?(logfile2))
assert(!File.exist?(logfile3))
logger.error("0" * 15)
assert(!File.exist?(logfile3))
logger.error("0" * 15)
assert(!File.exist?(logfile3))
logger.error("0" * 15)
assert(!File.exist?(logfile3))
File.unlink(logfile)
File.unlink(logfile0)
File.unlink(logfile1)
File.unlink(logfile2)
end
end end