Skip to content

Commit

Permalink
Merge pull request #18 from ruby/divide-classes-modules
Browse files Browse the repository at this point in the history
split logger classes/modules into separate files
  • Loading branch information
sonots authored Mar 18, 2019
2 parents 106ce55 + 1335a71 commit 5cce85a
Show file tree
Hide file tree
Showing 9 changed files with 312 additions and 297 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Or install it yourself as:

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `lib/logger.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `lib/logger/version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

## Advanced Development Tips

Expand Down
296 changes: 5 additions & 291 deletions lib/logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
# A simple system for logging messages. See Logger for more documentation.

require 'monitor'
require 'logger/version'
require 'logger/formatter'
require 'logger/log_device'
require 'logger/severity'
require 'logger/errors'

# == Description
#
Expand Down Expand Up @@ -224,7 +229,6 @@
# })
#
class Logger
VERSION = "1.3.0"
_, name, rev = %w$Id$
if name
name = name.chomp(",v")
Expand All @@ -234,27 +238,6 @@ class Logger
rev ||= "v#{VERSION}"
ProgName = "#{name}/#{rev}".freeze

class Error < RuntimeError # :nodoc:
end
# not used after 1.2.7. just for compat.
class ShiftingError < Error # :nodoc:
end

# Logging severity.
module Severity
# Low-level information, mostly for developers.
DEBUG = 0
# Generic (useful) information about system operation.
INFO = 1
# A warning.
WARN = 2
# A handleable error condition.
ERROR = 3
# An unhandleable error that results in a program crash.
FATAL = 4
# An unknown message that should always be logged.
UNKNOWN = 5
end
include Severity

# Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
Expand Down Expand Up @@ -596,273 +579,4 @@ def format_severity(severity)
def format_message(severity, datetime, progname, msg)
(@formatter || @default_formatter).call(severity, datetime, progname, msg)
end


# Default formatter for log messages.
class Formatter
Format = "%s, [%s#%d] %5s -- %s: %s\n".freeze

attr_accessor :datetime_format

def initialize
@datetime_format = nil
end

def call(severity, time, progname, msg)
Format % [severity[0..0], format_datetime(time), $$, severity, progname,
msg2str(msg)]
end

private

def format_datetime(time)
time.strftime(@datetime_format || "%Y-%m-%dT%H:%M:%S.%6N ".freeze)
end

def msg2str(msg)
case msg
when ::String
msg
when ::Exception
"#{ msg.message } (#{ msg.class })\n" <<
(msg.backtrace || []).join("\n")
else
msg.inspect
end
end
end

module Period
module_function

SiD = 24 * 60 * 60

def next_rotate_time(now, shift_age)
case shift_age
when 'daily'
t = Time.mktime(now.year, now.month, now.mday) + SiD
when 'weekly'
t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday)
when 'monthly'
t = Time.mktime(now.year, now.month, 1) + SiD * 32
return Time.mktime(t.year, t.month, 1)
else
return now
end
if t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero?
hour = t.hour
t = Time.mktime(t.year, t.month, t.mday)
t += SiD if hour > 12
end
t
end

def previous_period_end(now, shift_age)
case shift_age
when 'daily'
t = Time.mktime(now.year, now.month, now.mday) - SiD / 2
when 'weekly'
t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2)
when 'monthly'
t = Time.mktime(now.year, now.month, 1) - SiD / 2
else
return now
end
Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
end
end

# Device used for logging messages.
class LogDevice
include Period

attr_reader :dev
attr_reader :filename
include MonitorMixin

def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil)
@dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil
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

def write(message)
begin
synchronize do
if @shift_age and @dev.respond_to?(:stat)
begin
check_shift_log
rescue
warn("log shifting failed. #{$!}")
end
end
begin
@dev.write(message)
rescue
warn("log writing failed. #{$!}")
end
end
rescue Exception => ignored
warn("log writing failed. #{ignored}")
end
end

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

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

private

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

def open_logfile(filename)
begin
File.open(filename, (File::WRONLY | File::APPEND))
rescue Errno::ENOENT
create_logfile(filename)
end
end

def create_logfile(filename)
begin
logdev = File.open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL))
logdev.flock(File::LOCK_EX)
logdev.sync = true
add_log_header(logdev)
logdev.flock(File::LOCK_UN)
rescue Errno::EEXIST
# file is created by another process
logdev = open_logfile(filename)
logdev.sync = true
end
logdev
end

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

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

if /mswin|mingw/ =~ RUBY_PLATFORM
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. #{$!}")
else
sleep retry_sleep
retry_limit -= 1
retry_sleep *= 2
retry
end
end
rescue
warn("log rotation inter-process lock failed. #{$!}")
end
end

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

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
end
end
7 changes: 7 additions & 0 deletions lib/logger/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# not used after 1.2.7. just for compat.
class Logger
class Error < RuntimeError # :nodoc:
end
class ShiftingError < Error # :nodoc:
end
end
35 changes: 35 additions & 0 deletions lib/logger/formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class Logger
# Default formatter for log messages.
class Formatter
Format = "%s, [%s#%d] %5s -- %s: %s\n".freeze

attr_accessor :datetime_format

def initialize
@datetime_format = nil
end

def call(severity, time, progname, msg)
Format % [severity[0..0], format_datetime(time), $$, severity, progname,
msg2str(msg)]
end

private

def format_datetime(time)
time.strftime(@datetime_format || "%Y-%m-%dT%H:%M:%S.%6N ".freeze)
end

def msg2str(msg)
case msg
when ::String
msg
when ::Exception
"#{ msg.message } (#{ msg.class })\n" <<
(msg.backtrace || []).join("\n")
else
msg.inspect
end
end
end
end
Loading

0 comments on commit 5cce85a

Please sign in to comment.