From 45a830f6c7248ef627e64306df994c058d29f002 Mon Sep 17 00:00:00 2001 From: Takuro Ashie Date: Thu, 9 Jun 2022 21:59:25 +0900 Subject: [PATCH] Reopen log file when rotation is detected In the previous vesions, there was no way to detect log rotation event when log file was rotated by external tool (logrotate) It causes that DaemonLogger continues to write logs to rotated file even even when it's already renamed or removed, it's not expected behavior. This commit fixes by reopening log file when inode is changed. Signed-off-by: Takuro Ashie --- lib/serverengine/daemon_logger.rb | 37 +++++++++++++++++++++++++++++++ spec/daemon_logger_spec.rb | 25 +++++++++++++++++++++ 2 files changed, 62 insertions(+) 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