sawmill 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/History.rdoc +3 -0
  2. data/README.rdoc +81 -0
  3. data/Rakefile +147 -0
  4. data/lib/sawmill/entry.rb +307 -0
  5. data/lib/sawmill/entry_classifier.rb +75 -0
  6. data/lib/sawmill/entry_processor/build_records.rb +157 -0
  7. data/lib/sawmill/entry_processor/conditionals.rb +444 -0
  8. data/lib/sawmill/entry_processor/filter_basic_fields.rb +145 -0
  9. data/lib/sawmill/entry_processor/format.rb +228 -0
  10. data/lib/sawmill/entry_processor/simple_queue.rb +116 -0
  11. data/lib/sawmill/entry_processor.rb +158 -0
  12. data/lib/sawmill/errors.rb +66 -0
  13. data/lib/sawmill/level.rb +264 -0
  14. data/lib/sawmill/log_record_middleware.rb +93 -0
  15. data/lib/sawmill/logger.rb +373 -0
  16. data/lib/sawmill/parser.rb +181 -0
  17. data/lib/sawmill/record.rb +255 -0
  18. data/lib/sawmill/record_processor/conditionals.rb +330 -0
  19. data/lib/sawmill/record_processor/decompose.rb +75 -0
  20. data/lib/sawmill/record_processor/filter_by_attributes.rb +87 -0
  21. data/lib/sawmill/record_processor/filter_by_record_id.rb +77 -0
  22. data/lib/sawmill/record_processor/format.rb +88 -0
  23. data/lib/sawmill/record_processor/simple_queue.rb +117 -0
  24. data/lib/sawmill/record_processor.rb +137 -0
  25. data/lib/sawmill/rotater/base.rb +90 -0
  26. data/lib/sawmill/rotater/date_based_log_file.rb +145 -0
  27. data/lib/sawmill/rotater/shifting_log_file.rb +166 -0
  28. data/lib/sawmill/rotater.rb +236 -0
  29. data/lib/sawmill/util/queue.rb +138 -0
  30. data/lib/sawmill/version.rb +47 -0
  31. data/lib/sawmill.rb +78 -0
  32. data/tests/tc_entry_processors.rb +138 -0
  33. data/tests/tc_formatter_parser.rb +144 -0
  34. data/tests/tc_levels.rb +118 -0
  35. data/tests/tc_logger.rb +315 -0
  36. data/tests/tc_record_processors.rb +117 -0
  37. data/tests/tc_records.rb +206 -0
  38. metadata +116 -0
@@ -0,0 +1,255 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Sawmill log record 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
+ module Sawmill
38
+
39
+
40
+ # A log record.
41
+ #
42
+ # Log records are sequences of related log entries with a common record
43
+ # ID, beginning with a begin_record and ending with an end_record. Log
44
+ # records therefore can be analyzed as a group, for example to measure the
45
+ # time taken by an aggregate action such as a website request.
46
+ #
47
+ # Log records must follow a particular protocol:
48
+ #
49
+ # * The first entry must be begin_record.
50
+ # * The last entry must be end_record.
51
+ # * No other begin_record or end_record entries may be present.
52
+ # * All entries must have the same non-nil record_id.
53
+ # * Entries must be in nondecreasing order of timestamp.
54
+ #
55
+ # The only exception to these rules are incomplete log records, in which
56
+ # the final end_record is not present.
57
+
58
+ class Record
59
+
60
+
61
+ # Create a log record.
62
+ #
63
+ # You may optionally pass in an array of log entries to populate the
64
+ # record either partially or completely. If you do, note that
65
+ # Errors::IllegalRecordError may be raised if the log entries do not
66
+ # follow the log record protocol-- e.g. if the first entry is not a
67
+ # begin_record, etc.
68
+
69
+ def initialize(entries_=nil)
70
+ @started = false
71
+ @complete = false
72
+ @message_count = 0
73
+ @entries = []
74
+ @attributes = {}
75
+ if entries_ && entries_.size > 0
76
+ entries_.each do |entry_|
77
+ add_entry(entry_)
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ def to_s # :nodoc:
84
+ "#{type}: #{@line}"
85
+ end
86
+
87
+ def inspect # :nodoc:
88
+ "#<#{self.class}:0x#{object_id.to_s(16)} record_id=#{record_id.inspect}>"
89
+ end
90
+
91
+
92
+ # Append a log entry to this record.
93
+ #
94
+ # Entries must be added in order. Raises Errors::IllegalRecordError if
95
+ # the log record protocol is violated
96
+
97
+ def add_entry(entry_)
98
+ empty_ = @entries.size == 0
99
+ if entry_.type == :unknown_data
100
+ raise Errors::IllegalRecordError, "You cannot add an unknown_data entry to a record"
101
+ end
102
+ if empty_ && entry_.type != :begin_record
103
+ raise Errors::IllegalRecordError, "First entry in a record must be a begin_record"
104
+ elsif !empty_ && entry_.type == :begin_record
105
+ raise Errors::IllegalRecordError, "Extra begin_record found"
106
+ end
107
+ if @complete
108
+ raise Errors::IllegalRecordError, "Cannot have entries after end_record"
109
+ end
110
+ if empty_
111
+ if entry_.record_id.nil?
112
+ raise Errors::IllegalRecordError, "Entry has no record_id"
113
+ end
114
+ else
115
+ last_ = @entries.last
116
+ if last_.record_id != entry_.record_id
117
+ raise Errors::IllegalRecordError, "Entry has a mismatching record_id"
118
+ end
119
+ if last_.timestamp > entry_.timestamp
120
+ raise Errors::IllegalRecordError, "Entry's timestamp is earlier than the previous entry"
121
+ end
122
+ end
123
+ case entry_.type
124
+ when :begin_record
125
+ @started = true
126
+ when :end_record
127
+ @complete = true
128
+ when :attribute
129
+ case entry_.operation
130
+ when :set
131
+ @attributes[entry_.key] = entry_.value
132
+ when :append
133
+ val_ = @attributes[entry_.key]
134
+ case val_
135
+ when Array
136
+ val_ << entry_.value
137
+ when String
138
+ @attributes[entry_.key] = [val_, entry_.value]
139
+ when nil
140
+ @attributes[entry_.key] = [entry_.value]
141
+ end
142
+ end
143
+ when :message
144
+ @message_count += 1
145
+ end
146
+ @entries << entry_
147
+ end
148
+
149
+
150
+ # Returns true if the initial begin_record has been added.
151
+
152
+ def started?
153
+ @started
154
+ end
155
+
156
+
157
+ # Returns true if the final end_record has been added.
158
+
159
+ def complete?
160
+ @complete
161
+ end
162
+
163
+
164
+ # Returns the record ID as a string.
165
+
166
+ def record_id
167
+ @entries.size > 0 ? @entries.first.record_id : nil
168
+ end
169
+
170
+
171
+ # Returns the beginning timestamp as a Time object, if the log record
172
+ # has been started, or nil otherwise.
173
+
174
+ def begin_timestamp
175
+ @started ? @entries.first.timestamp : nil
176
+ end
177
+
178
+
179
+ # Returns the ending timestamp as a Time object, if the log record
180
+ # has been completed, or nil otherwise.
181
+
182
+ def end_timestamp
183
+ @complete ? @entries.last.timestamp : nil
184
+ end
185
+
186
+
187
+ # Returns the number of log entries currently in this record.
188
+
189
+ def entry_count
190
+ @entries.size
191
+ end
192
+
193
+ alias_method :size, :entry_count
194
+
195
+
196
+ # Returns the number of message entries currently in this record.
197
+
198
+ def message_count
199
+ @message_count
200
+ end
201
+
202
+
203
+ # Iterate over all log entries, passing each to the given block.
204
+
205
+ def each_entry(&block_)
206
+ @entries.each(&block_)
207
+ end
208
+
209
+
210
+ # Iterate over all log message entries, passing each to the given block.
211
+
212
+ def each_message
213
+ @entries.each do |entry_|
214
+ if entry_.type == :message
215
+ yield entry_
216
+ end
217
+ end
218
+ end
219
+
220
+
221
+ # Returns an array of all log entries.
222
+
223
+ def all_entries
224
+ @entries.dup
225
+ end
226
+
227
+
228
+ # Returns an array of all log message entries.
229
+
230
+ def all_messages
231
+ @entries.find_all{ |entry_| entry_.type == :message }
232
+ end
233
+
234
+
235
+ # Get the value of the given attribute.
236
+ # Returns a string if the attribute has a single value.
237
+ # Returns an array of strings if the attribute has multiple values.
238
+ # Returns nil if the attribute is not set.
239
+
240
+ def attribute(key_)
241
+ @attributes[key_.to_s]
242
+ end
243
+
244
+
245
+ # Get an array of attribute keys present in this log record.
246
+
247
+ def attribute_keys
248
+ @attributes.keys
249
+ end
250
+
251
+
252
+ end
253
+
254
+
255
+ end
@@ -0,0 +1,330 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Sawmill basic entry processors that implement conditionals
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
+ module RecordProcessor
40
+
41
+
42
+ # An "if" conditional.
43
+ #
44
+ # Takes a boolean condition processor and executes a processor on true
45
+ # (and optionally another one on false).
46
+ #
47
+ # For example, this builds a processor that sends formatted log records
48
+ # to STDOUT only if they have a "user" attribute of "daniel":
49
+ #
50
+ # processor = Sawmill::RecordProcessor.build do
51
+ # If(FilterByAttributes('user' => 'daniel'), Format(STDOUT))
52
+ # end
53
+
54
+ class If < Base
55
+
56
+
57
+ # Create an "if" conditional.
58
+ #
59
+ # The first parameter must be a processor whose methods return a
60
+ # boolean value indicating whether the record should be accepted.
61
+ # The second parameter is a processor to run on accepted records.
62
+ # The optional third parameter is an "else" processor to run on
63
+ # rejected records.
64
+
65
+ def initialize(condition_, on_true_, on_false_=nil)
66
+ @condition = condition_
67
+ @on_true = on_true_
68
+ @on_false = on_false_
69
+ end
70
+
71
+ def record(record_)
72
+ if @condition.record(record_)
73
+ @on_true.record(record_)
74
+ elsif @on_false
75
+ @on_false.record(record_)
76
+ end
77
+ end
78
+
79
+ def extra_entry(entry_)
80
+ if @condition.extra_entry(entry_)
81
+ @on_true.extra_entry(entry_)
82
+ elsif @on_false
83
+ @on_false.extra_entry(entry_)
84
+ end
85
+ end
86
+
87
+ def close
88
+ @on_true.close
89
+ @on_false.close if @on_false
90
+ end
91
+
92
+
93
+ end
94
+
95
+
96
+ # A boolean processor that returns the boolean negation of a given
97
+ # processor.
98
+ #
99
+ # For example, this builds a processor that sends formatted log records
100
+ # to STDOUT only if they do NOT have a "user" attribute of "daniel":
101
+ #
102
+ # processor = Sawmill::RecordProcessor.build do
103
+ # If(Not(FilterByAttributes('user' => 'daniel')), Format(STDOUT))
104
+ # end
105
+
106
+ class Not < Base
107
+
108
+
109
+ # Create a "not" boolean.
110
+ # The parameter is a boolean processor to run. This processor returns
111
+ # the boolean negation of its output.
112
+
113
+ def initialize(child_)
114
+ @child = _interpret_processor(child_)
115
+ end
116
+
117
+ def record(record_)
118
+ !@child.record(record_)
119
+ end
120
+
121
+ def extra_entry(entry_)
122
+ !@child.extra_entry(record_)
123
+ end
124
+
125
+ def close
126
+ @child.close
127
+ end
128
+
129
+
130
+ end
131
+
132
+
133
+ # A boolean processor that returns true if and only if all its child
134
+ # processors return true. This version short-circuits the processing,
135
+ # so once one child returns false, subsequent children are not called
136
+ # at all.
137
+ #
138
+ # For example, this builds a processor that sends formatted log records
139
+ # to STDOUT only if they have a "user" attribute of "daniel" AND their
140
+ # record ID is '12345678':
141
+ #
142
+ # processor = Sawmill::RecordProcessor.build do
143
+ # If(And(FilterByAttributes('user' => 'daniel'),
144
+ # FilterByRecordID('12345678')),
145
+ # Format(STDOUT))
146
+ # end
147
+
148
+ class And < Base
149
+
150
+
151
+ # Create an "and" boolean.
152
+ # The parameters are child processors whose return values should be
153
+ # combined with an AND operation.
154
+
155
+ def initialize(*children_)
156
+ @children = _interpret_processor_array(children_)
157
+ end
158
+
159
+ def record(record_)
160
+ @children.each do |child_|
161
+ return false unless child_.record(record_)
162
+ end
163
+ true
164
+ end
165
+
166
+ def extra_entry(entry_)
167
+ @children.each do |child_|
168
+ return false unless child_.extra_entry(entry_)
169
+ end
170
+ true
171
+ end
172
+
173
+ def close
174
+ @children.each{ |child_| child_.close }
175
+ end
176
+
177
+
178
+ end
179
+
180
+
181
+ # A boolean processor that returns true if and only if any of its child
182
+ # processors returns true. This version short-circuits the processing,
183
+ # so once one child returns true, subsequent children are not called
184
+ # at all.
185
+ #
186
+ # For example, this builds a processor that sends formatted log records
187
+ # to STDOUT only if they have a "user" attribute of "daniel" OR their
188
+ # record ID is '12345678':
189
+ #
190
+ # processor = Sawmill::RecordProcessor.build do
191
+ # If(Or(FilterByAttributes('user' => 'daniel'),
192
+ # FilterByRecordID('12345678')),
193
+ # Format(STDOUT))
194
+ # end
195
+
196
+ class Or < Base
197
+
198
+
199
+ # Create an "or" boolean.
200
+ # The parameters are child processors whose return values should be
201
+ # combined with an OR operation.
202
+
203
+ def initialize(*children_)
204
+ @children = _interpret_processor_array(children_)
205
+ end
206
+
207
+ def record(record_)
208
+ @children.each do |child_|
209
+ return true if child_.record(record_)
210
+ end
211
+ false
212
+ end
213
+
214
+ def extra_entry(entry_)
215
+ @children.each do |child_|
216
+ return true if child_.extra_entry(entry_)
217
+ end
218
+ false
219
+ end
220
+
221
+ def close
222
+ @children.each{ |child_| child_.close }
223
+ end
224
+
225
+
226
+ end
227
+
228
+
229
+ # A boolean processor that returns true if and only if all its child
230
+ # processors return true. This version does not short-circuit the
231
+ # processing, so all children are always called even if an early one
232
+ # returns false. Thus, this processor is also a good one to use as a
233
+ # multiplexor to simply run a bunch of processors.
234
+ #
235
+ # For example, this builds a processor that sends formatted log records
236
+ # to STDOUT only if they have a "user" attribute of "daniel" AND their
237
+ # record ID is '12345678':
238
+ #
239
+ # processor = Sawmill::RecordProcessor.build do
240
+ # If(All(FilterByAttributes('user' => 'daniel'),
241
+ # FilterByRecordID('12345678')),
242
+ # Format(STDOUT))
243
+ # end
244
+ #
245
+ # This processor just formats both to STDOUT and STDERR:
246
+ #
247
+ # processor = Sawmill::RecordProcessor.build do
248
+ # All(Format(STDOUT), Format(STDERR))
249
+ # end
250
+
251
+ class All < Base
252
+
253
+
254
+ # Create an "all" boolean.
255
+ # The parameters are child processors whose return values should be
256
+ # combined with an AND operation.
257
+
258
+ def initialize(*children_)
259
+ @children = _interpret_processor_array(children_)
260
+ end
261
+
262
+ def record(record_)
263
+ @children.inject(true) do |result_, child_|
264
+ child_.record(record_) && result_
265
+ end
266
+ end
267
+
268
+ def extra_entry(entry_)
269
+ @children.inject(true) do |result_, child_|
270
+ child_.extra_entry(entry_) && result_
271
+ end
272
+ end
273
+
274
+ def close
275
+ @children.each{ |child_| child_.close }
276
+ end
277
+
278
+
279
+ end
280
+
281
+
282
+ # A boolean processor that returns true if and only if any of its child
283
+ # processors returns true. This version does not short-circuit the
284
+ # processing, so all children are always called even if an early one
285
+ # returns true.
286
+ #
287
+ # For example, this builds a processor that sends formatted log records
288
+ # to STDOUT only if they have a "user" attribute of "daniel" OR their
289
+ # record ID is '12345678':
290
+ #
291
+ # processor = Sawmill::RecordProcessor.build do
292
+ # If(Any(FilterByAttributes('user' => 'daniel'),
293
+ # FilterByRecordID('12345678')),
294
+ # Format(STDOUT))
295
+ # end
296
+
297
+ class Any < Base
298
+
299
+
300
+ # Create an "any" boolean.
301
+ # The parameters are child processors whose return values should be
302
+ # combined with an OR operation.
303
+
304
+ def initialize(*children_)
305
+ @children = _interpret_processor_array(children_)
306
+ end
307
+
308
+ def record(record_)
309
+ @children.inject(false) do |result_, child_|
310
+ child_.record(record_) || result_
311
+ end
312
+ end
313
+
314
+ def extra_entry(entry_)
315
+ @children.inject(false) do |result_, child_|
316
+ child_.extra_entry(entry_) || result_
317
+ end
318
+ end
319
+
320
+ def close
321
+ @children.each{ |child_| child_.close }
322
+ end
323
+
324
+
325
+ end
326
+
327
+
328
+ end
329
+
330
+ end
@@ -0,0 +1,75 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Sawmill record processor that decomposes a record into entries
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
+ module RecordProcessor
40
+
41
+
42
+ # A processor that decomposes records into constituent entries and
43
+ # passes those entries to an entry processor.
44
+
45
+ class Decompose < Base
46
+
47
+
48
+ # Create a new decomposer that emits to the given entry processor.
49
+
50
+ def initialize(processor_, opts_={})
51
+ @processor = processor_
52
+ @classifier = ::Sawmill::EntryClassifier.new(processor_)
53
+ end
54
+
55
+
56
+ def record(record_)
57
+ record_.each_entry{ |entry_| @classifier.entry(entry_) }
58
+ true
59
+ end
60
+
61
+ def extra_entry(entry_)
62
+ @classifier.entry(entry_)
63
+ true
64
+ end
65
+
66
+ def close
67
+ @processor.close
68
+ end
69
+
70
+ end
71
+
72
+
73
+ end
74
+
75
+ end