require 'vedeu/configuration/configuration' module Vedeu # Allows the creation of a lock-less log device. # class MonoLogger < Logger # Create a trappable Logger instance. # # @param logdev [String|IO] The filename (String) or IO object (typically # STDOUT, STDERR or an open file). # @param shift_age [] Number of old log files to keep, or frequency of # rotation (daily, weekly, monthly). # @param shift_size [] Maximum log file size (only applies when shift_age # is a number). # # @example # Logger.new(name, shift_age = 7, shift_size = 1048576) # Logger.new(name, shift_age = 'weekly') # def initialize(logdev, shift_age=nil, shift_size=nil) @level = DEBUG @default_formatter = Formatter.new @formatter = nil @logdev = nil if logdev @logdev = LocklessLogDevice.new(logdev) end end # Ensures we can always write to the log file by creating a lock-less # log device. class LocklessLogDevice < LogDevice # @param log [] # @return [] def initialize(log = nil) @dev = nil @filename = nil @shift_age = nil @shift_size = nil if log.respond_to?(:write) && log.respond_to?(:close) @dev = log else @dev = open_logfile(log) @dev.sync = true @filename = log end end # @param message [] # @return [] def write(message) @dev.write(message) rescue Exception => ignored warn("log writing failed. #{ignored}") end # @return [] def close @dev.close rescue nil end private # @param filename [] # @return [] def open_logfile(filename) if (FileTest.exist?(filename)) open(filename, (File::WRONLY | File::APPEND)) else logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT)) logdev.sync = true logdev end end end # LocklessLogDevice end # MonoLogger # Provides the ability to log anything to the Vedeu log file. # class Log class << self # Write a message to the Vedeu log file. # # @param message [String] The message you wish to emit to the log file, # useful for debugging. # @param force [Boolean] When evaluates to true will attempt to write to # the log file regardless of the Configuration setting. # @param type [Symbol] Colour code messages in the log file depending # on their source. # # @example # Vedeu.log(message: 'A useful debugging message: Error!') # # @return [TrueClass] def log(message:, force: false, type: :info) logger.debug([message_type(type), message]) if enabled? || force end # @return [TrueClass] def logger MonoLogger.new(log_file).tap do |log| log.formatter = proc do |_, time, _, message| formatted_message(message, time) end end end private # @return [String] def formatted_message(message, time = Time.now) [timestamp(time.utc.iso8601), message, "\n"].join end # @return [Boolean] def enabled? Vedeu::Configuration.debug? end # @return [String] def log_file Vedeu::Configuration.log end # @return [String] def message_type(type) Vedeu::Esc.send(message_types.fetch(type, :default)) { "[#{type}]".ljust(9) } end # @return [Hash] def message_types { config: :yellow, create: :green, debug: :red, drb: :blue, event: :magenta, info: :default, input: :yellow, output: :green, store: :cyan, test: :white, update: :cyan, } end # Returns a formatted (red, underlined) UTC timestamp, # eg. 2014-10-24T12:34:56Z # # @return [String] def timestamp(utc_time) return '' if @last_seen == utc_time @last_seen = utc_time "\n\e[4m\e[31m" + utc_time + "\e[39m\e[24m\n" end end # Log eigenclass end # Log end # Vedeu