[ruby/logger] Enable log file rotation on Windows
authorNobuyoshi Nakada <[email protected]>
Wed, 9 Oct 2024 05:05:57 +0000 (9 14:05 +0900)
committergit <[email protected]>
Wed, 4 Dec 2024 07:45:39 +0000 (4 07:45 +0000)
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

lib/logger/log_device.rb
test/logger/test_logdevice.rb

index a5c3759..4c46278 100644 (file)
@@ -67,6 +67,12 @@ class Logger
 
   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
@@ -77,34 +83,54 @@ class Logger
         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
@@ -135,40 +161,33 @@ class Logger
       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
@@ -203,3 +222,15 @@ class Logger
     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
index 8f1c155..610905e 100644 (file)
@@ -452,7 +452,7 @@ class TestLogDevice < Test::Unit::TestCase
     end
   ensure
     logdev0.close
-  end unless /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os']
+  end
 
   def test_shifting_midnight
     Dir.mktmpdir do |tmpdir|