diff --git a/lib/serverengine/daemon_logger.rb b/lib/serverengine/daemon_logger.rb index 007aaa3..a63f603 100644 --- a/lib/serverengine/daemon_logger.rb +++ b/lib/serverengine/daemon_logger.rb @@ -55,6 +55,9 @@ def logdev=(logdev) # update path string old_file_dev = @file_dev @file_dev = LogDevice.new(logdev, shift_age: @rotate_age, shift_size: @rotate_size) + # Enable to detect rotation done by external tools. + # Otherwise it continues writing logs to old file unexpectedly. + @file_dev.extend(RotationAware) old_file_dev.close if old_file_dev @logdev = @file_dev end @@ -130,6 +133,40 @@ def close nil end + module RotationAware + def self.extended(obj) + obj.update_ino + end + + def update_ino + (@ino_mutex ||= Mutex.new).synchronize do + @ino = File.stat(filename).ino rescue nil + @last_ino_time = Time.now + end + end + + def reopen(log = nil) + super(log) + update_ino + end + + def reopen! + super + update_ino + end + + def write(message) + reopen_needed = false + @ino_mutex.synchronize do + if (Time.now - @last_ino_time).abs > 1 + ino = File.stat(filename).ino rescue nil + reopen_needed = true if ino && ino != @ino + end + end + reopen! if reopen_needed + super(message) + end + end end end diff --git a/spec/daemon_logger_spec.rb b/spec/daemon_logger_spec.rb index 9d52ca5..0bb19f6 100644 --- a/spec/daemon_logger_spec.rb +++ b/spec/daemon_logger_spec.rb @@ -1,4 +1,5 @@ require 'stringio' +require 'timecop' describe ServerEngine::DaemonLogger do before { FileUtils.rm_rf("tmp") } @@ -172,4 +173,28 @@ $stderr = STDERR stderr.should_not =~ /(log shifting failed|log writing failed|log rotation inter-process lock failed)/ end + + it 'reopen log when path is renamed' do + pending "rename isn't supported on windows" if ServerEngine.windows? + + log = DaemonLogger.new("tmp/rotate.log", { level: 'info', log_rotate_age: 0 }) + + log.info '11111' + File.read("tmp/rotate.log").should include('11111') + File.rename("tmp/rotate.log", "tmp/rotate.log.1") + + Timecop.travel(Time.now + 1) + + log.info '22222' + contents = File.read("tmp/rotate.log.1") + contents.should include('11111') + contents.should include('22222') + + FileUtils.touch("tmp/rotate.log") + Timecop.travel(Time.now + 1) + + log.info '33333' + File.read("tmp/rotate.log").should include('33333') + File.read("tmp/rotate.log.1").should_not include('33333') + end end