123456789_123456789_123456789_123456789_123456789_

Class: Logger::LogDevice

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
self, MonitorMixin, Period
Inherits: Object
Defined in: lib/logger/log_device.rb

Overview

Device used for logging messages.

Constant Summary

Period - Included

SiD

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Period - Included

Constructor Details

.new(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: false, reraise_write_errors: []) ⇒ LogDevice

[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 14

def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: false, reraise_write_errors: [])
  @dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil
  @binmode = binmode
  @reraise_write_errors = reraise_write_errors
  mon_initialize
  set_dev(log)
  if @filename
    @shift_age = shift_age || 7
    @shift_size = shift_size || 1048576
    @shift_period_suffix = shift_period_suffix || '%Y%m%d'

    unless @shift_age.is_a?(Integer)
      base_time = @dev.respond_to?(:stat) ? @dev.stat.mtime : Time.now
      @next_rotate_time = next_rotate_time(base_time, @shift_age)
    end
  end
end

Instance Attribute Details

#dev (readonly)

[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 10

attr_reader :dev

#filename (readonly)

[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 11

attr_reader :filename

Instance Method Details

#add_log_header(file) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 143

def add_log_header(file)
  file.write(
    "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
  ) if file.size == 0
end

#check_shift_log (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 149

def check_shift_log
  if @shift_age.is_a?(Integer)
    # Note: always returns false if '0'.
    if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
      lock_shift_log { shift_log_age }
    end
  else
    now = Time.now
    if now >= @next_rotate_time
      @next_rotate_time = next_rotate_time(now, @shift_age)
      lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) }
    end
  end
end

#close

[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 43

def close
  begin
    synchronize do
      @dev.close rescue nil
    end
  rescue Exception
    @dev.close rescue nil
  end
end

#create_logfile(filename) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 119

def create_logfile(filename)
  begin
    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
    open_logfile(filename)
  end
end

#fixup_mode(dev, filename) (private)

This method is for internal use only.

See additional method definition at line 91.

[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 95

def fixup_mode(dev, filename)
  dev
end

#handle_write_errors(mesg) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 135

def handle_write_errors(mesg)
  yield
rescue *@reraise_write_errors
  raise
rescue
  warn("log #{mesg} failed. #{$!}")
end

#lock_shift_log (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 164

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
        # 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 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

#open_logfile(filename) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 106

def open_logfile(filename)
  begin
    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

#reopen(log = nil)

[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 53

def reopen(log = nil)
  # reopen the same filename if no argument, do nothing for IO
  log ||= @filename if @filename
  if log
    synchronize do
      if @filename and @dev
        @dev.close rescue nil # close only file opened by Logger
        @filename = nil
      end
      set_dev(log)
    end
  end
  self
end

#set_dev(log) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 76

def set_dev(log)
  if log.respond_to?(:write) and log.respond_to?(:close)
    @dev = log
    if log.respond_to?(:path) and path = log.path
      if File.exist?(path)
        @filename = path
      end
    end
  else
    @dev = open_logfile(log)
    @filename = log
  end
end

#shift_log_age (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 193

def shift_log_age
  (@shift_age-3).downto(0) do |i|
    if FileTest.exist?("#{@filename}.#{i}")
      File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
    end
  end
  @dev.close rescue nil
  File.rename("#{@filename}", "#{@filename}.0")
  @dev = create_logfile(@filename)
  return true
end

#shift_log_period(period_end) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 205

def shift_log_period(period_end)
  suffix = period_end.strftime(@shift_period_suffix)
  age_file = "#{@filename}.#{suffix}"
  if FileTest.exist?(age_file)
    # try to avoid filename crash caused by Timestamp change.
    idx = 0
    # .99 can be overridden; avoid too much file search with 'loop do'
    while idx < 100
      idx += 1
      age_file = "#{@filename}.#{suffix}.#{idx}"
      break unless FileTest.exist?(age_file)
    end
  end
  @dev.close rescue nil
  File.rename("#{@filename}", age_file)
  @dev = create_logfile(@filename)
  return true
end

#write(message)

[ GitHub ]

  
# File 'lib/logger/log_device.rb', line 32

def write(message)
  handle_write_errors("writing") do
    synchronize do
      if @shift_age and @dev.respond_to?(:stat)
        handle_write_errors("shifting") {check_shift_log}
      end
      handle_write_errors("writing") {@dev.write(message)}
    end
  end
end