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

Detect log rotation and release renamed files #106

Closed
wants to merge 5 commits into from
Closed
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
10 changes: 7 additions & 3 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ jobs:
fail-fast: false
matrix:
ruby: [ '3.0', '2.7', '2.6', '2.5' ]
os:
- ubuntu-latest
os: [ 'ubuntu-latest' ]
test-optional: [ true ]
include:
- ruby: '2.7'
os: ubuntu-latest
test-optional: false
name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
Expand All @@ -22,5 +26,5 @@ jobs:
CI: true
run: |
gem install bundler rake
bundle install --jobs 4 --retry 3
bundle install --jobs 4 --retry 3 ${{ matrix.test-optional && '--with inotify' || '' }}
bundle exec rake spec
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
source 'https://rubygems.org/'
gemspec
group :inotify, optional: true do
gem 'rb-inotify'
end
Empty file modified Rakefile
100644 → 100755
Empty file.
56 changes: 56 additions & 0 deletions lib/serverengine/daemon_logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# limitations under the License.
#
require 'logger'
require 'serverengine/utils'

module ServerEngine

Expand All @@ -36,6 +37,13 @@ def initialize(logdev, config={})

super(nil)

if ServerEngine::linux?
begin
require 'rb-inotify'
rescue LoadError
end
end

self.level = config[:log_level] || 'debug'
self.logdev = logdev
end
Expand All @@ -58,6 +66,7 @@ def logdev=(logdev)
old_file_dev.close if old_file_dev
@logdev = @file_dev
end
enable_watching_logdev(logdev) if defined?(INotify)
logdev
end

Expand All @@ -76,9 +85,56 @@ def add(severity, message = nil, progname = nil, &block)
end
progname ||= @progname
self << format_message(SEVERITY_FORMATS_[severity+1], Time.now, progname, message)
poll_pending_inotify if defined?(INotify)
true
end

def poll_pending_inotify
return unless ServerEngine::linux?
return unless @inotify

if IO.select([@inotify.to_io], [], [], 0)
@inotify.process
end
end

def enable_watching_logdev(logdev)
return unless ServerEngine::linux?

target = nil
if logdev.respond_to?(:filename)
target = logdev.filename
elsif logdev.respond_to?(:path)
target = logdev.path
elsif logdev.is_a?(String)
target = logdev
else
# ignore StringIO for some test cases
return
end
if target
@inotify.close if @inotify
@inotify = INotify::Notifier.new
def @inotify.readpartial(size)
@handle.read_nonblock(size)
rescue Errno::EBADF, Errno::EAGAIN, EWOULDBLOCK
nil
end
@inotify.watch(target, :move_self) do |event|
if @logdev.respond_to?(:filename)
@logdev.close
@logdev.reopen(@logdev.filename)
elsif @logdev.respond_to?(:path)
@logdev.close
@logdev.reopen(@logdev.path)
else
close
reopen
end
end
end
end

module Severity
include Logger::Severity
TRACE = -1
Expand Down
5 changes: 5 additions & 0 deletions lib/serverengine/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
module ServerEngine

IS_WINDOWS = /mswin|mingw/ === RUBY_PLATFORM
IS_LINUX = /linux/ === RUBY_PLATFORM
private_constant :IS_WINDOWS

def self.windows?
IS_WINDOWS
end

def self.linux?
IS_LINUX
end

module ClassMethods
def dump_uncaught_error(e)
STDERR.write "Unexpected error #{e}\n"
Expand Down
43 changes: 43 additions & 0 deletions spec/daemon_logger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,47 @@
$stderr = STDERR
stderr.should_not =~ /(log shifting failed|log writing failed|log rotation inter-process lock failed)/
end

def lsof(path)
IO.popen("lsof | grep #{path}") do |io|
io.read
end
end

it 'reopen log when path is renamed' do
pending "not supported except Linux with inotify" unless ServerEngine.linux? and defined?(INotify)

log = DaemonLogger.new("tmp/rotate.log", level: 'info')
File.rename("tmp/rotate.log", "tmp/rotate.log.1")
log.info '1' * 15
FileUtils.touch("tmp/rotate.log")
lsof("rotate.log").should_not include("rotate.log.1")
end

it 'reopen log when path is renamed by external process' do
pending "not supported except Linux with inotify" unless ServerEngine.linux? and defined?(INotify)

log = DaemonLogger.new("tmp/rotate.log", level: 'info')
pid1 = Process.fork do
File.rename("tmp/rotate.log", "tmp/rotate.log.1")
FileUtils.touch("tmp/rotate.log")
end
Process.waitpid pid1
log.info '1' * 15
lsof("rotate.log").should_not include("rotate.log.1")
end

it 'reopen logger when file is renamed' do
pending "not supported except Linux with inotify" unless ServerEngine.linux? and defined?(INotify)

log = DaemonLogger.new("tmp/dummy.log", level: 'info')
log.logdev = File.open("tmp/rotate.log", "w")
pid1 = Process.fork do
File.rename("tmp/rotate.log", "tmp/rotate.log.1")
FileUtils.touch("tmp/rotate.log")
end
Process.waitpid pid1
log.info '1' * 15
lsof("rotate.log").should_not include("rotate.log.1")
end
end