sawmill 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.rdoc +3 -0
- data/README.rdoc +81 -0
- data/Rakefile +147 -0
- data/lib/sawmill/entry.rb +307 -0
- data/lib/sawmill/entry_classifier.rb +75 -0
- data/lib/sawmill/entry_processor/build_records.rb +157 -0
- data/lib/sawmill/entry_processor/conditionals.rb +444 -0
- data/lib/sawmill/entry_processor/filter_basic_fields.rb +145 -0
- data/lib/sawmill/entry_processor/format.rb +228 -0
- data/lib/sawmill/entry_processor/simple_queue.rb +116 -0
- data/lib/sawmill/entry_processor.rb +158 -0
- data/lib/sawmill/errors.rb +66 -0
- data/lib/sawmill/level.rb +264 -0
- data/lib/sawmill/log_record_middleware.rb +93 -0
- data/lib/sawmill/logger.rb +373 -0
- data/lib/sawmill/parser.rb +181 -0
- data/lib/sawmill/record.rb +255 -0
- data/lib/sawmill/record_processor/conditionals.rb +330 -0
- data/lib/sawmill/record_processor/decompose.rb +75 -0
- data/lib/sawmill/record_processor/filter_by_attributes.rb +87 -0
- data/lib/sawmill/record_processor/filter_by_record_id.rb +77 -0
- data/lib/sawmill/record_processor/format.rb +88 -0
- data/lib/sawmill/record_processor/simple_queue.rb +117 -0
- data/lib/sawmill/record_processor.rb +137 -0
- data/lib/sawmill/rotater/base.rb +90 -0
- data/lib/sawmill/rotater/date_based_log_file.rb +145 -0
- data/lib/sawmill/rotater/shifting_log_file.rb +166 -0
- data/lib/sawmill/rotater.rb +236 -0
- data/lib/sawmill/util/queue.rb +138 -0
- data/lib/sawmill/version.rb +47 -0
- data/lib/sawmill.rb +78 -0
- data/tests/tc_entry_processors.rb +138 -0
- data/tests/tc_formatter_parser.rb +144 -0
- data/tests/tc_levels.rb +118 -0
- data/tests/tc_logger.rb +315 -0
- data/tests/tc_record_processors.rb +117 -0
- data/tests/tc_records.rb +206 -0
- metadata +116 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Sawmill logger class
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2009 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
if RUBY_VERSION >= '1.9'
|
38
|
+
require 'securerandom'
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
module Sawmill
|
43
|
+
|
44
|
+
|
45
|
+
# This is the Sawmill logger.
|
46
|
+
# It duck-types most of the API of the logger class from the ruby
|
47
|
+
# standard library, and adds capabilities specific to Sawmill.
|
48
|
+
|
49
|
+
class Logger
|
50
|
+
|
51
|
+
|
52
|
+
# Create a new logger.
|
53
|
+
#
|
54
|
+
# Supported options include:
|
55
|
+
#
|
56
|
+
# <tt>:levels</tt>::
|
57
|
+
# Use a custom Sawmill::LevelGroup. Normally, you should leave this
|
58
|
+
# set to the default, which is Sawmill::STANDARD_LEVELS.
|
59
|
+
# <tt>:level</tt>::
|
60
|
+
# Default level to use for log messages when no level is explicitly
|
61
|
+
# provided. By default, this is set to the level group's default,
|
62
|
+
# which in the case of the standard levels is :INFO.
|
63
|
+
# <tt>:attribute_level</tt>::
|
64
|
+
# Default level to use for attributes when no level is explicitly
|
65
|
+
# provided. By default, this is set to the level group's highest,
|
66
|
+
# level, which in the case of the standard levels is :ANY.
|
67
|
+
# <tt>:progname</tt>::
|
68
|
+
# Progname to use in log messages. Default is "sawmill".
|
69
|
+
# <tt>:record_progname</tt>::
|
70
|
+
# Progname to use in special log entries dealing with log records
|
71
|
+
# (i.e. record delimiters and attribute messages). Default is the
|
72
|
+
# same as the normal progname setting.
|
73
|
+
# <tt>:record_id_generator</tt>::
|
74
|
+
# A proc that generates and returns a new record ID if one is not
|
75
|
+
# explicitly passed into begin_record. If you do not provide a
|
76
|
+
# generator, the default one is used, which generates an ID using the
|
77
|
+
# variant 4 (random) UUID standard.
|
78
|
+
# <tt>:processor</tt>::
|
79
|
+
# A processor for log entries generated by this logger.
|
80
|
+
# If not specified, log entries are written out to STDOUT.
|
81
|
+
|
82
|
+
def initialize(opts_={})
|
83
|
+
@levels = opts_[:levels] || ::Sawmill::STANDARD_LEVELS
|
84
|
+
@level = @levels.get(opts_[:level])
|
85
|
+
if opts_.include?(:attribute_level)
|
86
|
+
@attribute_level = @levels.get(opts_[:attribute_level])
|
87
|
+
else
|
88
|
+
@attribute_level = @levels.highest
|
89
|
+
end
|
90
|
+
@progname = opts_[:progname] || 'sawmill'
|
91
|
+
@record_progname = opts_[:record_progname] || @progname
|
92
|
+
@record_id_generator = opts_[:record_id_generator] || _get_default_record_id_generator
|
93
|
+
@processor = opts_[:processor] || ::Sawmill::Formatter.new(STDOUT)
|
94
|
+
@current_record_id = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Emit a log message. This method has the same behavior as the
|
99
|
+
# corresponding method in ruby's logger class.
|
100
|
+
|
101
|
+
def add(level_, message_=nil, progname_=nil, &block_)
|
102
|
+
level_obj_ = @levels.get(level_)
|
103
|
+
if level_obj_.nil?
|
104
|
+
raise Errors::UnknownLevelError, level_
|
105
|
+
end
|
106
|
+
return true if level_obj_ < @level
|
107
|
+
progname_ ||= @progname
|
108
|
+
if message_.nil?
|
109
|
+
if block_given?
|
110
|
+
message_ = yield
|
111
|
+
else
|
112
|
+
message_ = progname_
|
113
|
+
progname_ = @progname
|
114
|
+
end
|
115
|
+
end
|
116
|
+
case message_
|
117
|
+
when ::String
|
118
|
+
# Do nothing
|
119
|
+
when ::Exception
|
120
|
+
message_ = "#{message_.message} (#{message_.class})\n" +
|
121
|
+
(message_.backtrace || []).join("\n")
|
122
|
+
else
|
123
|
+
message_ = message_.inspect
|
124
|
+
end
|
125
|
+
@processor.message(Entry::Message.new(level_obj_, Time.now, progname_, @current_record_id, message_))
|
126
|
+
true
|
127
|
+
end
|
128
|
+
alias_method :log, :add
|
129
|
+
|
130
|
+
|
131
|
+
def to_s # :nodoc:
|
132
|
+
inspect
|
133
|
+
end
|
134
|
+
|
135
|
+
def inspect # :nodoc:
|
136
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} progname=#{@progname.inspect} level=#{@level.name}>"
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
# Emits an "unknown" log entry. This is equivalent to the corresponding
|
141
|
+
# method in ruby's logger class, which dumps the given string to the log
|
142
|
+
# device without any formatting. Normally, you would not use this method
|
143
|
+
# because it bypasses the log formatting and parsing capability.
|
144
|
+
|
145
|
+
def <<(message_)
|
146
|
+
add(@levels.default, message_)
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
# Emits a begin_record log entry. This begins a new log record.
|
151
|
+
#
|
152
|
+
# If you pass a string ID, that ID is used as the record ID for the new
|
153
|
+
# log record. If you leave it as nil, an ID is generated for you, using
|
154
|
+
# the record id generator for this logger. In either case, the record ID
|
155
|
+
# for the new record is returned.
|
156
|
+
#
|
157
|
+
# If you call this when a record is already open, the current record is
|
158
|
+
# automatically closed before the new record is opened. That is, an
|
159
|
+
# end_record is implicitly called in this case.
|
160
|
+
|
161
|
+
def begin_record(id_=nil)
|
162
|
+
end_record if @current_record_id
|
163
|
+
@current_record_id = (id_ || @record_id_generator.call).to_s
|
164
|
+
@processor.begin_record(Entry::BeginRecord.new(@levels.highest, Time.now, @record_progname, @current_record_id))
|
165
|
+
@current_record_id
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Returns the record ID for the currently open log record, or nil if
|
170
|
+
# there is not a log record currently open.
|
171
|
+
|
172
|
+
def current_record_id
|
173
|
+
@current_record_id
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
# Ends the current log record by emitting an end_record log entry, if
|
178
|
+
# a record is currently open. Returns the record ID of the ended log
|
179
|
+
# record if one was open, or nil if no log record was open.
|
180
|
+
|
181
|
+
def end_record
|
182
|
+
if @current_record_id
|
183
|
+
@processor.end_record(Entry::EndRecord.new(@levels.highest, Time.now, @record_progname, @current_record_id))
|
184
|
+
id_ = @current_record_id
|
185
|
+
@current_record_id = nil
|
186
|
+
id_
|
187
|
+
else
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# Emits an attribute log entry in the current record.
|
194
|
+
# You must specify a key and a value as strings, and an operation.
|
195
|
+
# The operation defaults to <tt>:set</tt> if not specified.
|
196
|
+
#
|
197
|
+
# If you specify a level, it will be used; otherwise the logger's
|
198
|
+
# default attribute level is used.
|
199
|
+
# Raises Errors::UnknownLevelError if you specify a level that doesn't
|
200
|
+
# exist.
|
201
|
+
|
202
|
+
def attribute(key_, value_, operation_=nil, level_=true, progname_=nil)
|
203
|
+
if level_ == true
|
204
|
+
level_obj_ = @attribute_level
|
205
|
+
else
|
206
|
+
level_obj_ = @levels.get(level_)
|
207
|
+
if level_obj_.nil?
|
208
|
+
raise Errors::UnknownLevelError, level_
|
209
|
+
end
|
210
|
+
end
|
211
|
+
return true if level_obj_ < @level
|
212
|
+
@processor.attribute(Entry::Attribute.new(level_obj_, Time.now, progname_ || @record_progname, @current_record_id, key_, value_, operation_))
|
213
|
+
true
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
# Emits a set-attribute log entry in the current record.
|
218
|
+
# You must specify a key and a value as strings.
|
219
|
+
|
220
|
+
|
221
|
+
def set_attribute(key_, value_)
|
222
|
+
attribute(key_, value_, :set)
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
# Emits an append-attribute log entry in the current record.
|
227
|
+
# You must specify a key and a value as strings.
|
228
|
+
|
229
|
+
|
230
|
+
def append_attribute(key_, value_)
|
231
|
+
attribute(key_, value_, :append)
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
# Close the logger by closing the log entry processor to which it is
|
236
|
+
# emitting log entries.
|
237
|
+
|
238
|
+
def close
|
239
|
+
@processor.close
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
# Get the current progname setting for this logger
|
244
|
+
|
245
|
+
def progname
|
246
|
+
@progname
|
247
|
+
end
|
248
|
+
|
249
|
+
|
250
|
+
# Set the current progname setting for this logger
|
251
|
+
|
252
|
+
def progname=(value_)
|
253
|
+
@progname = value_.to_s.gsub(/\s+/, '')
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
# Get the current level setting for this logger as a Sawmill::Level.
|
258
|
+
|
259
|
+
def level
|
260
|
+
@level
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
# Set the current level setting for this logger.
|
265
|
+
# You may specify the level as a string, a symbol, an integer, or a
|
266
|
+
# Sawmill::Level. Ruby's logger constants such as ::Logger::INFO
|
267
|
+
# will also work.
|
268
|
+
|
269
|
+
def level=(value_)
|
270
|
+
if value_.kind_of?(Level)
|
271
|
+
@level = value_
|
272
|
+
else
|
273
|
+
level_obj_ = @levels.get(value_)
|
274
|
+
if level_obj_.nil?
|
275
|
+
raise Errors::UnknownLevelError, value_
|
276
|
+
end
|
277
|
+
@level = level_obj_
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
alias_method :sev_threshold=, :level=
|
282
|
+
alias_method :sev_threshold, :level
|
283
|
+
|
284
|
+
|
285
|
+
# Provide a block that generates and returns a unique record ID string.
|
286
|
+
# This block will be called when begin_record is called without an
|
287
|
+
# explicit ID provided. If you do not provide a block, Sawmill will use
|
288
|
+
# a default generator which uses the variant 4 (random) UUID standard.
|
289
|
+
|
290
|
+
def to_generate_record_id(&block_)
|
291
|
+
@record_id_generator = block_ || _get_default_record_id_generator
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
# You may call additional methods on the logger as shortcuts to log
|
296
|
+
# messages at specific levels, or to query whether the logger is logging
|
297
|
+
# to a given level. These methods match the corresponding methods in the
|
298
|
+
# classic ruby logger object, except that they are configurable for
|
299
|
+
# custom level schemes.
|
300
|
+
#
|
301
|
+
# For example, in the standard level scheme, the method "info" is
|
302
|
+
# defined, so you may call:
|
303
|
+
#
|
304
|
+
# logger.info("MainApp") { "Received connection from #{ip}" }
|
305
|
+
# # ...
|
306
|
+
# logger.info "Waiting for input from user"
|
307
|
+
# # ...
|
308
|
+
# logger.info { "User typed #{input}" }
|
309
|
+
#
|
310
|
+
# You may also call:
|
311
|
+
#
|
312
|
+
# logger.info? # Returns true if INFO messages are accepted
|
313
|
+
#
|
314
|
+
# Methods available in the standard level scheme are as follows:
|
315
|
+
#
|
316
|
+
# * <tt>debug</tt>
|
317
|
+
# * <tt>info</tt>
|
318
|
+
# * <tt>warn</tt>
|
319
|
+
# * <tt>error</tt>
|
320
|
+
# * <tt>fatal</tt>
|
321
|
+
# * <tt>unknown</tt>
|
322
|
+
# * <tt>any</tt>
|
323
|
+
#
|
324
|
+
# The "unknown" and "any" methods both correspond to the +ANY+ level.
|
325
|
+
# The latter is the preferred name under Sawmill. The former is for
|
326
|
+
# backward compatibility with ruby's classic logger.
|
327
|
+
|
328
|
+
def method_missing(method_, *args_, &block_)
|
329
|
+
method_name_ = method_.to_s
|
330
|
+
question_ = method_name_[-1..-1] == '?'
|
331
|
+
method_name_ = method_name_[0..-2] if question_
|
332
|
+
level_ = @levels.lookup_method(method_name_)
|
333
|
+
return super(method_, *args_, &block_) unless level_
|
334
|
+
if question_
|
335
|
+
level_ >= @level
|
336
|
+
else
|
337
|
+
add(level_, nil, args_[0], &block_)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
|
342
|
+
def _get_default_record_id_generator # :nodoc:
|
343
|
+
if RUBY_VERSION >= '1.9'
|
344
|
+
lambda do
|
345
|
+
uuid_ = SecureRandom.hex(32)
|
346
|
+
uuid_[12] = '4'
|
347
|
+
uuid_[16] = (uuid_[16,1].to_i(16)&3|8).to_s(16)
|
348
|
+
uuid_.insert(8, '-')
|
349
|
+
uuid_.insert(13, '-')
|
350
|
+
uuid_.insert(18, '-')
|
351
|
+
uuid_.insert(23, '-')
|
352
|
+
uuid_
|
353
|
+
end
|
354
|
+
else
|
355
|
+
lambda do
|
356
|
+
uuid_ = Kernel.rand(0x100000000000000000000000000000000).to_s(16).rjust(32, '0')
|
357
|
+
uuid_[12] = '4'
|
358
|
+
uuid_[16] = (uuid_[16,1].to_i(16)&3|8).to_s(16)
|
359
|
+
uuid_.insert(8, '-')
|
360
|
+
uuid_.insert(13, '-')
|
361
|
+
uuid_.insert(18, '-')
|
362
|
+
uuid_.insert(23, '-')
|
363
|
+
uuid_
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
private :_get_default_record_id_generator
|
368
|
+
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Sawmill stream parser utility
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2009 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
module Sawmill
|
38
|
+
|
39
|
+
|
40
|
+
# A logfile parser that parses log entries from a logfile and sends them
|
41
|
+
# to an entry processor.
|
42
|
+
|
43
|
+
class Parser
|
44
|
+
|
45
|
+
# :stopdoc:
|
46
|
+
LINE_REGEXP = /^\[\s*([[:graph:]]+)\s+(\d{4})-(\d{2})-(\d{2})(T|\s)(\d{2}):(\d{2}):(\d{2})(.(\d{1,6}))?Z?\s?([+-]\d{4})?\s+([[:graph:]]+)(\s+([[:graph:]]+))?\s+([\^$.=])\]\s(.*)$/
|
47
|
+
DIRECTIVE_REGEXP = /^#\s+sawmill_format:\s+(\w+)=(.*)$/
|
48
|
+
ATTRIBUTE_REGEXP = /^([[:graph:]]+)\s([=+\/-])\s/
|
49
|
+
# :startdoc:
|
50
|
+
|
51
|
+
|
52
|
+
# Create a new parser that reads from the given stream.
|
53
|
+
#
|
54
|
+
# You should provide a processor to receive the data from the logfile.
|
55
|
+
# The processor may be either an entry processor or a record processor.
|
56
|
+
# You may also pass nil for the processor. In this case, the generated
|
57
|
+
# log entries will not be sent to a processor but will still be returned
|
58
|
+
# by the parse_one_entry method.
|
59
|
+
#
|
60
|
+
# Recognized options include:
|
61
|
+
#
|
62
|
+
# <tt>:levels</tt>
|
63
|
+
# Sawmill::LevelGroup to use to parse log levels.
|
64
|
+
# If not specified, Sawmill::STANDARD_LEVELS is used by default.
|
65
|
+
# <tt>:emit_incomplete_records_on_complete</tt>
|
66
|
+
# If set to true, causes any incomplete log records to be emitted
|
67
|
+
# in their incomplete state when EOF is reached.
|
68
|
+
|
69
|
+
def initialize(io_, processor_, opts_={})
|
70
|
+
@io = io_
|
71
|
+
@record_processor = nil
|
72
|
+
@processor = nil
|
73
|
+
if processor_.respond_to?(:record) && processor_.respond_to?(:extra_entry)
|
74
|
+
@processor = RecordBuilder.new(processor_)
|
75
|
+
elsif processor_.respond_to?(:begin_record) && processor_.respond_to?(:end_record)
|
76
|
+
@processor = processor_
|
77
|
+
end
|
78
|
+
@levels = opts_[:levels] || ::Sawmill::STANDARD_LEVELS
|
79
|
+
@emit_incomplete_records_on_complete = opts_[:emit_incomplete_records_on_complete]
|
80
|
+
@current_record_id = nil
|
81
|
+
@parser_directives = {}
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# Parse one log entry from the stream and emit it to the processor.
|
86
|
+
# Also returns the log entry.
|
87
|
+
# Returns nil if EOF has been reached.
|
88
|
+
|
89
|
+
def parse_one_entry
|
90
|
+
str_ = @io.gets
|
91
|
+
entry_ = nil
|
92
|
+
if str_
|
93
|
+
match_ = LINE_REGEXP.match(str_)
|
94
|
+
if match_
|
95
|
+
level_ = @levels.get(match_[1])
|
96
|
+
timestamp_ = ::Time.utc(match_[2].to_i, match_[3].to_i, match_[4].to_i,
|
97
|
+
match_[6].to_i, match_[7].to_i, match_[8].to_i, match_[10].to_s.ljust(6, '0').to_i)
|
98
|
+
offset_ = match_[11].to_i
|
99
|
+
if offset_ != 0
|
100
|
+
neg_ = offset_ < 0
|
101
|
+
offset_ = -offset_ if neg_
|
102
|
+
secs_ = offset_ / 100 * 3600 + offset_ % 100 * 60
|
103
|
+
if neg_
|
104
|
+
timestamp_ += secs_
|
105
|
+
else
|
106
|
+
timestamp_ -= secs_
|
107
|
+
end
|
108
|
+
end
|
109
|
+
progname_ = match_[12]
|
110
|
+
record_id_ = match_[14] || @current_record_id
|
111
|
+
type_code_ = match_[15]
|
112
|
+
str_ = match_[16]
|
113
|
+
if str_ =~ /(\\+)$/
|
114
|
+
count_ = $1.length
|
115
|
+
str_ = $` + "\\"*(count_/2)
|
116
|
+
while count_ % 2 == 1
|
117
|
+
str2_ = @io.gets
|
118
|
+
if str2_ && str2_ =~ /(\\*)\n?$/
|
119
|
+
count_ = $1.length
|
120
|
+
str_ << "\n" << $` << "\\"*(count_/2)
|
121
|
+
else
|
122
|
+
break
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
case type_code_
|
127
|
+
when '^'
|
128
|
+
if str_ =~ /^BEGIN\s/
|
129
|
+
@current_record_id = $'
|
130
|
+
entry_ = Entry::BeginRecord.new(level_, timestamp_, progname_, @current_record_id)
|
131
|
+
@processor.begin_record(entry_) if @processor
|
132
|
+
end
|
133
|
+
when '$'
|
134
|
+
if str_ =~ /^END\s/
|
135
|
+
@current_record_id = $'
|
136
|
+
entry_ = Entry::EndRecord.new(level_, timestamp_, progname_, @current_record_id)
|
137
|
+
@current_record_id = nil
|
138
|
+
@processor.end_record(entry_) if @processor
|
139
|
+
end
|
140
|
+
when '='
|
141
|
+
if str_ =~ ATTRIBUTE_REGEXP
|
142
|
+
key_ = $1
|
143
|
+
opcode_ = $2
|
144
|
+
value_ = $'
|
145
|
+
operation_ = opcode_ == '+' ? :append : :set
|
146
|
+
entry_ = Entry::Attribute.new(level_, timestamp_, progname_, record_id_, key_, value_, operation_)
|
147
|
+
@processor.attribute(entry_) if @processor
|
148
|
+
end
|
149
|
+
end
|
150
|
+
unless entry_
|
151
|
+
entry_ = Entry::Message.new(level_, timestamp_, progname_, record_id_, str_)
|
152
|
+
@processor.message(entry_) if @processor
|
153
|
+
end
|
154
|
+
else
|
155
|
+
if str_ =~ DIRECTIVE_REGEXP
|
156
|
+
@parser_directives[$1] = $2
|
157
|
+
end
|
158
|
+
entry_ = Entry::UnknownData.new(str_)
|
159
|
+
@processor.unknown_data(entry_) if @processor.respond_to?(:unknown_data)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
if @emit_incomplete_records_on_complete && @processor.respond_to?(:emit_incomplete_records)
|
163
|
+
@processor.emit_incomplete_records
|
164
|
+
end
|
165
|
+
end
|
166
|
+
entry_
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Parse the rest of the stream until EOF is reached, and emit the log
|
171
|
+
# entries to the processor.
|
172
|
+
|
173
|
+
def parse_all
|
174
|
+
while process_one_entry; end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
end
|