lib/backup/logger.rb
require "backup/logger/console"
require "backup/logger/logfile"
require "backup/logger/syslog"
require "backup/logger/fog_adapter"
module Backup
class Logger
class Config
class Logger < Struct.new(:class, :options)
def enabled?
options.enabled?
end
end
class DSL < Struct.new(:ignores, :console, :logfile, :syslog)
def ignore_warning(str_or_regexp)
ignores << str_or_regexp
end
end
attr_reader :ignores, :loggers, :dsl
def initialize
@ignores = []
@loggers = [
Logger.new(Console, Console::Options.new),
Logger.new(Logfile, Logfile::Options.new),
Logger.new(Syslog, Syslog::Options.new)
]
@dsl = DSL.new(ignores, *loggers.map(&:options))
end
end
##
# All messages sent to the Logger are stored in Logger.messages
# and sent to all enabled logger's #log method as Message objects.
class Message < Struct.new(:time, :level, :lines)
##
# Returns an Array of the message lines in the following format:
#
# [YYYY/MM/DD HH:MM:SS][level] message line text
def formatted_lines
timestamp = time.strftime("%Y/%m/%d %H:%M:%S")
lines.map { |line| "[#{timestamp}][#{level}] #{line}" }
end
def matches?(ignores)
text = lines.join("\n")
ignores.any? do |obj|
obj.is_a?(Regexp) ? text.match(obj) : text.include?(obj)
end
end
end
class << self
extend Forwardable
def_delegators :logger,
:start!, :abort!, :info, :warn, :error,
:messages, :has_warnings?, :has_errors?
##
# Allows the Logger to be configured.
#
# # shown with their default values
# Backup::Logger.configure do
# # Console options:
# console.quiet = false
#
# # Logfile options:
# logfile.enabled = true
# logfile.log_path = 'log'
# logfile.max_bytes = 500_000
#
# # Syslog options:
# syslog.enabled = false
# syslog.ident = 'backup'
# syslog.options = Syslog::LOG_PID
# syslog.facility = Syslog::LOG_LOCAL0
# syslog.info = Syslog::LOG_INFO
# syslog.warn = Syslog::LOG_WARNING
# syslog.error = Syslog::LOG_ERR
#
# # Ignore Warnings:
# # Converts :warn level messages to level :info
# ignore_warning 'that contains this string'
# ignore_warning /that matches this regexp/
# end
#
# See each Logger's Option class for details.
# @see Console::Options
# @see Logfile::Options
# @see Syslog::Options
def configure(&block)
config.dsl.instance_eval(&block)
end
##
# Called after each backup model/trigger has been performed.
def clear!
@logger = nil
logger.start!
end
private
def config
@config ||= Config.new
end
def logger
@logger ||= new(config)
end
def reset!
@config = @logger = nil
end
end
MUTEX = Mutex.new
##
# Returns an Array of Message objects for all logged messages received.
# These are used to attach log files to Mail notifications.
attr_reader :messages
def initialize(config)
@config = config
@messages = []
@loggers = []
@has_warnings = @has_errors = false
end
##
# Sends a message to the Logger using the specified log level.
# +obj+ may be any Object that responds to #to_s (i.e. an Exception)
[:info, :warn, :error].each do |level|
define_method level do |obj|
MUTEX.synchronize { log(obj, level) }
end
end
##
# Returns true if any +:warn+ level messages have been received.
def has_warnings?
@has_warnings
end
##
# Returns true if any +:error+ level messages have been received.
def has_errors?
@has_errors
end
##
# The Logger is available as soon as Backup is loaded, and stores all
# messages it receives. Since the Logger may be configured via the
# command line and/or the user's +config.rb+, no messages are sent
# until configuration can be completed. (see CLI#perform)
#
# Once configuration is completed, this method is called to activate
# all enabled loggers and send them any messages that have been received
# up to this point. From this point onward, these loggers will be sent
# all messages as soon as they're received.
def start!
@config.loggers.each do |logger|
@loggers << logger.class.new(logger.options) if logger.enabled?
end
messages.each do |message|
@loggers.each { |logger| logger.log(message) }
end
end
##
# If errors are encountered by Backup::CLI while preparing to perform
# the backup jobs, this method is called to dump all messages to the
# console before Backup exits.
def abort!
console = Console.new
console.log(messages.shift) until messages.empty?
end
private
def log(obj, level)
message = Message.new(Time.now.utc, level, obj.to_s.split("\n"))
if message.level == :warn && message.matches?(@config.ignores)
message.level = :info
end
@has_warnings ||= message.level == :warn
@has_errors ||= message.level == :error
messages << message
@loggers.each { |logger| logger.log(message) }
end
end
end