File: appender.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 (344 lines) | stat: -rw-r--r-- 9,502 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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344

module Logging

# The +Appender+ class is provides methods for appending log events to a
# logging destination. The log events are formatted into strings using a
# Layout.
#
# All other Appenders inherit from this class which provides stub methods.
# Each subclass should provide a +write+ method that will write log
# messages to the logging destination.
#
# A private +sync+ method is provided for use by subclasses. It is used to
# synchronize writes to the logging destination, and can be used by
# subclasses to synchronize the closing or flushing of the logging
# destination.
#
class Appender

  attr_reader :name, :layout, :level, :filters

  # call-seq:
  #    Appender.new( name )
  #    Appender.new( name, :layout => layout )
  #
  # Creates a new appender using the given name. If no Layout is specified,
  # then a Basic layout will be used. Any logging header supplied by the
  # layout will be written to the logging destination when the Appender is
  # created.
  #
  # Options:
  #
  #    :layout   => the layout to use when formatting log events
  #    :level    => the level at which to log
  #    :encoding => encoding to use when writing messages (defaults to UTF-8)
  #    :filters  => filters to apply to events before processing
  #
  def initialize( name, opts = {} )
    ::Logging.init unless ::Logging.initialized?

    @name    = name.to_s
    @closed  = false
    @filters = []
    @mutex   = ReentrantMutex.new

    self.layout   = opts.fetch(:layout, ::Logging::Layouts::Basic.new)
    self.level    = opts.fetch(:level, nil)
    self.encoding = opts.fetch(:encoding, self.encoding)
    self.filters  = opts.fetch(:filters, nil)

    if opts.fetch(:header, true)
      header = @layout.header

      unless header.nil? || header.empty?
        begin
          write(header)
        rescue StandardError => err
          ::Logging.log_internal_error(err)
        end
      end
    end

    ::Logging::Appenders[@name] = self
  end

  # call-seq:
  #    append( event )
  #
  # Write the given _event_ to the logging destination. The log event will
  # be processed through the Layout associated with the Appender.
  #
  def append( event )
    if @closed
      raise RuntimeError,
            "appender '<#{self.class.name}: #{@name}>' is closed"
    end

    # only append if the event level is less than or equal to the configured
    # appender level and the filter does not disallow it
    if event = allow(event)
      begin
        write(event)
      rescue StandardError => err
        ::Logging.log_internal_error(err)
      end
    end

    self
  end

  # call-seq:
  #    appender << string
  #
  # Write the given _string_ to the logging destination "as is" -- no
  # layout formatting will be performed.
  #
  def <<( str )
    if @closed
      raise RuntimeError,
            "appender '<#{self.class.name}: #{@name}>' is closed"
    end

    unless off?
      begin
        write(str)
      rescue StandardError => err
        ::Logging.log_internal_error(err)
      end
    end
    self
  end

  # call-seq:
  #    level = :all
  #
  # Set the level for this appender; log events below this level will be
  # ignored by this appender. The level can be either a +String+, a
  # +Symbol+, or an +Integer+. An +ArgumentError+ is raised if this is not
  # the case.
  #
  # There are two special levels -- "all" and "off". The former will
  # enable recording of all log events. The latter will disable the
  # recording of all events.
  #
  # Example:
  #
  #    appender.level = :debug
  #    appender.level = "INFO"
  #    appender.level = 4
  #    appender.level = 'off'
  #    appender.level = :all
  #
  # These produce an +ArgumentError+
  #
  #    appender.level = Object
  #    appender.level = -1
  #    appender.level = 1_000_000_000_000
  #
  def level=( level )
    lvl = case level
          when String, Symbol; ::Logging::level_num(level)
          when Integer; level
          when nil; 0
          else
            raise ArgumentError,
                  "level must be a String, Symbol, or Integer"
          end
    if lvl.nil? or lvl < 0 or lvl > ::Logging::LEVELS.length
      raise ArgumentError, "unknown level was given '#{level}'"
    end

    @level = lvl
  end

  # call-seq
  #    appender.layout = Logging::Layouts::Basic.new
  #
  # Sets the layout to be used by this appender.
  #
  def layout=( layout )
    unless layout.kind_of? ::Logging::Layout
      raise TypeError,
            "#{layout.inspect} is not a kind of 'Logging::Layout'"
    end
    @layout = layout
  end

  # Sets the filter(s) to be used by this appender. This method will clear the
  # current filter set and add those passed to this setter method.
  #
  # Examples
  #    appender.filters = Logging::Filters::Level.new(:warn, :error)
  #
  def filters=( args )
    @filters.clear
    add_filters(*args)
  end

  # Sets the filter(s) to be used by this appender. The filters will be
  # applied in the order that they are added to the appender.
  #
  # Examples
  #    add_filters(Logging::Filters::Level.new(:warn, :error))
  #
  # Returns this appender instance.
  def add_filters( *args )
    args.flatten.each do |filter|
      next if filter.nil?
      unless filter.kind_of?(::Logging::Filter)
        raise TypeError, "#{filter.inspect} is not a kind of 'Logging::Filter'"
      end
      @filters << filter
    end
    self
  end

  # call-seq:
  #    close( footer = true )
  #
  # Close the appender and writes the layout footer to the logging
  # destination if the _footer_ flag is set to +true+. Log events will
  # no longer be written to the logging destination after the appender
  # is closed.
  #
  def close( footer = true )
    return self if @closed
    ::Logging::Appenders.remove(@name)
    @closed = true

    flush

    if footer
      footer = @layout.footer
      unless footer.nil? || footer.empty?
        begin
          write(footer)
        rescue StandardError => err
          ::Logging.log_internal_error(err)
        end
      end
    end
    self
  end

  # call-seq:
  #    closed?
  #
  # Returns +true+ if the appender has been closed; returns +false+
  # otherwise. When an appender is closed, no more log events can be
  # written to the logging destination.
  #
  def closed?
    @closed
  end

  # Reopen the connection to the underlying logging destination. If the
  # connection is currently closed then it will be opened. If the connection
  # is currently open then it will be closed and immediately opened.
  #
  def reopen
    @closed = false
    self
  end

  # call-seq:
  #    flush
  #
  # Call +flush+ to force an appender to write out any buffered log events.
  # Similar to IO#flush, so use in a similar fashion.
  #
  def flush
    self
  end

  # Save off the original `to_s` for use in tests
  alias_method :_to_s, :to_s

  # call-seq:
  #     to_s => string
  #
  # Returns a string representation of the appender.
  #
  def to_s
    "<%s name=\"%s\">" % [self.class.name.sub(%r/^Logging::/, ''), self.name]
  end

  # Returns the current Encoding for the appender or nil if an encoding has
  # not been set.
  #
  def encoding
    return @encoding if defined? @encoding
    @encoding = Object.const_defined?(:Encoding) ? Encoding.default_external : nil
  end

  # Set the appender encoding to the given value. The value can either be an
  # Encoding instance or a String or Symbol referring to a valid encoding.
  #
  # This method only applies to Ruby 1.9 or later. The encoding will always be
  # nil for older Rubies.
  #
  # value - The encoding as a String, Symbol, or Encoding instance.
  #
  # Raises ArgumentError if the value is not a valid encoding.
  def encoding=( value )
    if value.nil?
      @encoding = nil
    else
      @encoding = Object.const_defined?(:Encoding) ? Encoding.find(value.to_s) : nil
    end
  end

  # Check to see if the event should be processed by the appender. An event will
  # be rejected if the event level is lower than the configured level for the
  # appender. Or it will be rejected if one of the filters rejects the event.
  #
  # event - The LogEvent to check
  #
  # Returns the event if it is allowed; returns `nil` if it is not allowed.
  def allow( event )
    return nil if @level > event.level
    @filters.each do |filter|
      break unless event = filter.allow(event)
    end
    event
  end

  # Returns `true` if the appender has been turned off. This is useful for
  # appenders that write data to a remote location (such as syslog or email),
  # and that write encounters too many errors. The appender can turn itself off
  # to and log an error via the `Logging` logger.
  #
  # Set the appender's level to a valid value to turn it back on.
  def off?
    @level >= ::Logging::LEVELS.length
  end


private

  # call-seq:
  #    write( event )
  #
  # Writes the given _event_ to the logging destination. Subclasses should
  # provide an implementation of this method. The _event_ can be either a
  # LogEvent or a String. If a LogEvent, then it will be formatted using
  # the layout given to the appender when it was created.
  #
  def write( event )
    nil
  end

  # call-seq:
  #    sync { block }
  #
  # Obtains an exclusive lock, runs the block, and releases the lock when
  # the block completes. This method is re-entrant so that a single thread
  # can call +sync+ multiple times without hanging the thread.
  #
  def sync( &block )
    @mutex.synchronize(&block)
  end

end  # class Appender
end  # module Logging