[ruby/logger] Enable log file rotation on Windows

Since ruby 2.3, a file opened with `File::SHARE_DELETE` and
`File::BINARY` can be renamed or removed.

https://github.com/ruby/logger/commit/7b6146fee6
This commit is contained in:
Nobuyoshi Nakada 2024-10-09 14:05:57 +09:00 committed by git
parent ed47b6b324
commit edd3977b40
2 changed files with 69 additions and 38 deletions

View File

@ -67,6 +67,12 @@ class Logger
private private
# :stopdoc:
MODE = File::WRONLY | File::APPEND
MODE_TO_OPEN = MODE | File::SHARE_DELETE | File::BINARY
MODE_TO_CREATE = MODE_TO_OPEN | File::CREAT | File::EXCL
def set_dev(log) def set_dev(log)
if log.respond_to?(:write) and log.respond_to?(:close) if log.respond_to?(:write) and log.respond_to?(:close)
@dev = log @dev = log
@ -77,34 +83,54 @@ class Logger
end end
else else
@dev = open_logfile(log) @dev = open_logfile(log)
@dev.sync = true
@dev.binmode if @binmode
@filename = log @filename = log
end end
end end
if MODE_TO_OPEN == MODE
def fixup_mode(dev, filename)
dev
end
else
def fixup_mode(dev, filename)
return dev if @binmode
dev.autoclose = false
old_dev = dev
dev = File.new(dev.fileno, mode: MODE, path: filename)
old_dev.close
PathAttr.set_path(dev, filename) if defined?(PathAttr)
dev
end
end
def open_logfile(filename) def open_logfile(filename)
begin begin
File.open(filename, (File::WRONLY | File::APPEND)) dev = File.open(filename, MODE_TO_OPEN)
rescue Errno::ENOENT rescue Errno::ENOENT
create_logfile(filename) create_logfile(filename)
else
dev = fixup_mode(dev, filename)
dev.sync = true
dev.binmode if @binmode
dev
end end
end end
def create_logfile(filename) def create_logfile(filename)
begin begin
logdev = File.open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL)) logdev = File.open(filename, MODE_TO_CREATE)
logdev.flock(File::LOCK_EX) logdev.flock(File::LOCK_EX)
logdev = fixup_mode(logdev, filename)
logdev.sync = true logdev.sync = true
logdev.binmode if @binmode logdev.binmode if @binmode
add_log_header(logdev) add_log_header(logdev)
logdev.flock(File::LOCK_UN) logdev.flock(File::LOCK_UN)
logdev
rescue Errno::EEXIST rescue Errno::EEXIST
# file is created by another process # file is created by another process
logdev = open_logfile(filename) open_logfile(filename)
logdev.sync = true
end end
logdev end
def handle_write_errors(mesg) def handle_write_errors(mesg)
yield yield
@ -135,40 +161,33 @@ class Logger
end end
end end
if /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os'] def lock_shift_log
def lock_shift_log retry_limit = 8
yield retry_sleep = 0.1
end begin
else File.open(@filename, MODE_TO_OPEN) do |lock|
def lock_shift_log lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file
retry_limit = 8 if File.identical?(@filename, lock) and File.identical?(lock, @dev)
retry_sleep = 0.1 yield # log shifting
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 else
sleep retry_sleep # log shifted by another process (i-node before locking and i-node after locking are different)
retry_limit -= 1 @dev.close rescue nil
retry_sleep *= 2 @dev = open_logfile(@filename)
retry
end end
end end
rescue rescue Errno::ENOENT
warn("log rotation inter-process lock failed. #{$!}") # @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 end
rescue
warn("log rotation inter-process lock failed. #{$!}")
end end
def shift_log_age def shift_log_age
@ -203,3 +222,15 @@ class Logger
end end
end end
end end
File.open(IO::NULL) do |f|
File.new(f.fileno, autoclose: false, path: "").path
rescue IOError
module PathAttr # :nodoc:
attr_reader :path
def self.set_path(file, path)
file.extend(self).instance_variable_set(:@path, path)
end
end
end

View File

@ -452,7 +452,7 @@ class TestLogDevice < Test::Unit::TestCase
end end
ensure ensure
logdev0.close logdev0.close
end unless /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os'] end
def test_shifting_midnight def test_shifting_midnight
Dir.mktmpdir do |tmpdir| Dir.mktmpdir do |tmpdir|