Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable log file rotation on Windows #102

Merged
merged 2 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 78 additions & 56 deletions lib/logger/log_device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,13 @@ def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix:
end

def write(message)
begin
handle_write_errors("writing") do
synchronize do
if @shift_age and @dev.respond_to?(:stat)
begin
check_shift_log
rescue *@reraise_write_errors
raise
rescue
warn("log shifting failed. #{$!}")
end
end
begin
@dev.write(message)
rescue *@reraise_write_errors
raise
rescue
warn("log writing failed. #{$!}")
handle_write_errors("shifting") {check_shift_log}
end
handle_write_errors("writing") {@dev.write(message)}
end
rescue *@reraise_write_errors
raise
rescue Exception => ignored
warn("log writing failed. #{ignored}")
end
end

Expand Down Expand Up @@ -83,6 +67,12 @@ def reopen(log = nil)

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)
if log.respond_to?(:write) and log.respond_to?(:close)
@dev = log
Expand All @@ -93,34 +83,61 @@ def set_dev(log)
end
else
@dev = open_logfile(log)
@dev.sync = true
@dev.binmode if @binmode
@filename = log
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)
begin
File.open(filename, (File::WRONLY | File::APPEND))
dev = File.open(filename, MODE_TO_OPEN)
rescue Errno::ENOENT
create_logfile(filename)
else
dev = fixup_mode(dev, filename)
dev.sync = true
dev.binmode if @binmode
dev
end
end

def create_logfile(filename)
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 = fixup_mode(logdev, filename)
logdev.sync = true
logdev.binmode if @binmode
add_log_header(logdev)
logdev.flock(File::LOCK_UN)
logdev
rescue Errno::EEXIST
# file is created by another process
logdev = open_logfile(filename)
logdev.sync = true
open_logfile(filename)
end
logdev
end

def handle_write_errors(mesg)
yield
rescue *@reraise_write_errors
raise
rescue
warn("log #{mesg} failed. #{$!}")
end

def add_log_header(file)
Expand All @@ -144,40 +161,33 @@ def check_shift_log
end
end

if /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os']
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. #{$!}")
def lock_shift_log
retry_limit = 8
retry_sleep = 0.1
begin
File.open(@filename, MODE_TO_OPEN) 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
sleep retry_sleep
retry_limit -= 1
retry_sleep *= 2
retry
# log shifted by another process (i-node before locking and i-node after locking are different)
@dev.close rescue nil
@dev = open_logfile(@filename)
end
end
rescue
warn("log rotation inter-process lock failed. #{$!}")
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

def shift_log_age
Expand Down Expand Up @@ -212,3 +222,15 @@ def shift_log_period(period_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
2 changes: 1 addition & 1 deletion test/logger/test_logdevice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ def test_shifting_size_not_rotate_too_much
end
ensure
logdev0.close
end unless /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os']
end

def test_shifting_midnight
Dir.mktmpdir do |tmpdir|
Expand Down