about summary refs log tree commit homepage
path: root/lib/yahns/log.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/yahns/log.rb')
-rw-r--r--lib/yahns/log.rb73
1 files changed, 73 insertions, 0 deletions
diff --git a/lib/yahns/log.rb b/lib/yahns/log.rb
new file mode 100644
index 0000000..59baa85
--- /dev/null
+++ b/lib/yahns/log.rb
@@ -0,0 +1,73 @@
+# -*- encoding: binary -*-
+# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
+# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# logging-related utility functions for all of yahns
+module Yahns::Log # :nodoc:
+  def self.exception(logger, prefix, exc)
+    message = exc.message
+    message = message.dump if /[[:cntrl:]]/ =~ message # prevent code injection
+    logger.error "#{prefix}: #{message} (#{exc.class})"
+    exc.backtrace.each { |line| logger.error(line) }
+  end
+
+  def self.is_log?(fp)
+    append_flags = IO::WRONLY | IO::APPEND
+
+    ! fp.closed? &&
+      fp.stat.file? &&
+      fp.sync &&
+      (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
+  rescue IOError, Errno::EBADF
+    false
+  end
+
+  def self.chown_all(uid, gid)
+    ObjectSpace.each_object(File) do |fp|
+      fp.chown(uid, gid) if is_log?(fp)
+    end
+  end
+
+  # This reopens ALL logfiles in the process that have been rotated
+  # using logrotate(8) (without copytruncate) or similar tools.
+  # A +File+ object is considered for reopening if it is:
+  #   1) opened with the O_APPEND and O_WRONLY flags
+  #   2) the current open file handle does not match its original open path
+  #   3) unbuffered (as far as userspace buffering goes, not O_SYNC)
+  # Returns the number of files reopened
+  def self.reopen_all
+    to_reopen = []
+    nr = 0
+    ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
+
+    to_reopen.each do |fp|
+      orig_st = begin
+        fp.stat
+      rescue IOError, Errno::EBADF
+        next
+      end
+
+      begin
+        b = File.stat(fp.path)
+        next if orig_st.ino == b.ino && orig_st.dev == b.dev
+      rescue Errno::ENOENT
+      end
+
+      begin
+        File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
+        fp.sync = true
+        new_st = fp.stat
+
+        # this should only happen in the master:
+        if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
+          fp.chown(orig_st.uid, orig_st.gid)
+        end
+
+        nr += 1
+      rescue IOError, Errno::EBADF
+        # not much we can do...
+      end
+    end
+    nr
+  end
+end