1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
|
require "time" # httpdate
module DataObjects
module Logging
def log(message)
logger = driver_namespace.logger
if logger.level <= DataObjects::Logger::LEVELS[:debug]
message = "(%.6f) %s" % [message.duration / 1000000.0, message.query]
logger.debug message
end
end
end
class << self
# The global logger for DataObjects
attr_accessor :logger
end
# ==== Public DataObjects Logger API
#
# Logger taken from Merb :)
#
# To replace an existing logger with a new one:
# DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
#
# Available logging levels are
# DataObjects::Logger::{ Fatal, Error, Warn, Info, Debug }
#
# Logging via:
# DataObjects.logger.fatal(message<String>)
# DataObjects.logger.error(message<String>)
# DataObjects.logger.warn(message<String>)
# DataObjects.logger.info(message<String>)
# DataObjects.logger.debug(message<String>)
#
# Flush the buffer to
# DataObjects.logger.flush
#
# Remove the current log object
# DataObjects.logger.close
#
# ==== Private DataObjects Logger API
#
# To initialize the logger you create a new object, proxies to set_log.
# DataObjects::Logger.new(log{String, IO},level{Symbol, String})
#
# Logger will not create the file until something is actually logged
# This avoids file creation on DataObjects init when it creates the
# default logger.
class Logger
# Use asynchronous I/O?
attr_accessor :aio
# delimiter to use between message sections
attr_accessor :delimiter
# a symbol representing the log level from {:off, :fatal, :error, :warn, :info, :debug}
attr_reader :level
# Direct access to the buffer
attr_reader :buffer
# The name of the log file
attr_reader :log
Message = Struct.new(:query, :start, :duration)
#
# Ruby (standard) logger levels:
# off: absolutely nothing
# fatal: an unhandleable error that results in a program crash
# error: a handleable error condition
# warn: a warning
# info: generic (useful) information about system operation
# debug: low-level information for developers
#
# DataObjects::Logger::LEVELS[:off, :fatal, :error, :warn, :info, :debug]
LEVELS =
{
:off => 99999,
:fatal => 7,
:error => 6,
:warn => 4,
:info => 3,
:debug => 0
}
# Set the log level (use the level symbols as documented)
def level=(new_level)
@level = LEVELS[new_level.to_sym]
reset_methods(:close)
end
private
# The idea here is that instead of performing an 'if' conditional check on
# each logging we do it once when the log object is setup
def set_write_method
@log.instance_eval do
# Determine if asynchronous IO can be used
def aio?
@aio = !RUBY_PLATFORM.match(/java|mswin/) &&
!(@log == STDOUT) &&
@log.respond_to?(:write_nonblock)
end
# Define the write method based on if aio an be used
undef write_method if defined? write_method
if aio?
alias :write_method :write_nonblock
else
alias :write_method :write
end
end
end
def initialize_log(log)
close if @log # be sure that we don't leave open files laying around.
@log = log || "log/dm.log"
end
def reset_methods(o_or_c)
if o_or_c == :open
alias internal_push push_opened
elsif o_or_c == :close
alias internal_push push_closed
end
end
def push_opened(string)
message = Time.now.httpdate
message << delimiter
message << string
message << "\n" unless message[-1] == ?\n
@buffer << message
flush # Force a flush for now until we figure out where we want to use the buffering.
end
def push_closed(string)
unless @log.respond_to?(:write)
log = Pathname(@log)
log.dirname.mkpath
@log = log.open('a')
@log.sync = true
end
set_write_method
reset_methods(:open)
push(string)
end
alias internal_push push_closed
def prep_msg(message, level)
level << delimiter << message
end
public
# To initialize the logger you create a new object, proxies to set_log.
# DataObjects::Logger.new(log{String, IO},level{Symbol, String})
#
# @param log<IO,String> either an IO object or a name of a logfile.
# @param log_level<String> the message string to be logged
# @param delimiter<String> delimiter to use between message sections
# @param log_creation<Boolean> log that the file is being created
def initialize(*args)
set_log(*args)
end
# To replace an existing logger with a new one:
# DataObjects::Logger.set_log(log{String, IO},level{Symbol, String})
#
#
# @param log<IO,String> either an IO object or a name of a logfile.
# @param log_level<Symbol> a symbol representing the log level from
# {:off, :fatal, :error, :warn, :info, :debug}
# @param delimiter<String> delimiter to use between message sections
# @param log_creation<Boolean> log that the file is being created
def set_log(log, log_level = :off, delimiter = " ~ ", log_creation = false)
delimiter ||= " ~ "
if log_level && LEVELS[log_level.to_sym]
self.level = log_level.to_sym
else
self.level = :debug
end
@buffer = []
@delimiter = delimiter
initialize_log(log)
DataObjects.logger = self
self.info("Logfile created") if log_creation
end
# Flush the entire buffer to the log object.
# DataObjects.logger.flush
#
def flush
return unless @buffer.size > 0
@log.write_method(@buffer.slice!(0..-1).join)
end
# Close and remove the current log object.
# DataObjects.logger.close
#
def close
flush
@log.close if @log.respond_to?(:close)
@log = nil
end
# Appends a string and log level to logger's buffer.
#
# Note that the string is discarded if the string's log level less than the
# logger's log level.
#
# Note that if the logger is aio capable then the logger will use
# non-blocking asynchronous writes.
#
# @param level<Fixnum> the logging level as an integer
# @param string<String> the message string to be logged
def push(string)
internal_push(string)
end
alias << push
# Generate the following logging methods for DataObjects.logger as described
# in the API:
# :fatal, :error, :warn, :info, :debug
# :off only gets an off? method
LEVELS.each_pair do |name, number|
unless name.to_sym == :off
class_eval <<-EOS, __FILE__, __LINE__
# DOC
def #{name}(message)
self.<<( prep_msg(message, "#{name}") ) if #{name}?
end
EOS
end
class_eval <<-EOS, __FILE__, __LINE__
# DOC
def #{name}?
#{number} >= level
end
EOS
end
end # class Logger
end # module DataObjects
|