File: layout.rb

package info (click to toggle)
ruby-logging 2.2.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 660 kB
  • sloc: ruby: 6,139; sh: 11; makefile: 2
file content (260 lines) | stat: -rw-r--r-- 7,450 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

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 <tt>:format_as</tt> style. This can be one of <tt>:string</tt>,
  # <tt>:inspect</tt>, or <tt>:yaml</tt>. 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 <tt>:string</tt> 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