lib/timber/logger.rb in timber-2.0.24 vs lib/timber/logger.rb in timber-2.1.0.rc1

- old
+ new

@@ -1,8 +1,13 @@ require "logger" require "msgpack" +require "timber/current_context" +require "timber/event" +require "timber/log_devices/http" +require "timber/log_entry" + module Timber # The Timber Logger behaves exactly like `::Logger`, except that it supports a transparent API # for logging structured messages. It ensures your log messages are communicated properly # with the Timber.io API. # @@ -70,51 +75,53 @@ "WARN" => :warn, "ERROR" => :error, "FATAL" => :fatal, "UNKNOWN" => :unknown } + EMPTY_ARRAY = [] private - def build_log_entry(severity, time, progname, msg) - level = SEVERITY_MAP.fetch(severity) + def build_log_entry(severity, time, progname, logged_obj) context_snapshot = CurrentContext.instance.snapshot - + level = SEVERITY_MAP.fetch(severity) tags = extract_active_support_tagged_logging_tags - time_ms = nil - if msg.is_a?(Hash) - if msg.key?(:tag) || msg.key?(:tags) - tags = tags.clone - tags << msg.delete(:tag) if msg.key?(:tag) - tags += msg.delete(:tags) if msg.key?(:tags) - tags.uniq! - end - time_ms = msg.delete(:time_ms) if msg.key?(:time_ms) - msg = msg[:message] if msg.length == 1 - end + if logged_obj.is_a?(Event) + LogEntry.new(level, time, progname, logged_obj.message, context_snapshot, logged_obj, + tags: tags) + elsif logged_obj.is_a?(Hash) + # Extract the tags + tags = tags.clone + tags << logged_obj.delete(:tag) if logged_obj.key?(:tag) + tags += logged_obj.delete(:tags) if logged_obj.key?(:tags) + tags.uniq! - event = Events.build(msg) + # Extract the time_ms + time_ms = logged_obj.delete(:time_ms) - if event - LogEntry.new(level, time, progname, event.message, context_snapshot, event, tags: tags, time_ms: time_ms) + # Build the event + event = Events.build(logged_obj) + message = event ? event.message : logged_obj[:message] + + LogEntry.new(level, time, progname, message, context_snapshot, event, tags: tags, + time_ms: time_ms) else - LogEntry.new(level, time, progname, msg, context_snapshot, nil, tags: tags, time_ms: time_ms) + LogEntry.new(level, time, progname, logged_obj, context_snapshot, nil, tags: tags) end end - # Because of all the crazy ways Rails has attempted this we need this crazy method. + # Because of all the crazy ways Rails has attempted tags, we need this crazy method. def extract_active_support_tagged_logging_tags Thread.current[:activesupport_tagged_logging_tags] || Thread.current["activesupport_tagged_logging_tags:#{object_id}"] || - [] + EMPTY_ARRAY end end # For use in development and test environments where you do not want metadata # included in the log lines. - class SimpleFormatter < Formatter - + class MessageOnlyFormatter < Formatter # This method is invoked when a log event occurs def call(severity, timestamp, progname, msg) log_entry = build_log_entry(severity, timestamp, progname, msg) log_entry.to_s end @@ -125,23 +132,21 @@ # # Example message with metdata: # # My log message @metadata {"level":"info","dt":"2016-09-01T07:00:00.000000-05:00"} # - class StringFormatter < Formatter - METADATA_CALLOUT = "@metadata".freeze + class AugmentedFormatter < Formatter + METADATA_CALLOUT = " @metadata ".freeze + NEW_LINE = "\n".freeze + ESCAPED_NEW_LINE = "\\n".freeze def call(severity, time, progname, msg) log_entry = build_log_entry(severity, time, progname, msg) - - if Config.instance.append_metadata? - metadata = log_entry.to_json(:except => [:message]) - # use << for concatenation for performance reasons - log_entry.message.gsub("\n", "\\n") << " " << METADATA_CALLOUT << " " << metadata << "\n" - else - log_entry.message + "\n" - end + metadata = log_entry.to_json(:except => [:message]) + # use << for concatenation for performance reasons + log_entry.message.gsub(NEW_LINE, ESCAPED_NEW_LINE) << METADATA_CALLOUT << + metadata << NEW_LINE end end # Structures your log messages into JSON. # @@ -174,11 +179,11 @@ # in a rails environment. include ::ActiveSupport::LoggerThreadSafeLevel if defined?(::ActiveSupport::LoggerThreadSafeLevel) include ::LoggerSilence if defined?(::LoggerSilence) # Creates a new Timber::Logger instances. Accepts the same arguments as `::Logger.new`. - # The only difference is that it default the formatter to {HybridFormatter}. Using + # The only difference is that it default the formatter to {AugmentedFormatter}. Using # a different formatter is easy. For example, if you prefer your logs in JSON. # # @example Changing your formatter # logger = Timber::Logger.new(STDOUT) # logger.formatter = Timber::Logger::JSONFormatter.new @@ -191,20 +196,23 @@ end if args.size == 1 and args.first.is_a?(LogDevices::HTTP) self.formatter = PassThroughFormatter.new else - self.formatter = StringFormatter.new + self.formatter = AugmentedFormatter.new end self.level = environment_level after_initialize if respond_to?(:after_initialize) @initialized = true end + # Sets a new formatted on the logger. + # + # @note The formatter cannot be changed if you are using the HTTP logger backend. def formatter=(value) if @initialized && @logdev && @logdev.dev.is_a?(Timber::LogDevices::HTTP) && !value.is_a?(PassThroughFormatter) raise ArgumentError.new("The formatter cannot be changed when using the " + "Timber::LogDevices::HTTP log device. The PassThroughFormatter must be used for proper " + "delivery.") @@ -217,17 +225,24 @@ # a more detailed description and examples. def with_context(context, &block) Timber::CurrentContext.with(context, &block) end + # Patch to ensure that the {#level} method is used instead of `@level`. + # This is required because of Rails' monkey patching on Logger via `::LoggerSilence`. + def add(severity, message = nil, progname = nil, &block) + return true if @logdev.nil? || (severity || UNKNOWN) < level + super + end + # Backwards compatibility with older ActiveSupport::Logger versions Logger::Severity.constants.each do |severity| class_eval(<<-EOT, __FILE__, __LINE__ + 1) def #{severity.downcase}(*args, &block) progname = args.first options = args.last - if args.length == 2 and options.is_a?(Hash) && options != {} + if args.length == 2 and options.is_a?(Hash) && options.length > 0 progname = options.merge(message: progname) end add(#{severity}, nil, progname, &block) end \ No newline at end of file