lib/lumberjack/logger.rb in lumberjack-1.0.13 vs lib/lumberjack/logger.rb in lumberjack-1.1.0

- old
+ new

@@ -28,104 +28,151 @@ include Severity # The time that the device was last flushed. attr_reader :last_flushed_at - # The name of the program associated with log messages. + # Set +silencer+ to false to disable silencing the log. + attr_accessor :silencer + + # Set the name of the program to attach to log entries. attr_writer :progname - # The device being written to. + # The device being written to attr_reader :device - # Set +silencer+ to false to disable silencing the log. - attr_accessor :silencer - # Create a new logger to log to a Device. # # The +device+ argument can be in any one of several formats. # # If it is a Device object, that object will be used. # If it has a +write+ method, it will be wrapped in a Device::Writer class. - # If it is <tt>:null</tt>, it will be a Null device that won't record any output. + # If it is :null, it will be a Null device that won't record any output. # Otherwise, it will be assumed to be file path and wrapped in a Device::LogFile class. # # This method can take the following options: # - # * <tt>:level</tt> - The logging level below which messages will be ignored. - # * <tt>:progname</tt> - The name of the program that will be recorded with each log entry. - # * <tt>:flush_seconds</tt> - The maximum number of seconds between flush calls. - # * <tt>:roll</tt> - If the log device is a file path, it will be a Device::DateRollingLogFile if this is set. - # * <tt>:max_size</tt> - If the log device is a file path, it will be a Device::SizeRollingLogFile if this is set. + # * :level - The logging level below which messages will be ignored. + # * :formatter - The formatter to use for outputting messages to the log. + # * :datetime_format - The format to use for log timestamps. + # * :progname - The name of the program that will be recorded with each log entry. + # * :flush_seconds - The maximum number of seconds between flush calls. + # * :roll - If the log device is a file path, it will be a Device::DateRollingLogFile if this is set. + # * :max_size - If the log device is a file path, it will be a Device::SizeRollingLogFile if this is set. # # All other options are passed to the device constuctor. def initialize(device = STDOUT, options = {}) - @thread_settings = {} - options = options.dup self.level = options.delete(:level) || INFO self.progname = options.delete(:progname) max_flush_seconds = options.delete(:flush_seconds).to_f - @device = open_device(device, options) - @_formatter = Formatter.new + @device = open_device(device, options) if device + @_formatter = (options[:formatter] || Formatter.new) + time_format = (options[:datetime_format] || options[:time_format]) + self.datetime_format = time_format if time_format @last_flushed_at = Time.now @silencer = true + @tags = {} create_flusher_thread(max_flush_seconds) if max_flush_seconds > 0 end - # Get the Formatter object used to convert messages into strings. - def formatter - @_formatter + # Get the timestamp format on the device if it has one. + def datetime_format + @device.datetime_format if @device.respond_to?(:datetime_format) end + # Set the timestamp format on the device if it is supported. + def datetime_format=(format) + if @device.respond_to?(:datetime_format=) + @device.datetime_format = format + end + end + # Get the level of severity of entries that are logged. Entries with a lower # severity level will be ignored. def level thread_local_value(:lumberjack_logger_level) || @level end + alias_method :sev_threshold, :level + + # Set the log level using either an integer level like Logger::INFO or a label like + # :info or "info" + def level=(value) + if value.is_a?(Integer) + @level = value + else + @level = Severity::label_to_level(value) + end + end + + alias_method :sev_threshold=, :level= + + # Set the Lumberjack::Formatter used to format objects for logging as messages. + def formatter=(value) + @_formatter = value + end + + # Get the Lumberjack::Formatter used to format objects for logging as messages. + def formatter + @_formatter + end + # Add a message to the log with a given severity. The message can be either # passed in the +message+ argument or supplied with a block. This method # is not normally called. Instead call one of the helper functions # +fatal+, +error+, +warn+, +info+, or +debug+. # # The severity can be passed in either as one of the Severity constants, # or as a Severity label. # # === Example # - # logger.add(Lumberjack::Severity::ERROR, exception) - # logger.add(Lumberjack::Severity::INFO, "Request completed") - # logger.add(:warn, "Request took a long time") - # logger.add(Lumberjack::Severity::DEBUG){"Start processing with options #{options.inspect}"} - def add(severity, message = nil, progname = nil) - severity = Severity.label_to_level(severity) if severity.is_a?(String) || severity.is_a?(Symbol) + # logger.add_entry(Logger::ERROR, exception) + # logger.add_entry(Logger::INFO, "Request completed") + # logger.add_entry(:warn, "Request took a long time") + # logger.add_entry(Logger::DEBUG){"Start processing with options #{options.inspect}"} + def add_entry(severity, message, progname = nil, tags = nil) + severity = Severity.label_to_level(severity) unless severity.is_a?(Integer) - return unless severity && severity >= level + return true unless @device && severity && severity >= level time = Time.now + message = message.call if message.is_a?(Proc) + message = formatter.format(message) + progname ||= self.progname + + current_tags = self.tags + tags = nil unless tags.is_a?(Hash) + if current_tags.empty? + tags = Tags.stringify_keys(tags) unless tags.nil? + else + if tags.nil? + tags = current_tags.dup + else + tags = current_tags.merge(Tags.stringify_keys(tags)) + end + end + + entry = LogEntry.new(time, severity, message, progname, $$, tags) + write_to_device(entry) + + true + end + + # ::Logger compatible method to add a log entry. + def add(severity, message = nil, progname = nil, &block) if message.nil? - if block_given? - message = yield + if block + message = block else message = progname progname = nil end end - - message = @_formatter.format(message) - progname ||= self.progname - entry = LogEntry.new(time, severity, message, progname, $$, Lumberjack.unit_of_work_id) - begin - device.write(entry) - rescue => e - $stderr.puts("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}") - $stderr.puts(entry.to_s) - end - - nil + add_entry(severity, message, progname) end alias_method :log, :add # Flush the logging device. Messages are not guaranteed to be written until this method is called. @@ -139,83 +186,80 @@ def close flush @device.close if @device.respond_to?(:close) end + def reopen(logdev = nil) + device.reopen(logdev) if device.respond_to?(:reopen) + end + # Log a +FATAL+ message. The message can be passed in either the +message+ argument or in a block. - def fatal(message = nil, progname = nil, &block) - add(FATAL, message, progname, &block) + def fatal(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) + call_add_entry(FATAL, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +FATAL+ messages are being logged. def fatal? level <= FATAL end # Log an +ERROR+ message. The message can be passed in either the +message+ argument or in a block. - def error(message = nil, progname = nil, &block) - add(ERROR, message, progname, &block) + def error(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) + call_add_entry(ERROR, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +ERROR+ messages are being logged. def error? level <= ERROR end # Log a +WARN+ message. The message can be passed in either the +message+ argument or in a block. - def warn(message = nil, progname = nil, &block) - add(WARN, message, progname, &block) + def warn(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) + call_add_entry(WARN, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +WARN+ messages are being logged. def warn? level <= WARN end # Log an +INFO+ message. The message can be passed in either the +message+ argument or in a block. - def info(message = nil, progname = nil, &block) - add(INFO, message, progname, &block) + def info(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) + call_add_entry(INFO, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +INFO+ messages are being logged. def info? level <= INFO end # Log a +DEBUG+ message. The message can be passed in either the +message+ argument or in a block. - def debug(message = nil, progname = nil, &block) - add(DEBUG, message, progname, &block) + def debug(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) + call_add_entry(DEBUG, message_or_progname_or_tags, progname_or_tags, &block) end # Return +true+ if +DEBUG+ messages are being logged. def debug? level <= DEBUG end # Log a message when the severity is not known. Unknown messages will always appear in the log. # The message can be passed in either the +message+ argument or in a block. - def unknown(message = nil, progname = nil, &block) - add(UNKNOWN, message, progname, &block) + def unknown(message_or_progname_or_tags = nil, progname_or_tags = nil, &block) + call_add_entry(UNKNOWN, message_or_progname_or_tags, progname_or_tags, &block) end - alias_method :<<, :unknown - - # Set the minimum level of severity of messages to log. - def level=(severity) - if severity.is_a?(Integer) - @level = severity - else - @level = Severity.label_to_level(severity) - end + def <<(msg) + add_entry(UNKNOWN, msg) end # Silence the logger by setting a new log level inside a block. By default, only +ERROR+ or +FATAL+ # messages will be logged. # # === Example # - # logger.level = Lumberjack::Severity::INFO + # logger.level = Logger::INFO # logger.silence do # do_something # Log level inside the block is +ERROR+ # end def silence(temporary_level = ERROR, &block) if silencer @@ -238,12 +282,62 @@ # Get the program name associated with log messages. def progname thread_local_value(:lumberjack_logger_progname) || @progname end + # Set a hash of tags on logger. If a block is given, the tags will only be set + # for the duration of the block. + def tag(tags, &block) + tags = Tags.stringify_keys(tags) + if block + thread_tags = thread_local_value(:lumberjack_logger_tags) + value = (thread_tags ? thread_tags.merge(tags) : tags) + push_thread_local_value(:lumberjack_logger_tags, value, &block) + else + @tags.merge!(tags) + end + end + + # Return all tags in scope on the logger including global tags set on the Lumberjack + # context, tags set on the logger, and tags set on the current block for the logger + def tags + tags = {} + context_tags = Lumberjack.context_tags + tags.merge!(context_tags) if context_tags && !context_tags.empty? + tags.merge!(@tags) if !@tags.empty? + scope_tags = thread_local_value(:lumberjack_logger_tags) + tags.merge!(scope_tags) if scope_tags && !scope_tags.empty? + tags + end + private + # Dereference arguments to log calls so we can have methods with compatibility with ::Logger + def call_add_entry(severity, message_or_progname_or_tags, progname_or_tags, &block) #:nodoc: + message = nil + progname = nil + tags = nil + if block + message = block + if message_or_progname_or_tags.is_a?(Hash) + tags = message_or_progname_or_tags + progname = progname_or_tags + else + progname = message_or_progname_or_tags + tags = progname_or_tags if progname_or_tags.is_a?(Hash) + end + else + message = message_or_progname_or_tags + if progname_or_tags.is_a?(Hash) + tags = progname_or_tags + else + progname = progname_or_tags + end + end + add_entry(severity, message, progname, tags) + end + # Set a local value for a thread tied to this object. def set_thread_local_value(name, value) #:nodoc: values = Thread.current[name] unless values values = {} @@ -274,24 +368,35 @@ end end # Open a logging device. def open_device(device, options) #:nodoc: - if device.is_a?(Device) + if device.nil? + nil + elsif device.is_a?(Device) device elsif device.respond_to?(:write) && device.respond_to?(:flush) Device::Writer.new(device, options) elsif device == :null Device::Null.new else device = device.to_s if options[:roll] Device::DateRollingLogFile.new(device, options) elsif options[:max_size] - Device::SizeRollingLogFile.new(device, options) + Device::SizeRollingLogFile.new(device, options) else Device::LogFile.new(device, options) end + end + end + + def write_to_device(entry) #:nodoc: + begin + @device.write(entry) + rescue => e + $stderr.puts("#{e.class.name}: #{e.message}#{' at ' + e.backtrace.first if e.backtrace}") + $stderr.puts(entry.to_s) end end # Create a thread that will periodically call flush. def create_flusher_thread(flush_seconds) #:nodoc: