module Logging # The +Layout+ class provides methods for formatting log events into a # string representation. Layouts are used by Appenders to format log # events before writing them to the logging destination. # # All other Layouts inherit from this class which provides stub methods. # Each subclass should provide a +format+ method. A layout can be used by # more than one +Appender+ so all the methods need to be thread safe. # class Layout # call-seq: # Layout.new( :format_as => :string ) # # Creates a new layout that will format objects as strings using the # given :format_as style. This can be one of :string, # :inspect, or :yaml. These formatting commands map to # the following object methods: # # * :string => to_s # * :inspect => inspect # * :yaml => to_yaml # * :json => MultiJson.encode(obj) # # If the format is not specified then the global object format is used # (see Logging#format_as). If the global object format is not specified # then :string is used. # def initialize( opts = {} ) ::Logging.init unless ::Logging.initialized? default = ::Logging.const_defined?('OBJ_FORMAT') ? ::Logging::OBJ_FORMAT : nil f = opts.fetch(:format_as, default) f = f.intern if f.instance_of? String @obj_format = case f when :inspect, :yaml, :json; f else :string end self.backtrace = opts.fetch(:backtrace, ::Logging.backtrace) self.utc_offset = opts.fetch(:utc_offset, ::Logging.utc_offset) self.cause_depth = opts.fetch(:cause_depth, ::Logging.cause_depth) end # call-seq: # layout.backtrace = true # # Set the backtrace flag to the given value. This can be set to `true` or # `false`. # def backtrace=( value ) @backtrace = case value when :on, 'on', true; true when :off, 'off', false; false else raise ArgumentError, "backtrace must be `true` or `false`" end end # Returns the backtrace setting. attr_reader :backtrace alias :backtrace? :backtrace # Set the UTC offset used when formatting time values. If left unset, the # default local time zone will be used for time values. This method accepts # the `utc_offset` format supported by the `Time#localtime` method in Ruby. # # Passing "UTC" or `0` as the UTC offset will cause all times to be reported # in the UTC timezone. # # layout.utc_offset = "-07:00" # Mountain Standard Time in North America # layout.utc_offset = "+01:00" # Central European Time # layout.utc_offset = "UTC" # UTC # layout.utc_offset = 0 # UTC # def utc_offset=( value ) @utc_offset = case value when nil; nil when "UTC", "GMT", 0; 0 else Time.now.localtime(value) value end end # Returns the UTC offset. attr_reader :utc_offset # # def cause_depth=( value ) if value.nil? @cause_depth = ::Logging::DEFAULT_CAUSE_DEPTH else value = Integer(value) @cause_depth = value < 0 ? ::Logging::DEFAULT_CAUSE_DEPTH : value end end # Returns the exception cause depth formatting limit. attr_reader :cause_depth # Internal: Helper method that applies the UTC offset to the given `time` # instance. A new Time is returned that is equivalent to the original `time` # but pinned to the timezone given by the UTC offset. # # If a UTC offset has not been set, then the original `time` instance is # returned unchanged. # def apply_utc_offset( time ) return time if utc_offset.nil? time = time.dup if utc_offset == 0 time.utc else time.localtime(utc_offset) end time end # call-seq: # format( event ) # # Returns a string representation of the given logging _event_. It is # up to subclasses to implement this method. # def format( event ) nil end # call-seq: # header # # Returns a header string to be used at the beginning of a logging # appender. # def header( ) '' end # call-seq: # footer # # Returns a footer string to be used at the end of a logging appender. # def footer( ) '' end # call-seq: # format_obj( obj ) # # Return a string representation of the given object. Depending upon # the configuration of the logger system the format will be an +inspect+ # based representation or a +yaml+ based representation. # def format_obj( obj ) case obj when String; obj when Exception lines = ["<#{obj.class.name}> #{obj.message}"] lines.concat(obj.backtrace) if backtrace? && obj.backtrace format_cause(obj, lines) lines.join("\n\t") when nil; "<#{obj.class.name}> nil" else str = "<#{obj.class.name}> " str << case @obj_format when :inspect; obj.inspect when :yaml; try_yaml(obj) when :json; try_json(obj) else obj.to_s end str end end # Internal: Format any nested exceptions found in the given exception `e` # while respecting the maximum `cause_depth`. The lines array is used to # capture all the output lines form the nested exceptions; the array is later # joined by the `format_obj` method. # # e - Exception to format # lines - Array of output lines # # Returns the input `lines` Array def format_cause(e, lines) return lines if cause_depth == 0 cause_depth.times do break unless e.respond_to?(:cause) && e.cause cause = e.cause lines << "--- Caused by ---" lines << "<#{cause.class.name}> #{cause.message}" lines.concat(format_cause_backtrace(e, cause)) if backtrace? && cause.backtrace e = cause end if e.respond_to?(:cause) && e.cause lines << "--- Further #cause backtraces were omitted ---" end lines end # Internal: Format the backtrace of the nested `cause` but remove the common # exception lines from the parent exception. This helps keep the backtraces a # wee bit shorter and more comprehensible. # # e - parent exception # cause - the nested exception generating the returned backtrace # # Returns an Array of backtracke lines. def format_cause_backtrace(e, cause) # Find where the cause's backtrace differs from the parent exception's. backtrace = Array(e.backtrace) cause_backtrace = Array(cause.backtrace) index = -1 min_index = [backtrace.size, cause_backtrace.size].min * -1 just_in_case = -5000 while index > min_index && backtrace[index] == cause_backtrace[index] && index >= just_in_case index -= 1 end # Add on a few common frames to make it clear where the backtraces line up. index += 3 index = -1 if index >= 0 cause_backtrace[0..index] end # Attempt to format the _obj_ using yaml, but fall back to inspect style # formatting if yaml fails. # # obj - The Object to format. # # Returns a String representation of the object. # def try_yaml( obj ) "\n#{obj.to_yaml}" rescue TypeError obj.inspect end # Attempt to format the given object as a JSON string, but fall back to # inspect formatting if JSON encoding fails. # # obj - The Object to format. # # Returns a String representation of the object. # def try_json( obj ) MultiJson.encode(obj) rescue StandardError obj.inspect end end end