logsly 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +5 -3
- data/lib/logsly/colors.rb +2 -2
- data/lib/logsly/logging182/appender.rb +290 -0
- data/lib/logsly/logging182/appenders/buffering.rb +398 -0
- data/lib/logsly/logging182/appenders/console.rb +81 -0
- data/lib/logsly/logging182/appenders/email.rb +178 -0
- data/lib/logsly/logging182/appenders/file.rb +85 -0
- data/lib/logsly/logging182/appenders/growl.rb +200 -0
- data/lib/logsly/logging182/appenders/io.rb +84 -0
- data/lib/logsly/logging182/appenders/rolling_file.rb +338 -0
- data/lib/logsly/logging182/appenders/string_io.rb +92 -0
- data/lib/logsly/logging182/appenders/syslog.rb +215 -0
- data/lib/logsly/logging182/appenders.rb +64 -0
- data/lib/logsly/logging182/color_scheme.rb +248 -0
- data/lib/logsly/logging182/config/configurator.rb +187 -0
- data/lib/logsly/logging182/config/yaml_configurator.rb +190 -0
- data/lib/logsly/logging182/diagnostic_context.rb +332 -0
- data/lib/logsly/logging182/layout.rb +132 -0
- data/lib/logsly/logging182/layouts/basic.rb +38 -0
- data/lib/logsly/logging182/layouts/parseable.rb +256 -0
- data/lib/logsly/logging182/layouts/pattern.rb +568 -0
- data/lib/logsly/logging182/layouts.rb +9 -0
- data/lib/logsly/logging182/log_event.rb +44 -0
- data/lib/logsly/logging182/logger.rb +509 -0
- data/lib/logsly/logging182/proxy.rb +59 -0
- data/lib/logsly/logging182/rails_compat.rb +36 -0
- data/lib/logsly/logging182/repository.rb +231 -0
- data/lib/logsly/logging182/root_logger.rb +60 -0
- data/lib/logsly/logging182/stats.rb +277 -0
- data/lib/logsly/logging182/utils.rb +231 -0
- data/lib/logsly/logging182.rb +559 -0
- data/lib/logsly/outputs.rb +5 -5
- data/lib/logsly/version.rb +1 -1
- data/lib/logsly.rb +6 -6
- data/logsly.gemspec +4 -2
- data/test/unit/colors_tests.rb +3 -3
- data/test/unit/logsly_tests.rb +14 -14
- data/test/unit/outputs_tests.rb +34 -24
- metadata +45 -6
@@ -0,0 +1,398 @@
|
|
1
|
+
|
2
|
+
module Logsly::Logging182::Appenders
|
3
|
+
|
4
|
+
# The Buffering module is used to implement buffering of the log messages
|
5
|
+
# in a given appender. The size of the buffer can be specified, and the
|
6
|
+
# buffer can be configured to auto-flush at a given threshold. The
|
7
|
+
# threshold can be a single message or a very large number of messages.
|
8
|
+
#
|
9
|
+
# Log messages of a certain level can cause the buffer to be flushed
|
10
|
+
# immediately. If an error occurs, all previous messages and the error
|
11
|
+
# message will be written immediately to the logging destination if the
|
12
|
+
# buffer is configured to do so.
|
13
|
+
#
|
14
|
+
module Buffering
|
15
|
+
|
16
|
+
# Default buffer size
|
17
|
+
#
|
18
|
+
DEFAULT_BUFFER_SIZE = 500;
|
19
|
+
|
20
|
+
# The buffer holding the log messages
|
21
|
+
#
|
22
|
+
attr_reader :buffer
|
23
|
+
|
24
|
+
# The auto-flushing setting. When the buffer reaches this size, all
|
25
|
+
# messages will be be flushed automatically.
|
26
|
+
#
|
27
|
+
attr_reader :auto_flushing
|
28
|
+
|
29
|
+
# When set, the buffer will be flushed at regular intervals defined by the
|
30
|
+
# flush_period.
|
31
|
+
#
|
32
|
+
attr_reader :flush_period
|
33
|
+
|
34
|
+
# Setup the message buffer and other variables for automatically and
|
35
|
+
# periodically flushing the buffer.
|
36
|
+
#
|
37
|
+
def initialize( *args, &block )
|
38
|
+
@buffer = []
|
39
|
+
@immediate = []
|
40
|
+
@auto_flushing = 1
|
41
|
+
@flush_period = @periodic_flusher = nil
|
42
|
+
|
43
|
+
super(*args, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Close the message buffer by flushing all log events to the appender. If a
|
47
|
+
# periodic flusher thread is running, shut it down and allow it to exit.
|
48
|
+
#
|
49
|
+
def close( *args )
|
50
|
+
flush
|
51
|
+
|
52
|
+
if @periodic_flusher
|
53
|
+
@periodic_flusher.stop
|
54
|
+
@periodic_flusher = nil
|
55
|
+
Thread.pass
|
56
|
+
end
|
57
|
+
|
58
|
+
super(*args)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Reopen the connection to the underlying logging destination. In addition
|
62
|
+
# if the appender is configured for periodic flushing, then the flushing
|
63
|
+
# thread will be stopped and restarted.
|
64
|
+
#
|
65
|
+
def reopen
|
66
|
+
_setup_periodic_flusher
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
# Call +flush+ to force an appender to write out any buffered log events.
|
71
|
+
# Similar to IO#flush, so use in a similar fashion.
|
72
|
+
#
|
73
|
+
def flush
|
74
|
+
return self if @buffer.empty?
|
75
|
+
|
76
|
+
str = nil
|
77
|
+
sync {
|
78
|
+
str = @buffer.join
|
79
|
+
@buffer.clear
|
80
|
+
}
|
81
|
+
|
82
|
+
canonical_write str unless str.empty?
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
# Configure the levels that will trigger an immediate flush of the
|
87
|
+
# logging buffer. When a log event of the given level is seen, the
|
88
|
+
# buffer will be flushed immediately. Only the levels explicitly given
|
89
|
+
# in this assignment will flush the buffer; if an "error" message is
|
90
|
+
# configured to immediately flush the buffer, a "fatal" message will not
|
91
|
+
# even though it is a higher level. Both must be explicitly passed to
|
92
|
+
# this assignment.
|
93
|
+
#
|
94
|
+
# You can pass in a single level name or number, an array of level
|
95
|
+
# names or numbers, or a string containing a comma separated list of level
|
96
|
+
# names or numbers.
|
97
|
+
#
|
98
|
+
# immediate_at = :error
|
99
|
+
# immediate_at = [:error, :fatal]
|
100
|
+
# immediate_at = "warn, error"
|
101
|
+
#
|
102
|
+
def immediate_at=( level )
|
103
|
+
@immediate.clear
|
104
|
+
|
105
|
+
# get the immediate levels -- no buffering occurs at these levels, and
|
106
|
+
# a log message is written to the logging destination immediately
|
107
|
+
immediate_at =
|
108
|
+
case level
|
109
|
+
when String; level.split(',').map {|x| x.strip}
|
110
|
+
when Array; level
|
111
|
+
else Array(level) end
|
112
|
+
|
113
|
+
immediate_at.each do |lvl|
|
114
|
+
num = ::Logsly::Logging182.level_num(lvl)
|
115
|
+
next if num.nil?
|
116
|
+
@immediate[num] = true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Configure the auto-flushing threshold. Auto-flushing is used to flush
|
121
|
+
# the contents of the logging buffer to the logging destination
|
122
|
+
# automatically when the buffer reaches a certain threshold.
|
123
|
+
#
|
124
|
+
# By default, the auto-flushing will be configured to flush after each
|
125
|
+
# log message.
|
126
|
+
#
|
127
|
+
# The allowed settings are as follows:
|
128
|
+
#
|
129
|
+
# N : flush after every N messages (N is an integer)
|
130
|
+
# true : flush after each log message
|
131
|
+
# false OR
|
132
|
+
# nil OR
|
133
|
+
# 0 : only flush when the buffer is full (500 messages)
|
134
|
+
#
|
135
|
+
# If the default buffer size of 500 is too small, then you can manually
|
136
|
+
# configure it to be as large as you want. This will consume more memory.
|
137
|
+
#
|
138
|
+
# auto_flushing = 42_000
|
139
|
+
#
|
140
|
+
def auto_flushing=( period )
|
141
|
+
@auto_flushing =
|
142
|
+
case period
|
143
|
+
when true; 1
|
144
|
+
when false, nil, 0; DEFAULT_BUFFER_SIZE
|
145
|
+
when Integer; period
|
146
|
+
when String; Integer(period)
|
147
|
+
else
|
148
|
+
raise ArgumentError,
|
149
|
+
"unrecognized auto_flushing period: #{period.inspect}"
|
150
|
+
end
|
151
|
+
|
152
|
+
if @auto_flushing <= 0
|
153
|
+
raise ArgumentError,
|
154
|
+
"auto_flushing period must be greater than zero: #{period.inspect}"
|
155
|
+
end
|
156
|
+
|
157
|
+
@auto_flushing = DEFAULT_BUFFER_SIZE if @flush_period and @auto_flushing <= 1
|
158
|
+
end
|
159
|
+
|
160
|
+
# Configure periodic flushing of the message buffer. Periodic flushing is
|
161
|
+
# used to flush the contents of the logging buffer at some regular
|
162
|
+
# interval. Periodic flushing is disabled by default.
|
163
|
+
#
|
164
|
+
# When enabling periodic flushing the flush period should be set using one
|
165
|
+
# of the following formats: "HH:MM:SS" or seconds as an numeric or string.
|
166
|
+
#
|
167
|
+
# "01:00:00" : every hour
|
168
|
+
# "00:05:00" : every 5 minutes
|
169
|
+
# "00:00:30" : every 30 seconds
|
170
|
+
# 60 : every 60 seconds (1 minute)
|
171
|
+
# "120" : every 120 seconds (2 minutes)
|
172
|
+
#
|
173
|
+
# For the periodic flusher to work properly, the auto-flushing threshold
|
174
|
+
# will be set to the default value of 500. The auto-flushing threshold can
|
175
|
+
# be changed, but it must be greater than 1.
|
176
|
+
#
|
177
|
+
# To disable the periodic flusher simply set the flush period to +nil+.
|
178
|
+
# The auto-flushing threshold will not be changed; it must be disabled
|
179
|
+
# manually if so desired.
|
180
|
+
#
|
181
|
+
def flush_period=( period )
|
182
|
+
period =
|
183
|
+
case period
|
184
|
+
when Integer, Float, nil; period
|
185
|
+
when String;
|
186
|
+
num = _parse_hours_minutes_seconds(period) || _parse_numeric(period)
|
187
|
+
num = ArgumentError.new("unrecognized flush period: #{period.inspect}") if num.nil?
|
188
|
+
num
|
189
|
+
else ArgumentError.new("unrecognized flush period: #{period.inspect}") end
|
190
|
+
|
191
|
+
raise period if Exception === period
|
192
|
+
@flush_period = period
|
193
|
+
|
194
|
+
_setup_periodic_flusher
|
195
|
+
end
|
196
|
+
|
197
|
+
protected
|
198
|
+
|
199
|
+
# Configure the buffering using the arguments found in the give options
|
200
|
+
# hash. This method must be called in order to use the message buffer.
|
201
|
+
# The supported options are "immediate_at" and "auto_flushing". Please
|
202
|
+
# refer to the documentation for those methods to see the allowed
|
203
|
+
# options.
|
204
|
+
#
|
205
|
+
def configure_buffering( opts )
|
206
|
+
::Logsly::Logging182.init unless ::Logsly::Logging182.initialized?
|
207
|
+
|
208
|
+
self.immediate_at = opts.getopt(:immediate_at, '')
|
209
|
+
self.auto_flushing = opts.getopt(:auto_flushing, true)
|
210
|
+
self.flush_period = opts.getopt(:flush_period, nil)
|
211
|
+
end
|
212
|
+
|
213
|
+
# Returns true if the _event_ level matches one of the configured
|
214
|
+
# immediate logging levels. Otherwise returns false.
|
215
|
+
#
|
216
|
+
def immediate?( event )
|
217
|
+
return false unless event.respond_to? :level
|
218
|
+
@immediate[event.level]
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
# call-seq:
|
225
|
+
# write( event )
|
226
|
+
#
|
227
|
+
# Writes the given _event_ to the logging destination. The _event_ can
|
228
|
+
# be either a LogEvent or a String. If a LogEvent, then it will be
|
229
|
+
# formatted using the layout given to the appender when it was created.
|
230
|
+
#
|
231
|
+
# The _event_ will be formatted and then buffered until the
|
232
|
+
# "auto_flushing" level has been reached. At this time the canonical_write
|
233
|
+
# method will be used to log all events stored in the buffer.
|
234
|
+
#
|
235
|
+
def write( event )
|
236
|
+
str = event.instance_of?(::Logsly::Logging182::LogEvent) ?
|
237
|
+
layout.format(event) : event.to_s
|
238
|
+
return if str.empty?
|
239
|
+
|
240
|
+
if @auto_flushing == 1
|
241
|
+
canonical_write(str)
|
242
|
+
else
|
243
|
+
sync {
|
244
|
+
str = str.force_encoding(encoding) if encoding and str.encoding != encoding
|
245
|
+
@buffer << str
|
246
|
+
}
|
247
|
+
@periodic_flusher.signal if @periodic_flusher
|
248
|
+
flush if @buffer.length >= @auto_flushing || immediate?(event)
|
249
|
+
end
|
250
|
+
|
251
|
+
self
|
252
|
+
end
|
253
|
+
|
254
|
+
# Attempt to parse an hours/minutes/seconds value from the string and return
|
255
|
+
# an integer number of seconds.
|
256
|
+
#
|
257
|
+
# _parse_hours_minutes_seconds("14:12:42") #=> 51162
|
258
|
+
# _parse_hours_minutes_seconds("foo") #=> nil
|
259
|
+
#
|
260
|
+
def _parse_hours_minutes_seconds( str )
|
261
|
+
m = %r/^\s*(\d{2,}):(\d{2}):(\d{2}(?:\.\d+)?)\s*$/.match(str)
|
262
|
+
return if m.nil?
|
263
|
+
|
264
|
+
(3600 * m[1].to_i) + (60 * m[2].to_i) + (m[3].to_f)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Convert the string into a numeric value. If the string does not
|
268
|
+
# represent a valid Integer or Float then +nil+ is returned.
|
269
|
+
#
|
270
|
+
# _parse_numeric("10") #=> 10
|
271
|
+
# _parse_numeric("1.0") #=> 1.0
|
272
|
+
# _parse_numeric("foo") #=> nil
|
273
|
+
#
|
274
|
+
def _parse_numeric( str )
|
275
|
+
Integer(str) rescue (Float(str) rescue nil)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Using the flush_period, create a new PeriodicFlusher attached to this
|
279
|
+
# appender. If the flush_period is nil, then no action will be taken. If a
|
280
|
+
# PeriodicFlusher already exists, it will be stopped and a new one will be
|
281
|
+
# created.
|
282
|
+
#
|
283
|
+
def _setup_periodic_flusher
|
284
|
+
# stop and remove any existing periodic flusher instance
|
285
|
+
if @periodic_flusher
|
286
|
+
@periodic_flusher.stop
|
287
|
+
@periodic_flusher = nil
|
288
|
+
Thread.pass
|
289
|
+
end
|
290
|
+
|
291
|
+
# create a new periodic flusher if we have a valid flush period
|
292
|
+
if @flush_period
|
293
|
+
@auto_flushing = DEFAULT_BUFFER_SIZE unless @auto_flushing > 1
|
294
|
+
@periodic_flusher = PeriodicFlusher.new(self, @flush_period)
|
295
|
+
@periodic_flusher.start
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# :stopdoc:
|
300
|
+
|
301
|
+
# The PeriodicFlusher contains an internal run loop that will periodically
|
302
|
+
# wake up and flush any log events contained in the message buffer of the
|
303
|
+
# owning appender instance. The PeriodicFlusher relies on a _signal_ from
|
304
|
+
# the appender in order to wakeup and perform the flush on the appender.
|
305
|
+
#
|
306
|
+
class PeriodicFlusher
|
307
|
+
|
308
|
+
# Create a new PeriodicFlusher instance that will call the +flush+
|
309
|
+
# method on the given _appender_. The +flush+ method will be called
|
310
|
+
# every _period_ seconds, but only when the message buffer is non-empty.
|
311
|
+
#
|
312
|
+
def initialize( appender, period )
|
313
|
+
@appender = appender
|
314
|
+
@period = period
|
315
|
+
|
316
|
+
@mutex = Mutex.new
|
317
|
+
@cv = ConditionVariable.new
|
318
|
+
@thread = nil
|
319
|
+
@waiting = nil
|
320
|
+
@signaled = false
|
321
|
+
end
|
322
|
+
|
323
|
+
# Start the periodic flusher's internal run loop.
|
324
|
+
#
|
325
|
+
def start
|
326
|
+
return if @thread
|
327
|
+
|
328
|
+
@thread = Thread.new { loop {
|
329
|
+
begin
|
330
|
+
break if Thread.current[:stop]
|
331
|
+
_wait_for_signal
|
332
|
+
sleep @period unless Thread.current[:stop]
|
333
|
+
@appender.flush
|
334
|
+
rescue => err
|
335
|
+
::Logsly::Logging182.log_internal {"PeriodicFlusher for appender #{@appender.inspect} encountered an error"}
|
336
|
+
::Logsly::Logging182.log_internal(-2) {err}
|
337
|
+
end
|
338
|
+
}; @thread = nil }
|
339
|
+
|
340
|
+
self
|
341
|
+
end
|
342
|
+
|
343
|
+
# Stop the periodic flusher's internal run loop.
|
344
|
+
#
|
345
|
+
def stop
|
346
|
+
return if @thread.nil?
|
347
|
+
@thread[:stop] = true
|
348
|
+
signal if waiting?
|
349
|
+
self
|
350
|
+
end
|
351
|
+
|
352
|
+
# Signal the periodic flusher. This will wake up the run loop if it is
|
353
|
+
# currently waiting for something to do. If the signal method is never
|
354
|
+
# called, the periodic flusher will never perform the flush action on
|
355
|
+
# the appender.
|
356
|
+
#
|
357
|
+
def signal
|
358
|
+
return if Thread.current == @thread # don't signal ourselves
|
359
|
+
return if @signaled # don't need to signal again
|
360
|
+
|
361
|
+
@mutex.synchronize {
|
362
|
+
@signaled = true
|
363
|
+
@cv.signal
|
364
|
+
}
|
365
|
+
self
|
366
|
+
end
|
367
|
+
|
368
|
+
# Returns +true+ if the flusher is waiting for a signal. Returns +false+
|
369
|
+
# if the flusher is somewhere in the processing loop.
|
370
|
+
#
|
371
|
+
def waiting?
|
372
|
+
@waiting
|
373
|
+
end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
def _wait_for_signal
|
378
|
+
@mutex.synchronize {
|
379
|
+
begin
|
380
|
+
# wait on the condition variable only if we have NOT been signaled
|
381
|
+
unless @signaled
|
382
|
+
@waiting = true
|
383
|
+
@cv.wait(@mutex)
|
384
|
+
@waiting = false
|
385
|
+
end
|
386
|
+
ensure
|
387
|
+
@signaled = false
|
388
|
+
end
|
389
|
+
}
|
390
|
+
ensure
|
391
|
+
@waiting = false
|
392
|
+
end
|
393
|
+
end # class PeriodicFlusher
|
394
|
+
# :startdoc:
|
395
|
+
|
396
|
+
end # Buffering
|
397
|
+
end # Logsly::Logging182::Appenders
|
398
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
|
2
|
+
module Logsly::Logging182::Appenders
|
3
|
+
|
4
|
+
# Accessor / Factory for the Stdout appender.
|
5
|
+
#
|
6
|
+
def self.stdout( *args )
|
7
|
+
if args.empty?
|
8
|
+
return self['stdout'] || ::Logsly::Logging182::Appenders::Stdout.new
|
9
|
+
end
|
10
|
+
::Logsly::Logging182::Appenders::Stdout.new(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
# This class provides an Appender that can write to STDOUT.
|
14
|
+
#
|
15
|
+
class Stdout < ::Logsly::Logging182::Appenders::IO
|
16
|
+
|
17
|
+
# call-seq:
|
18
|
+
# Stdout.new( name = 'stdout' )
|
19
|
+
# Stdout.new( :layout => layout )
|
20
|
+
# Stdout.new( name = 'stdout', :level => 'info' )
|
21
|
+
#
|
22
|
+
# Creates a new Stdout Appender. The name 'stdout' will be used unless
|
23
|
+
# another is given. Optionally, a layout can be given for the appender
|
24
|
+
# to use (otherwise a basic appender will be created) and a log level
|
25
|
+
# can be specified.
|
26
|
+
#
|
27
|
+
# Options:
|
28
|
+
#
|
29
|
+
# :layout => the layout to use when formatting log events
|
30
|
+
# :level => the level at which to log
|
31
|
+
#
|
32
|
+
def initialize( *args )
|
33
|
+
opts = Hash === args.last ? args.pop : {}
|
34
|
+
name = args.empty? ? 'stdout' : args.shift
|
35
|
+
|
36
|
+
opts[:encoding] = STDOUT.external_encoding if STDOUT.respond_to? :external_encoding
|
37
|
+
|
38
|
+
super(name, STDOUT, opts)
|
39
|
+
end
|
40
|
+
end # Stdout
|
41
|
+
|
42
|
+
|
43
|
+
# Accessor / Factory for the Stderr appender.
|
44
|
+
#
|
45
|
+
def self.stderr( *args )
|
46
|
+
if args.empty?
|
47
|
+
return self['stderr'] || ::Logsly::Logging182::Appenders::Stderr.new
|
48
|
+
end
|
49
|
+
::Logsly::Logging182::Appenders::Stderr.new(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
# This class provides an Appender that can write to STDERR.
|
53
|
+
#
|
54
|
+
class Stderr < ::Logsly::Logging182::Appenders::IO
|
55
|
+
|
56
|
+
# call-seq:
|
57
|
+
# Stderr.new( name = 'stderr' )
|
58
|
+
# Stderr.new( :layout => layout )
|
59
|
+
# Stderr.new( name = 'stderr', :level => 'warn' )
|
60
|
+
#
|
61
|
+
# Creates a new Stderr Appender. The name 'stderr' will be used unless
|
62
|
+
# another is given. Optionally, a layout can be given for the appender
|
63
|
+
# to use (otherwise a basic appender will be created) and a log level
|
64
|
+
# can be specified.
|
65
|
+
#
|
66
|
+
# Options:
|
67
|
+
#
|
68
|
+
# :layout => the layout to use when formatting log events
|
69
|
+
# :level => the level at which to log
|
70
|
+
#
|
71
|
+
def initialize( *args )
|
72
|
+
opts = Hash === args.last ? args.pop : {}
|
73
|
+
name = args.empty? ? 'stderr' : args.shift
|
74
|
+
|
75
|
+
opts[:encoding] = STDERR.external_encoding if STDERR.respond_to? :external_encoding
|
76
|
+
|
77
|
+
super(name, STDERR, opts)
|
78
|
+
end
|
79
|
+
end # Stderr
|
80
|
+
end # Logsly::Logging182::Appenders
|
81
|
+
|
@@ -0,0 +1,178 @@
|
|
1
|
+
|
2
|
+
require 'net/smtp'
|
3
|
+
require 'time' # get rfc822 time format
|
4
|
+
|
5
|
+
module Logsly::Logging182::Appenders
|
6
|
+
|
7
|
+
# Accessor / Factory for the Email appender.
|
8
|
+
#
|
9
|
+
def self.email( *args )
|
10
|
+
return ::Logsly::Logging182::Appenders::Email if args.empty?
|
11
|
+
::Logsly::Logging182::Appenders::Email.new(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Provides an appender that can send log messages via email to a list of
|
15
|
+
# recipients.
|
16
|
+
#
|
17
|
+
class Email < ::Logsly::Logging182::Appender
|
18
|
+
include Buffering
|
19
|
+
|
20
|
+
attr_reader :authentication, :to, :port
|
21
|
+
attr_accessor :address, :domain, :from, :subject
|
22
|
+
attr_accessor :user_name, :password, :enable_starttls_auto
|
23
|
+
|
24
|
+
# call-seq:
|
25
|
+
# Email.new( name, :from => 'me@example.com', :to => 'you@example.com', :subject => 'Whoops!' )
|
26
|
+
#
|
27
|
+
# Create a new email appender that will buffer messages and then send them
|
28
|
+
# out in batches to the listed recipients. See the options below to
|
29
|
+
# configure how emails are sent through you mail server of choice. All the
|
30
|
+
# buffering options apply to the email appender.
|
31
|
+
#
|
32
|
+
# The following options are required:
|
33
|
+
#
|
34
|
+
# :from - The base filename to use when constructing new log
|
35
|
+
# filenames.
|
36
|
+
# :to - The list of email recipients either as an Array or a comma
|
37
|
+
# separated list.
|
38
|
+
#
|
39
|
+
# The following options are optional:
|
40
|
+
#
|
41
|
+
# :subject - The subject line for the email.
|
42
|
+
# :address - Allows you to use a remote mail server. Just change it
|
43
|
+
# from its default "localhost" setting.
|
44
|
+
# :port - On the off chance that your mail server doesn't run on
|
45
|
+
# port 25, you can change it.
|
46
|
+
# :domain - If you need to specify a HELO domain, you can do it here.
|
47
|
+
# :user_name - If your mail server requires authentication, set the user
|
48
|
+
# name in this setting.
|
49
|
+
# :password - If your mail server requires authentication, set the
|
50
|
+
# password in this setting.
|
51
|
+
# :authentication - If your mail server requires authentication, you need
|
52
|
+
# to specify the authentication type here. This is a
|
53
|
+
# symbol and one of :plain (will send the password in
|
54
|
+
# the clear), :login (will send password Base64
|
55
|
+
# encoded) or :cram_md5 (combines a Challenge/Response
|
56
|
+
# mechanism to exchange information and a cryptographic
|
57
|
+
# Message Digest 5 algorithm to hash important
|
58
|
+
# information)
|
59
|
+
# :enable_starttls_auto - When set to true, detects if STARTTLS is
|
60
|
+
# enabled in your SMTP server and starts to use it.
|
61
|
+
#
|
62
|
+
# Example:
|
63
|
+
#
|
64
|
+
# Setup an email appender that will buffer messages for up to 1 minute,
|
65
|
+
# and only send messages for ERROR and FATAL messages. This example uses
|
66
|
+
# Google's SMTP server with authentication to send out messages.
|
67
|
+
#
|
68
|
+
# Logger.appenders.email( 'email',
|
69
|
+
# :from => "server@example.com",
|
70
|
+
# :to => "developers@example.com",
|
71
|
+
# :subject => "Application Error [#{%x(uname -n).strip}]",
|
72
|
+
#
|
73
|
+
# :address => "smtp.google.com",
|
74
|
+
# :port => 443,
|
75
|
+
# :domain => "google.com",
|
76
|
+
# :user_name => "example",
|
77
|
+
# :password => "12345",
|
78
|
+
# :authentication => :plain,
|
79
|
+
# :enable_starttls_auto => true,
|
80
|
+
#
|
81
|
+
# :auto_flushing => 200, # send an email after 200 messages have been buffered
|
82
|
+
# :flush_period => 60, # send an email after one minute
|
83
|
+
# :level => :error # only process log events that are "error" or "fatal"
|
84
|
+
# )
|
85
|
+
#
|
86
|
+
def initialize( name, opts = {} )
|
87
|
+
opts[:header] = false
|
88
|
+
super(name, opts)
|
89
|
+
|
90
|
+
af = opts.getopt(:buffsize) ||
|
91
|
+
opts.getopt(:buffer_size) ||
|
92
|
+
100
|
93
|
+
configure_buffering({:auto_flushing => af}.merge(opts))
|
94
|
+
|
95
|
+
# get the SMTP parameters
|
96
|
+
self.from = opts.getopt :from
|
97
|
+
raise ArgumentError, 'Must specify from address' if @from.nil?
|
98
|
+
|
99
|
+
self.to = opts.getopt :to
|
100
|
+
raise ArgumentError, 'Must specify recipients' if @to.empty?
|
101
|
+
|
102
|
+
self.subject = opts.getopt :subject, "Message from #{$0}"
|
103
|
+
self.address = opts.getopt(:server) || opts.getopt(:address) || 'localhost'
|
104
|
+
self.port = opts.getopt(:port, 25)
|
105
|
+
self.domain = opts.getopt(:domain, ENV['HOSTNAME']) || 'localhost.localdomain'
|
106
|
+
self.user_name = opts.getopt(:acct) || opts.getopt(:user_name)
|
107
|
+
self.password = opts.getopt(:passwd) || opts.getopt(:password)
|
108
|
+
self.enable_starttls_auto = opts.getopt(:enable_starttls_auto, false)
|
109
|
+
self.authentication = opts.getopt(:authtype) || opts.getopt(:authentication) || :plain
|
110
|
+
end
|
111
|
+
|
112
|
+
# Close the email appender. If the layout contains a foot, it will not be
|
113
|
+
# sent as an email.
|
114
|
+
#
|
115
|
+
def close( *args )
|
116
|
+
super(false)
|
117
|
+
end
|
118
|
+
|
119
|
+
# If your mail server requires authentication, you need to specify the
|
120
|
+
# authentication type here. This is a symbol and one of :plain (will send
|
121
|
+
# the password in the clear), :login (will send password Base64 encoded)
|
122
|
+
# or :cram_md5 (combines a Challenge/Response mechanism to exchange
|
123
|
+
# information and a cryptographic Message Digest 5 algorithm to hash
|
124
|
+
# important information)
|
125
|
+
#
|
126
|
+
def authentication=( val )
|
127
|
+
@authentication = val.to_s.to_sym
|
128
|
+
end
|
129
|
+
|
130
|
+
# On the off chance that your mail server doesn't run on port 25, you can
|
131
|
+
# change it.
|
132
|
+
#
|
133
|
+
def port=( val )
|
134
|
+
@port = Integer(val)
|
135
|
+
end
|
136
|
+
|
137
|
+
# The list of email recipients. This can either be an Array of recipients
|
138
|
+
# or a comma separated list. A single recipient is also valid.
|
139
|
+
#
|
140
|
+
# email.to = ['mike@example.com', 'tony@example.com']
|
141
|
+
# email.to = 'alicia@example.com'
|
142
|
+
# email.to = 'bob@example.com, andy@example.com, john@example.com'
|
143
|
+
#
|
144
|
+
def to=( val )
|
145
|
+
@to = val.respond_to?(:split) ? val.split(',') : Array(val)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
# This method is called by the buffering code when messages need to be
|
152
|
+
# sent out as an email.
|
153
|
+
#
|
154
|
+
def canonical_write( str )
|
155
|
+
### build a mail header for RFC 822
|
156
|
+
rfc822msg = "From: #{@from}\n"
|
157
|
+
rfc822msg << "To: #{@to.join(",")}\n"
|
158
|
+
rfc822msg << "Subject: #{@subject}\n"
|
159
|
+
rfc822msg << "Date: #{Time.new.rfc822}\n"
|
160
|
+
rfc822msg << "Message-Id: <#{"%.8f" % Time.now.to_f}@#{@domain}>\n\n"
|
161
|
+
|
162
|
+
rfc822msg = rfc822msg.force_encoding(encoding) if encoding and rfc822msg.encoding != encoding
|
163
|
+
rfc822msg << str
|
164
|
+
|
165
|
+
### send email
|
166
|
+
smtp = Net::SMTP.new(@address, @port)
|
167
|
+
smtp.enable_starttls_auto if @enable_starttls_auto and smtp.respond_to? :enable_starttls_auto
|
168
|
+
smtp.start(@domain, @user_name, @password, @authentication) { |s| s.sendmail(rfc822msg, @from, @to) }
|
169
|
+
self
|
170
|
+
rescue StandardError, TimeoutError => err
|
171
|
+
self.level = :off
|
172
|
+
::Logsly::Logging182.log_internal {'e-mail notifications have been disabled'}
|
173
|
+
::Logsly::Logging182.log_internal(-2) {err}
|
174
|
+
end
|
175
|
+
|
176
|
+
end # Email
|
177
|
+
end # Logsly::Logging182::Appenders
|
178
|
+
|