fluentd 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- data/AUTHORS +1 -0
- data/COPYING +14 -0
- data/ChangeLog +178 -0
- data/README.rdoc +57 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/bin/fluent-cat +6 -0
- data/bin/fluent-gem +10 -0
- data/bin/fluentd +6 -0
- data/fluent.conf +78 -0
- data/fluentd.gemspec +116 -0
- data/lib/fluent/buffer.rb +274 -0
- data/lib/fluent/command/cat.rb +299 -0
- data/lib/fluent/command/fluentd.rb +245 -0
- data/lib/fluent/config.rb +304 -0
- data/lib/fluent/engine.rb +224 -0
- data/lib/fluent/env.rb +6 -0
- data/lib/fluent/event.rb +159 -0
- data/lib/fluent/input.rb +41 -0
- data/lib/fluent/load.rb +23 -0
- data/lib/fluent/log.rb +277 -0
- data/lib/fluent/match.rb +189 -0
- data/lib/fluent/mixin.rb +170 -0
- data/lib/fluent/output.rb +466 -0
- data/lib/fluent/parser.rb +115 -0
- data/lib/fluent/plugin.rb +145 -0
- data/lib/fluent/plugin/buf_file.rb +181 -0
- data/lib/fluent/plugin/buf_memory.rb +97 -0
- data/lib/fluent/plugin/buf_zfile.rb +84 -0
- data/lib/fluent/plugin/in_http.rb +282 -0
- data/lib/fluent/plugin/in_stream.rb +187 -0
- data/lib/fluent/plugin/in_syslog.rb +174 -0
- data/lib/fluent/plugin/in_tail.rb +150 -0
- data/lib/fluent/plugin/out_copy.rb +72 -0
- data/lib/fluent/plugin/out_file.rb +111 -0
- data/lib/fluent/plugin/out_null.rb +44 -0
- data/lib/fluent/plugin/out_roundrobin.rb +72 -0
- data/lib/fluent/plugin/out_stdout.rb +34 -0
- data/lib/fluent/plugin/out_stream.rb +128 -0
- data/lib/fluent/plugin/out_test.rb +68 -0
- data/lib/fluent/test.rb +8 -0
- data/lib/fluent/test/base.rb +63 -0
- data/lib/fluent/test/input_test.rb +89 -0
- data/lib/fluent/test/output_test.rb +93 -0
- data/lib/fluent/version.rb +5 -0
- data/test/helper.rb +6 -0
- data/test/match.rb +115 -0
- data/test/plugin/in_http.rb +84 -0
- data/test/plugin/in_stream.rb +136 -0
- data/test/plugin/out_copy.rb +55 -0
- data/test/plugin/out_file.rb +82 -0
- data/test/plugin/out_roundrobin.rb +65 -0
- data/test/plugin/out_stream.rb +74 -0
- metadata +224 -0
@@ -0,0 +1,274 @@
|
|
1
|
+
#
|
2
|
+
# Fluent
|
3
|
+
#
|
4
|
+
# Copyright (C) 2011 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module Fluent
|
19
|
+
|
20
|
+
|
21
|
+
class BufferError < StandardError
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
class Buffer
|
26
|
+
include Configurable
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def configure(conf)
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def start
|
37
|
+
end
|
38
|
+
|
39
|
+
def shutdown
|
40
|
+
end
|
41
|
+
|
42
|
+
def before_shutdown(out)
|
43
|
+
end
|
44
|
+
|
45
|
+
#def emit(key, data, chain)
|
46
|
+
#end
|
47
|
+
|
48
|
+
#def keys
|
49
|
+
#end
|
50
|
+
|
51
|
+
#def push(key)
|
52
|
+
#end
|
53
|
+
|
54
|
+
#def pop(out)
|
55
|
+
#end
|
56
|
+
|
57
|
+
#def clear!
|
58
|
+
#end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
class BufferChunk
|
63
|
+
include MonitorMixin
|
64
|
+
|
65
|
+
def initialize(key)
|
66
|
+
super()
|
67
|
+
@key = key
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_reader :key
|
71
|
+
|
72
|
+
#def <<(data)
|
73
|
+
#end
|
74
|
+
|
75
|
+
#def size
|
76
|
+
#end
|
77
|
+
|
78
|
+
def empty?
|
79
|
+
size == 0
|
80
|
+
end
|
81
|
+
|
82
|
+
#def close
|
83
|
+
#end
|
84
|
+
|
85
|
+
#def purge
|
86
|
+
#end
|
87
|
+
|
88
|
+
#def read
|
89
|
+
#end
|
90
|
+
|
91
|
+
#def open
|
92
|
+
#end
|
93
|
+
|
94
|
+
def write_to(io)
|
95
|
+
open {|i|
|
96
|
+
FileUtils.copy_stream(i, io)
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def msgpack_each(&block)
|
101
|
+
open {|io|
|
102
|
+
u = MessagePack::Unpacker.new(io)
|
103
|
+
begin
|
104
|
+
u.each(&block)
|
105
|
+
rescue EOFError
|
106
|
+
end
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
class BasicBuffer < Buffer
|
113
|
+
include MonitorMixin
|
114
|
+
|
115
|
+
def initialize
|
116
|
+
super()
|
117
|
+
@chunk_limit = 16*1024*1024 # TODO default
|
118
|
+
@queue_limit = 64 # TODO default
|
119
|
+
@parallel = false
|
120
|
+
end
|
121
|
+
|
122
|
+
attr_accessor :queue_limit, :chunk_limit
|
123
|
+
|
124
|
+
def configure(conf)
|
125
|
+
super
|
126
|
+
|
127
|
+
if chunk_limit = conf['buffer_chunk_limit']
|
128
|
+
@chunk_limit = Config.size_value(chunk_limit)
|
129
|
+
end
|
130
|
+
|
131
|
+
if queue_limit = conf['buffer_queue_limit']
|
132
|
+
@queue_limit = queue_limit.to_i
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def start
|
137
|
+
@queue, @map = resume
|
138
|
+
@queue.extend(MonitorMixin)
|
139
|
+
end
|
140
|
+
|
141
|
+
def shutdown
|
142
|
+
synchronize do
|
143
|
+
@queue.synchronize do
|
144
|
+
until @queue.empty?
|
145
|
+
@queue.shift.close
|
146
|
+
end
|
147
|
+
end
|
148
|
+
@map.each_pair {|key,chunk|
|
149
|
+
chunk.close
|
150
|
+
}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def emit(key, data, chain)
|
155
|
+
key = key.to_s
|
156
|
+
|
157
|
+
synchronize do
|
158
|
+
top = (@map[key] ||= new_chunk(key)) # TODO generate unique chunk id
|
159
|
+
|
160
|
+
if top.size + data.bytesize <= @chunk_limit
|
161
|
+
chain.next
|
162
|
+
top << data
|
163
|
+
return false
|
164
|
+
|
165
|
+
elsif data.bytesize > @chunk_limit
|
166
|
+
# TODO
|
167
|
+
raise BufferError, "received data too large"
|
168
|
+
|
169
|
+
elsif @queue.size >= @queue_limit
|
170
|
+
# TODO
|
171
|
+
raise BufferError, "queue size exceeds limit"
|
172
|
+
end
|
173
|
+
|
174
|
+
nc = new_chunk(key) # TODO generate unique chunk id
|
175
|
+
ok = false
|
176
|
+
|
177
|
+
begin
|
178
|
+
nc << data
|
179
|
+
chain.next
|
180
|
+
|
181
|
+
@queue.synchronize {
|
182
|
+
enqueue(top)
|
183
|
+
@queue << top
|
184
|
+
@map[key] = nc
|
185
|
+
}
|
186
|
+
|
187
|
+
ok = true
|
188
|
+
return @queue.size == 1
|
189
|
+
ensure
|
190
|
+
nc.purge unless ok
|
191
|
+
end
|
192
|
+
|
193
|
+
end # synchronize
|
194
|
+
end
|
195
|
+
|
196
|
+
def keys
|
197
|
+
@map.keys
|
198
|
+
end
|
199
|
+
|
200
|
+
def queue_size
|
201
|
+
@queue.size
|
202
|
+
end
|
203
|
+
|
204
|
+
#def new_chunk(key)
|
205
|
+
#end
|
206
|
+
|
207
|
+
#def resume
|
208
|
+
#end
|
209
|
+
|
210
|
+
#def enqueue(chunk)
|
211
|
+
#end
|
212
|
+
|
213
|
+
def push(key)
|
214
|
+
synchronize do
|
215
|
+
top = @map[key]
|
216
|
+
if !top || top.empty?
|
217
|
+
return false
|
218
|
+
end
|
219
|
+
|
220
|
+
@queue.synchronize do
|
221
|
+
enqueue(top)
|
222
|
+
@queue << top
|
223
|
+
@map.delete(key)
|
224
|
+
end
|
225
|
+
|
226
|
+
return true
|
227
|
+
end # synchronize
|
228
|
+
end
|
229
|
+
|
230
|
+
def pop(out)
|
231
|
+
chunk = nil
|
232
|
+
@queue.synchronize do
|
233
|
+
if @parallel
|
234
|
+
chunk = @queue.find {|c| c.try_mon_enter }
|
235
|
+
return false unless chunk
|
236
|
+
else
|
237
|
+
chunk = @queue.first
|
238
|
+
return false unless chunk
|
239
|
+
return false unless chunk.try_mon_enter
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
begin
|
244
|
+
if !chunk.empty?
|
245
|
+
write_chunk(chunk, out)
|
246
|
+
end
|
247
|
+
|
248
|
+
@queue.delete_if {|c|
|
249
|
+
c.object_id == chunk.object_id
|
250
|
+
}
|
251
|
+
|
252
|
+
chunk.purge
|
253
|
+
|
254
|
+
return !@queue.empty?
|
255
|
+
ensure
|
256
|
+
chunk.mon_exit
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def write_chunk(chunk, out)
|
261
|
+
out.write(chunk)
|
262
|
+
end
|
263
|
+
|
264
|
+
def clear!
|
265
|
+
@queue.delete_if {|chunk|
|
266
|
+
chunk.purge
|
267
|
+
true
|
268
|
+
}
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
end
|
274
|
+
|
@@ -0,0 +1,299 @@
|
|
1
|
+
#
|
2
|
+
# Fluent cat
|
3
|
+
#
|
4
|
+
# Copyright (C) 2011 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'optparse'
|
20
|
+
require 'fluent/env'
|
21
|
+
|
22
|
+
op = OptionParser.new
|
23
|
+
|
24
|
+
op.banner += " <tag>"
|
25
|
+
|
26
|
+
port = Fluent::DEFAULT_LISTEN_PORT
|
27
|
+
host = '127.0.0.1'
|
28
|
+
unix = false
|
29
|
+
socket_path = Fluent::DEFAULT_SOCKET_PATH
|
30
|
+
|
31
|
+
config_path = Fluent::DEFAULT_CONFIG_PATH
|
32
|
+
format = 'json'
|
33
|
+
|
34
|
+
op.on('-p', '--port PORT', "fluent tcp port (default: #{port})", Integer) {|i|
|
35
|
+
port = i
|
36
|
+
}
|
37
|
+
|
38
|
+
op.on('-h', '--host HOST', "fluent host (default: #{host})") {|s|
|
39
|
+
host = s
|
40
|
+
}
|
41
|
+
|
42
|
+
op.on('-u', '--unix', "use unix socket instead of tcp", TrueClass) {|b|
|
43
|
+
unix = b
|
44
|
+
}
|
45
|
+
|
46
|
+
op.on('-s', '--socket PATH', "unix socket path (default: #{socket_path})") {|s|
|
47
|
+
socket_path = s
|
48
|
+
}
|
49
|
+
|
50
|
+
op.on('-f', '--format FORMAT', "input format (default: #{format})") {|s|
|
51
|
+
format = s
|
52
|
+
}
|
53
|
+
|
54
|
+
op.on('--json', "same as: -f json", TrueClass) {|b|
|
55
|
+
format = 'json'
|
56
|
+
}
|
57
|
+
|
58
|
+
op.on('--msgpack', "same as: -f msgpack", TrueClass) {|b|
|
59
|
+
format = 'msgpack'
|
60
|
+
}
|
61
|
+
|
62
|
+
(class<<self;self;end).module_eval do
|
63
|
+
define_method(:usage) do |msg|
|
64
|
+
puts op.to_s
|
65
|
+
puts "error: #{msg}" if msg
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
begin
|
71
|
+
op.parse!(ARGV)
|
72
|
+
|
73
|
+
if ARGV.length != 1
|
74
|
+
usage nil
|
75
|
+
end
|
76
|
+
|
77
|
+
tag = ARGV.shift
|
78
|
+
|
79
|
+
rescue
|
80
|
+
usage $!.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
require 'thread'
|
85
|
+
require 'monitor'
|
86
|
+
require 'socket'
|
87
|
+
require 'json'
|
88
|
+
require 'msgpack'
|
89
|
+
|
90
|
+
|
91
|
+
class Writer
|
92
|
+
include MonitorMixin
|
93
|
+
|
94
|
+
class TimerThread
|
95
|
+
def initialize(writer)
|
96
|
+
@writer = writer
|
97
|
+
end
|
98
|
+
|
99
|
+
def start
|
100
|
+
@finish = false
|
101
|
+
@thread = Thread.new(&method(:run))
|
102
|
+
end
|
103
|
+
|
104
|
+
def shutdown
|
105
|
+
@finish = true
|
106
|
+
@thread.join
|
107
|
+
end
|
108
|
+
|
109
|
+
def run
|
110
|
+
until @finish
|
111
|
+
sleep 1
|
112
|
+
@writer.on_timer
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def initialize(tag, connector)
|
118
|
+
@tag = tag
|
119
|
+
@connector = connector
|
120
|
+
@socket = false
|
121
|
+
|
122
|
+
@socket_time = Time.now.to_i
|
123
|
+
@socket_ttl = 10 # TODO
|
124
|
+
@error_history = []
|
125
|
+
|
126
|
+
@pending = []
|
127
|
+
@pending_limit = 1024 # TODO
|
128
|
+
@retry_wait = 1
|
129
|
+
@retry_limit = 5 # TODO
|
130
|
+
|
131
|
+
super()
|
132
|
+
end
|
133
|
+
|
134
|
+
def write(record)
|
135
|
+
if record.class != Hash
|
136
|
+
raise ArgumentError, "Input must be a map (got #{record.class})"
|
137
|
+
end
|
138
|
+
|
139
|
+
entry = [Time.now.to_i, record]
|
140
|
+
synchronize {
|
141
|
+
unless write_impl([entry])
|
142
|
+
# write failed
|
143
|
+
@pending.push(entry)
|
144
|
+
|
145
|
+
while @pending.size > @pending_limit
|
146
|
+
# exceeds pending limit; trash oldest record
|
147
|
+
time, record = @pending.shift
|
148
|
+
abort_message(time, record)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
def on_timer
|
155
|
+
now = Time.now.to_i
|
156
|
+
|
157
|
+
synchronize {
|
158
|
+
unless @pending.empty?
|
159
|
+
# flush pending records
|
160
|
+
if write_impl(@pending)
|
161
|
+
# write succeeded
|
162
|
+
@pending.clear
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
if @socket && @socket_time + @socket_ttl < now
|
167
|
+
# socket is not used @socket_ttl seconds
|
168
|
+
close
|
169
|
+
end
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
def close
|
174
|
+
@socket.close
|
175
|
+
@socket = nil
|
176
|
+
end
|
177
|
+
|
178
|
+
def start
|
179
|
+
@timer = TimerThread.new(self)
|
180
|
+
@timer.start
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
def shutdown
|
185
|
+
@timer.shutdown
|
186
|
+
end
|
187
|
+
|
188
|
+
private
|
189
|
+
def write_impl(array)
|
190
|
+
socket = get_socket
|
191
|
+
unless socket
|
192
|
+
return false
|
193
|
+
end
|
194
|
+
|
195
|
+
begin
|
196
|
+
socket.write [@tag, array].to_msgpack
|
197
|
+
socket.flush
|
198
|
+
rescue
|
199
|
+
$stderr.puts "write failed: #{$!}"
|
200
|
+
close
|
201
|
+
return false
|
202
|
+
end
|
203
|
+
|
204
|
+
return true
|
205
|
+
end
|
206
|
+
|
207
|
+
def get_socket
|
208
|
+
unless @socket
|
209
|
+
unless try_connect
|
210
|
+
return nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
@socket_time = Time.now.to_i
|
215
|
+
return @socket
|
216
|
+
end
|
217
|
+
|
218
|
+
def try_connect
|
219
|
+
now = Time.now.to_i
|
220
|
+
|
221
|
+
unless @error_history.empty?
|
222
|
+
# wait before re-connecting
|
223
|
+
wait = @retry_wait * (2 ** (@error_history.size-1))
|
224
|
+
if now <= @socket_time + wait
|
225
|
+
return false
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
begin
|
230
|
+
@socket = @connector.call
|
231
|
+
@error_history.clear
|
232
|
+
return true
|
233
|
+
|
234
|
+
rescue
|
235
|
+
$stderr.puts "connect failed: #{$!}"
|
236
|
+
@error_history << $!
|
237
|
+
@socket_time = now
|
238
|
+
|
239
|
+
if @retry_limit < @error_history.size
|
240
|
+
# abort all pending records
|
241
|
+
@pending.each {|(time, record)|
|
242
|
+
abort_message(time, record)
|
243
|
+
}
|
244
|
+
@pending.clear
|
245
|
+
@error_history.clear
|
246
|
+
end
|
247
|
+
|
248
|
+
return false
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def abort_message(time, record)
|
253
|
+
$stdout.puts "!#{time}:#{record.to_json}"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
if unix
|
259
|
+
connector = Proc.new {
|
260
|
+
UNIXSocket.open(socket_path)
|
261
|
+
}
|
262
|
+
else
|
263
|
+
connector = Proc.new {
|
264
|
+
TCPSocket.new(host, port)
|
265
|
+
}
|
266
|
+
end
|
267
|
+
|
268
|
+
w = Writer.new(tag, connector)
|
269
|
+
w.start
|
270
|
+
|
271
|
+
case format
|
272
|
+
when 'json'
|
273
|
+
begin
|
274
|
+
while line = $stdin.gets
|
275
|
+
record = JSON.parse(line)
|
276
|
+
w.write(record)
|
277
|
+
end
|
278
|
+
rescue
|
279
|
+
$stderr.puts $!
|
280
|
+
exit 1
|
281
|
+
end
|
282
|
+
|
283
|
+
when 'msgpack'
|
284
|
+
begin
|
285
|
+
u = MessagePack::Unpacker.new($stdin)
|
286
|
+
u.each {|record|
|
287
|
+
w.write(record)
|
288
|
+
}
|
289
|
+
rescue EOFError
|
290
|
+
rescue
|
291
|
+
$stderr.puts $!
|
292
|
+
exit 1
|
293
|
+
end
|
294
|
+
|
295
|
+
else
|
296
|
+
$stderr.puts "Unknown format '#{format}'"
|
297
|
+
exit 1
|
298
|
+
end
|
299
|
+
|