This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
require 'monitor'
|
||||
|
||||
module Yell #:nodoc:
|
||||
module Adapters #:nodoc:
|
||||
|
||||
# This class provides the basic interface for all allowed operations on any
|
||||
# adapter implementation. Other adapters should inherit from it for the methods
|
||||
# used by the {Yell::Logger}.
|
||||
#
|
||||
# Writing your own adapter is really simple. Inherit from the base class and use
|
||||
# the `setup`, `write` and `close` methods. Yell requires the `write` method to be
|
||||
# specified (`setup` and `close` are optional).
|
||||
#
|
||||
#
|
||||
# The following example shows how to define a basic Adapter to format and print
|
||||
# log events to STDOUT:
|
||||
#
|
||||
# class PutsAdapter < Yell::Adapters::Base
|
||||
# include Yell::Formatter::Helpers
|
||||
#
|
||||
# setup do |options|
|
||||
# self.format = options[:format]
|
||||
# end
|
||||
#
|
||||
# write do |event|
|
||||
# message = format.call(event)
|
||||
#
|
||||
# STDOUT.puts message
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# After the Adapter has been written, we need to register it to Yell:
|
||||
#
|
||||
# Yell::Adapters.register :puts, PutsAdapter
|
||||
#
|
||||
# Now, we can use it like so:
|
||||
#
|
||||
# logger = Yell.new :puts
|
||||
# logger.info "Hello World!"
|
||||
class Base < Monitor
|
||||
include Yell::Helpers::Base
|
||||
include Yell::Helpers::Level
|
||||
|
||||
class << self
|
||||
# Setup your adapter with this helper method.
|
||||
#
|
||||
# @example
|
||||
# setup do |options|
|
||||
# @file_handle = File.new( '/dev/null', 'w' )
|
||||
# end
|
||||
def setup( &block )
|
||||
compile!(:setup!, &block)
|
||||
end
|
||||
|
||||
# Define your write method with this helper.
|
||||
#
|
||||
# @example Printing messages to file
|
||||
# write do |event|
|
||||
# @file_handle.puts event.message
|
||||
# end
|
||||
def write( &block )
|
||||
compile!(:write!, &block)
|
||||
end
|
||||
|
||||
# Define your open method with this helper.
|
||||
#
|
||||
# @example Open a file handle
|
||||
# open do
|
||||
# @stream = ::File.open( 'test.log', ::File::WRONLY|::File::APPEND|::File::CREAT )
|
||||
# end
|
||||
def open( &block )
|
||||
compile!(:open!, &block)
|
||||
end
|
||||
|
||||
# Define your close method with this helper.
|
||||
#
|
||||
# @example Closing a file handle
|
||||
# close do
|
||||
# @stream.close
|
||||
# end
|
||||
def close( &block )
|
||||
compile!(:close!, &block)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Pretty funky code block, I know but here is what it basically does:
|
||||
#
|
||||
# @example
|
||||
# compile! :write! do |event|
|
||||
# puts event.message
|
||||
# end
|
||||
#
|
||||
# # Is actually defining the `:write!` instance method with a call to super:
|
||||
#
|
||||
# def write!( event )
|
||||
# puts event.method
|
||||
# super
|
||||
# end
|
||||
def compile!( name, &block )
|
||||
# Get the already defined method
|
||||
m = instance_method( name )
|
||||
|
||||
# Create a new method with leading underscore
|
||||
define_method("_#{name}", &block)
|
||||
_m = instance_method("_#{name}")
|
||||
remove_method("_#{name}")
|
||||
|
||||
# Define instance method
|
||||
define!(name, _m, m, &block)
|
||||
end
|
||||
|
||||
# Define instance method by given name and call the unbound
|
||||
# methods in order with provided block.
|
||||
def define!( name, _m, m, &block )
|
||||
if block.arity == 0
|
||||
define_method(name) do
|
||||
_m.bind(self).call
|
||||
m.bind(self).call
|
||||
end
|
||||
else
|
||||
define_method(name) do |*args|
|
||||
_m.bind(self).call(*args)
|
||||
m.bind(self).call(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Initializes a new Adapter.
|
||||
#
|
||||
# You should not overload the constructor, use #setup instead.
|
||||
def initialize( options = {}, &block )
|
||||
super() # init the monitor superclass
|
||||
|
||||
reset!
|
||||
setup!(options)
|
||||
|
||||
# eval the given block
|
||||
block.arity > 0 ? block.call(self) : instance_eval(&block) if block_given?
|
||||
end
|
||||
|
||||
# The main method for calling the adapter.
|
||||
#
|
||||
# The method receives the log `event` and determines whether to
|
||||
# actually write or not.
|
||||
def write( event )
|
||||
synchronize { write!(event) } if write?(event)
|
||||
rescue Exception => e
|
||||
# make sure the adapter is closed and re-raise the exception
|
||||
synchronize { close }
|
||||
|
||||
raise(e)
|
||||
end
|
||||
|
||||
# Close the adapter (stream, connection, etc).
|
||||
#
|
||||
# Adapter classes should provide their own implementation
|
||||
# of this method.
|
||||
def close
|
||||
close!
|
||||
end
|
||||
|
||||
# Get a pretty string representation of the adapter, including
|
||||
def inspect
|
||||
inspection = inspectables.map { |m| "#{m}: #{send(m).inspect}" }
|
||||
"#<#{self.class.name} #{inspection * ', '}>"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Setup the adapter instance.
|
||||
#
|
||||
# Adapter classes should provide their own implementation
|
||||
# of this method (if applicable).
|
||||
def setup!( options )
|
||||
self.level = Yell.__fetch__(options, :level)
|
||||
end
|
||||
|
||||
# Perform the actual write.
|
||||
#
|
||||
# Adapter classes must provide their own implementation
|
||||
# of this method.
|
||||
def write!( event )
|
||||
# Not implemented
|
||||
end
|
||||
|
||||
# Perform the actual open.
|
||||
#
|
||||
# Adapter classes should provide their own implementation
|
||||
# of this method.
|
||||
def open!
|
||||
# Not implemented
|
||||
end
|
||||
|
||||
# Perform the actual close.
|
||||
#
|
||||
# Adapter classes should provide their own implementation
|
||||
# of this method.
|
||||
def close!
|
||||
# Not implemented
|
||||
end
|
||||
|
||||
# Determine whether to write at the given severity.
|
||||
#
|
||||
# @example
|
||||
# write? Yell::Event.new( 'INFO', 'Hello Wold!' )
|
||||
#
|
||||
# @param [Yell::Event] event The log event
|
||||
#
|
||||
# @return [Boolean] true or false
|
||||
def write?( event )
|
||||
level.nil? || level.at?(event.level)
|
||||
end
|
||||
|
||||
# Get an array of inspected attributes for the adapter.
|
||||
def inspectables
|
||||
[:level]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
module Yell #:nodoc:
|
||||
module Adapters #:nodoc:
|
||||
|
||||
# The +Datefile+ adapter is similar to the +File+ adapter. However, it
|
||||
# rotates the file at midnight (by default).
|
||||
class Datefile < Yell::Adapters::File
|
||||
|
||||
# The default date pattern, e.g. "19820114" (14 Jan 1982)
|
||||
DefaultDatePattern = "%Y%m%d"
|
||||
|
||||
# Metadata
|
||||
Header = lambda { |date, pattern| "# -*- #{date.iso8601} (#{date.to_f}) [#{pattern}] -*-" }
|
||||
HeaderRegexp = /^# -\*- (.+) \((\d+\.\d+)\) \[(.+)\] -\*-$/
|
||||
|
||||
# The pattern to be used for the files
|
||||
#
|
||||
# @example
|
||||
# date_pattern = "%Y%m%d" # default
|
||||
# date_pattern = "%Y-week-%V"
|
||||
attr_accessor :date_pattern
|
||||
|
||||
# Tell the adapter to create a symlink onto the currently
|
||||
# active (timestamped) file. Upon rollover, the symlink is
|
||||
# set to the newly created file, and so on.
|
||||
#
|
||||
# @example
|
||||
# symlink = true
|
||||
attr_accessor :symlink
|
||||
|
||||
# Set the amount of logfiles to keep when rolling over.
|
||||
# By default, no files will be cleaned up.
|
||||
#
|
||||
# @example Keep the last 5 logfiles
|
||||
# keep = 5
|
||||
# keep = '10'
|
||||
#
|
||||
# @example Do not clean up any files
|
||||
# keep = 0
|
||||
attr_accessor :keep
|
||||
|
||||
# You can suppress the first line of the logfile that contains
|
||||
# the metadata. This is important upon rollover, because on *nix
|
||||
# systems, it is not possible to determine the creation time of a file,
|
||||
# on the last access time. The header compensates this.
|
||||
#
|
||||
# @example
|
||||
# header = false
|
||||
attr_accessor :header
|
||||
|
||||
|
||||
private
|
||||
|
||||
# @overload setup!( options )
|
||||
def setup!( options )
|
||||
self.header = Yell.__fetch__(options, :header, default: true)
|
||||
self.date_pattern = Yell.__fetch__(options, :date_pattern, default: DefaultDatePattern)
|
||||
self.keep = Yell.__fetch__(options, :keep, default: false)
|
||||
self.symlink = Yell.__fetch__(options, :symlink, default: true)
|
||||
|
||||
@original_filename = ::File.expand_path(Yell.__fetch__(options, :filename, default: default_filename))
|
||||
options[:filename] = @original_filename
|
||||
|
||||
@date = Time.now
|
||||
@date_strftime = @date.strftime(date_pattern)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# @overload write!( event )
|
||||
def write!( event )
|
||||
# do nothing when not closing
|
||||
return super unless close?
|
||||
close
|
||||
|
||||
# exit when file ready present
|
||||
return super if ::File.exist?(@filename)
|
||||
|
||||
header! if header?
|
||||
symlink! if symlink?
|
||||
cleanup! if cleanup?
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# @overload close!
|
||||
def close!
|
||||
@filename = filename_for(@date)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# Determine whether to close the file handle or not.
|
||||
#
|
||||
# It is based on the `:date_pattern` (can be passed as option upon initialize).
|
||||
# If the current time hits the pattern, it closes the file stream.
|
||||
#
|
||||
# @return [Boolean] true or false
|
||||
def close?
|
||||
_date = Time.now
|
||||
_date_strftime = _date.strftime(date_pattern)
|
||||
|
||||
if @stream.nil? or _date_strftime != @date_strftime
|
||||
@date, @date_strftime = _date, _date_strftime
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Removes old logfiles of the same date pattern.
|
||||
#
|
||||
# By reading the header of the files that match the date pattern, the
|
||||
# adapter determines whether to remove them or not. If no header is present,
|
||||
# it makes the best guess by checking the last access time (which may result
|
||||
# in false cleanups).
|
||||
def cleanup!
|
||||
files = Dir[ @original_filename.sub(/(\.\w+)?$/, ".*\\1") ].sort.select do |file|
|
||||
_, pattern = header_from(file)
|
||||
|
||||
# Select if the date pattern is nil (no header info available within the file) or
|
||||
# when the pattern matches.
|
||||
pattern.nil? || pattern == self.date_pattern
|
||||
end
|
||||
|
||||
::File.unlink( *files[0..-keep-1] )
|
||||
end
|
||||
|
||||
# Cleanup old logfiles?
|
||||
#
|
||||
# @return [Boolean] true or false
|
||||
def cleanup?
|
||||
!!keep && keep.to_i > 0
|
||||
end
|
||||
|
||||
# Symlink the current filename to the original one.
|
||||
def symlink!
|
||||
# do nothing, because symlink is already correct
|
||||
return if ::File.symlink?(@original_filename) && ::File.readlink(@original_filename) == @filename
|
||||
|
||||
::File.unlink(@original_filename) if ::File.exist?(@original_filename) || ::File.symlink?(@original_filename)
|
||||
::File.symlink(@filename, @original_filename)
|
||||
end
|
||||
|
||||
# Symlink the original filename?
|
||||
#
|
||||
# @return [Boolean] true or false
|
||||
def symlink?
|
||||
!!symlink
|
||||
end
|
||||
|
||||
# Write the header information into the file
|
||||
def header!
|
||||
stream.puts( Header.call(@date, date_pattern) )
|
||||
end
|
||||
|
||||
# Write header into the file?
|
||||
#
|
||||
# @return [Boolean] true or false
|
||||
def header?
|
||||
!!header
|
||||
end
|
||||
|
||||
# Sets the filename with the `:date_pattern` appended to it.
|
||||
def filename_for( date )
|
||||
@original_filename.sub(/(\.\w+)?$/, ".#{date.strftime(date_pattern)}\\1")
|
||||
end
|
||||
|
||||
# Fetch the header form the file
|
||||
def header_from( file )
|
||||
if m = ::File.open(file, &:readline).match(HeaderRegexp)
|
||||
# in case there is a Header present, we can just read from it
|
||||
[ Time.at(m[2].to_f), m[3] ]
|
||||
else
|
||||
# In case there is no header: we need to take a good guess
|
||||
#
|
||||
# Since the pattern can not be determined, we will just return the Posix ctime.
|
||||
# That is NOT the creatint time, so the value will potentially be wrong!
|
||||
[::File.ctime(file), nil]
|
||||
end
|
||||
end
|
||||
|
||||
# @overload inspectables
|
||||
def inspectables
|
||||
super.concat %i[date_pattern header keep symlink]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
module Yell #:nodoc:
|
||||
module Adapters #:nodoc:
|
||||
|
||||
# The +File+ adapter is the most basic. As one would expect, it's used
|
||||
# for logging into files.
|
||||
class File < Yell::Adapters::Io
|
||||
|
||||
private
|
||||
|
||||
# @overload setup!( options )
|
||||
def setup!( options )
|
||||
@filename = ::File.expand_path(Yell.__fetch__(options, :filename, default: default_filename))
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# @overload open!
|
||||
def open!
|
||||
@stream = ::File.open(@filename, ::File::WRONLY|::File::APPEND|::File::CREAT)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def default_filename #:nodoc:
|
||||
logdir = ::File.expand_path("log")
|
||||
|
||||
::File.expand_path(::File.directory?(logdir) ? "#{logdir}/#{Yell.env}.log" : "#{Yell.env}.log")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
module Yell #:nodoc:
|
||||
module Adapters #:nodoc:
|
||||
class Io < Yell::Adapters::Base
|
||||
include Yell::Helpers::Formatter
|
||||
|
||||
# The possible unix log colors
|
||||
TTYColors = {
|
||||
0 => "\033[1;32m", # green
|
||||
1 => "\033[0m", # normal
|
||||
2 => "\033[1;33m", # yellow
|
||||
3 => "\033[1;31m", # red
|
||||
4 => "\033[1;35m", # magenta
|
||||
5 => "\033[1;36m", # cyan
|
||||
-1 => "\033[0m" # normal
|
||||
}
|
||||
|
||||
# Sets the “sync mode” to true or false.
|
||||
#
|
||||
# When true (default), every log event is immediately written to the file.
|
||||
# When false, the log event is buffered internally.
|
||||
attr_accessor :sync
|
||||
|
||||
# Sets colored output on or off (default off)
|
||||
#
|
||||
# @example Enable colors
|
||||
# colors = true
|
||||
#
|
||||
# @example Disable colors
|
||||
# colors = false
|
||||
attr_accessor :colors
|
||||
|
||||
# Shortcut to enable colors.
|
||||
#
|
||||
# @example
|
||||
# colorize!
|
||||
def colorize!; @colors = true; end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# @overload setup!( options )
|
||||
def setup!( options )
|
||||
@stream = nil
|
||||
|
||||
self.colors = Yell.__fetch__(options, :colors, default: false)
|
||||
self.formatter = Yell.__fetch__(options, :format, :formatter)
|
||||
self.sync = Yell.__fetch__(options, :sync, default: true)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# @overload write!( event )
|
||||
def write!( event )
|
||||
message = formatter.call(event)
|
||||
|
||||
# colorize if applicable
|
||||
if colors and color = TTYColors[event.level]
|
||||
message = color + message + TTYColors[-1]
|
||||
end
|
||||
|
||||
stream.syswrite(message)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# @overload open!
|
||||
def open!
|
||||
@stream.sync = self.sync if @stream.respond_to?(:sync)
|
||||
@stream.flush if @stream.respond_to?(:flush)
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# @overload close!
|
||||
def close!
|
||||
@stream.close if @stream.respond_to?(:close)
|
||||
@stream = nil
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# The IO stream
|
||||
#
|
||||
# Adapter classes should provide their own implementation
|
||||
# of this method.
|
||||
def stream
|
||||
synchronize { open! if @stream.nil?; @stream }
|
||||
end
|
||||
|
||||
# @overload inspectables
|
||||
def inspectables
|
||||
super.concat [:formatter, :colors, :sync]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
module Yell #:nodoc:
|
||||
module Adapters #:nodoc:
|
||||
|
||||
class Stdout < Yell::Adapters::Io
|
||||
|
||||
private
|
||||
|
||||
# @overload open!
|
||||
def open!
|
||||
@stream = $stdout.clone
|
||||
super
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class Stderr < Yell::Adapters::Io
|
||||
|
||||
private
|
||||
|
||||
# @overload open!
|
||||
def open!
|
||||
@stream = $stderr.clone
|
||||
super
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user