fluentd 0.10.4 → 0.10.5
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/ChangeLog +7 -0
- data/VERSION +1 -1
- data/fluent.conf +3 -3
- data/lib/fluent/plugin/in_exec.rb +132 -0
- data/lib/fluent/plugin/in_forward.rb +199 -0
- data/lib/fluent/plugin/in_stream.rb +25 -10
- data/lib/fluent/plugin/out_exec.rb +91 -0
- data/lib/fluent/plugin/out_exec_filter.rb +143 -0
- data/lib/fluent/plugin/out_forward.rb +356 -0
- data/lib/fluent/plugin/out_stream.rb +7 -0
- data/lib/fluent/plugin/out_test.rb +15 -5
- data/lib/fluent/test/input_test.rb +16 -6
- data/lib/fluent/version.rb +1 -1
- data/test/plugin/in_exec.rb +44 -0
- data/test/plugin/in_forward.rb +123 -0
- data/test/plugin/out_exec.rb +69 -0
- data/test/plugin/out_exec_filter.rb +74 -0
- data/test/plugin/out_forward.rb +35 -0
- metadata +29 -17
@@ -0,0 +1,143 @@
|
|
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 ExecFilterOutput < Output
|
22
|
+
Plugin.register_output('exec_filter', self)
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
config_param :command, :string
|
29
|
+
config_param :in_keys, :string
|
30
|
+
config_param :out_keys, :string
|
31
|
+
config_param :tag, :string, :default => nil
|
32
|
+
config_param :tag_key, :string, :default => nil
|
33
|
+
config_param :time_key, :string, :default => nil
|
34
|
+
config_param :time_format, :string, :default => nil
|
35
|
+
config_param :localtime, :bool, :default => true
|
36
|
+
|
37
|
+
def configure(conf)
|
38
|
+
super
|
39
|
+
|
40
|
+
if localtime = conf['localtime']
|
41
|
+
@localtime = true
|
42
|
+
elsif utc = conf['utc']
|
43
|
+
@localtime = false
|
44
|
+
end
|
45
|
+
|
46
|
+
if !@tag && !@tag_key
|
47
|
+
raise ConfigError, "'tag' or 'tag_key' option is required on exec_filter output"
|
48
|
+
end
|
49
|
+
|
50
|
+
@in_keys = @in_keys.split(',')
|
51
|
+
@out_keys = @out_keys.split(',')
|
52
|
+
|
53
|
+
if @time_key
|
54
|
+
if @time_format
|
55
|
+
f = @time_format
|
56
|
+
tf = TimeFormatter.new(f, @localtime)
|
57
|
+
@time_format_proc = tf.method(:format)
|
58
|
+
@time_parse_proc = Proc.new {|str| Time.strptime(str, f).to_i }
|
59
|
+
else
|
60
|
+
@time_format_proc = Proc.new {|time| time.to_s }
|
61
|
+
@time_parse_proc = Proc.new {|str| str.to_i }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def start
|
67
|
+
@io = IO.popen(@command, "r+")
|
68
|
+
@pid = @io.pid
|
69
|
+
@io.sync = true
|
70
|
+
@thread = Thread.new(&method(:run))
|
71
|
+
end
|
72
|
+
|
73
|
+
def shutdown
|
74
|
+
Process.kill(:TERM, @pid)
|
75
|
+
if @thread.join(60) # TODO wait time
|
76
|
+
return
|
77
|
+
end
|
78
|
+
Process.kill(:KILL, @pid)
|
79
|
+
@thread.join
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def emit(tag, es, chain)
|
84
|
+
out = ''
|
85
|
+
es.each {|time,record|
|
86
|
+
last = @in_keys.length-1
|
87
|
+
for i in 0..last
|
88
|
+
key = @in_keys[i]
|
89
|
+
if key == @time_key
|
90
|
+
out << @time_format_proc.call(time)
|
91
|
+
elsif key == @tag_key
|
92
|
+
out << tag
|
93
|
+
else
|
94
|
+
out << record[key].to_s
|
95
|
+
end
|
96
|
+
out << "\t" if i != last
|
97
|
+
end
|
98
|
+
out << "\n"
|
99
|
+
}
|
100
|
+
@io.write out
|
101
|
+
chain.next
|
102
|
+
end
|
103
|
+
|
104
|
+
def run
|
105
|
+
@io.each_line {|line|
|
106
|
+
begin
|
107
|
+
line.chomp!
|
108
|
+
vals = line.split("\t")
|
109
|
+
|
110
|
+
tag = @tag
|
111
|
+
time = nil
|
112
|
+
record = {}
|
113
|
+
for i in 0..@out_keys.length-1
|
114
|
+
key = @out_keys[i]
|
115
|
+
val = vals[i]
|
116
|
+
if key == @time_key
|
117
|
+
time = @time_parse_proc.call(val)
|
118
|
+
elsif key == @tag_key
|
119
|
+
tag = val
|
120
|
+
else
|
121
|
+
record[key] = val
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if tag
|
126
|
+
time ||= Engine.now
|
127
|
+
Engine.emit(tag, time, record)
|
128
|
+
end
|
129
|
+
rescue
|
130
|
+
$log.error "exec_filter failed to emit", :error=>$!, :line=>line
|
131
|
+
$log.warn_backtrace $!.backtrace
|
132
|
+
end
|
133
|
+
}
|
134
|
+
Process.waitpid(@pid)
|
135
|
+
rescue
|
136
|
+
$log.error "exec_filter process exited", :error=>$!
|
137
|
+
$log.warn_backtrace $!.backtrace
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
end
|
143
|
+
|
@@ -0,0 +1,356 @@
|
|
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 ForwardOutput < BufferedOutput
|
22
|
+
Plugin.register_output('forward', self)
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
require 'socket'
|
27
|
+
require 'fileutils'
|
28
|
+
@nodes = {} #=> {sockaddr => Node}
|
29
|
+
end
|
30
|
+
|
31
|
+
config_param :send_timeout, :time, :default => 60
|
32
|
+
config_param :heartbeat_interval, :time, :default => 1
|
33
|
+
config_param :recover_wait, :time, :default => 10
|
34
|
+
config_param :hard_timeout, :time, :default => nil
|
35
|
+
config_param :phi_threshold, :integer, :default => 8
|
36
|
+
attr_reader :nodes
|
37
|
+
|
38
|
+
# backward compatibility
|
39
|
+
config_param :port, :integer, :default => DEFAULT_LISTEN_PORT
|
40
|
+
config_param :host, :string, :default => nil
|
41
|
+
|
42
|
+
def configure(conf)
|
43
|
+
super
|
44
|
+
|
45
|
+
# backward compatibility
|
46
|
+
if host = conf['host']
|
47
|
+
$log.warn "'host' option in forward output is obsoleted. Use '<server> host xxx </server>' instead."
|
48
|
+
port = conf['port']
|
49
|
+
port = port ? port.to_i : DEFAULT_LISTEN_PORT
|
50
|
+
e = conf.add_element('server')
|
51
|
+
e['host'] = host
|
52
|
+
e['port'] = port.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
@hard_timeout ||= @send_timeout
|
56
|
+
|
57
|
+
recover_sample_size = @recover_wait / @heartbeat_interval
|
58
|
+
|
59
|
+
conf.elements.each {|e|
|
60
|
+
next if e.name != "server"
|
61
|
+
|
62
|
+
host = e['host']
|
63
|
+
port = e['port']
|
64
|
+
port = port ? port.to_i : DEFAULT_LISTEN_PORT
|
65
|
+
|
66
|
+
weight = e['weight']
|
67
|
+
weight = weight ? weight.to_i : 60
|
68
|
+
|
69
|
+
name = e['name']
|
70
|
+
unless name
|
71
|
+
name = "#{host}:#{port}"
|
72
|
+
end
|
73
|
+
|
74
|
+
failure = FailureDetector.new(@heartbeat_interval.to_f/2, @hard_timeout)
|
75
|
+
sockaddr = Socket.pack_sockaddr_in(port, host)
|
76
|
+
port, host = Socket.unpack_sockaddr_in(sockaddr)
|
77
|
+
@nodes[sockaddr] = Node.new(name, host, port, weight, failure,
|
78
|
+
@phi_threshold, recover_sample_size)
|
79
|
+
$log.info "adding forwarding server '#{name}'", :host=>host, :port=>port, :weight=>weight
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def start
|
84
|
+
super
|
85
|
+
|
86
|
+
@weight_array = []
|
87
|
+
gcd = @nodes.values.map {|n| n.weight }.inject(0) {|r,w| r.gcd(w) }
|
88
|
+
@nodes.each_value {|n|
|
89
|
+
(n.weight / gcd).times {
|
90
|
+
@weight_array << n
|
91
|
+
}
|
92
|
+
}
|
93
|
+
@weight_array.sort_by { rand }
|
94
|
+
|
95
|
+
@rr = 0
|
96
|
+
|
97
|
+
@loop = Coolio::Loop.new
|
98
|
+
|
99
|
+
@usock = UDPSocket.new
|
100
|
+
@hb = HeartbeatHandler.new(@usock, method(:on_heartbeat))
|
101
|
+
@loop.attach(@hb)
|
102
|
+
|
103
|
+
@timer = HeartbeatRequestTimer.new(@heartbeat_interval, method(:on_timer))
|
104
|
+
@loop.attach(@timer)
|
105
|
+
|
106
|
+
@thread = Thread.new(&method(:run))
|
107
|
+
end
|
108
|
+
|
109
|
+
def shutdown
|
110
|
+
@finished = true
|
111
|
+
@usock.close
|
112
|
+
@loop.stop
|
113
|
+
@thread.join
|
114
|
+
end
|
115
|
+
|
116
|
+
def run
|
117
|
+
@loop.run
|
118
|
+
rescue
|
119
|
+
$log.error "unexpected error", :error=>$!.to_s
|
120
|
+
$log.error_backtrace
|
121
|
+
end
|
122
|
+
|
123
|
+
# override BufferedOutput#emit
|
124
|
+
def emit(tag, es, chain)
|
125
|
+
data = es.to_msgpack_stream
|
126
|
+
if @buffer.emit(tag, data, chain) # use key = tag
|
127
|
+
submit_flush
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def write(chunk)
|
132
|
+
wlen = @weight_array.length
|
133
|
+
wlen.times do
|
134
|
+
node = @weight_array[@rr]
|
135
|
+
@rr = (@rr + 1) % wlen
|
136
|
+
|
137
|
+
if node.available?
|
138
|
+
send_data(node, chunk)
|
139
|
+
return
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
raise "no nodes are available" # TODO message
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
# MessagePack FixArray length = 2
|
148
|
+
FORWARD_HEADER = [0x92].pack('C')
|
149
|
+
|
150
|
+
def send_data(node, chunk)
|
151
|
+
sock = connect(node)
|
152
|
+
begin
|
153
|
+
opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
154
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
155
|
+
|
156
|
+
opt = [@send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
157
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
158
|
+
|
159
|
+
# beginArray(2)
|
160
|
+
sock.write FORWARD_HEADER
|
161
|
+
|
162
|
+
# writeRaw(tag)
|
163
|
+
sock.write chunk.key.to_msgpack # tag
|
164
|
+
|
165
|
+
# beginRaw(size)
|
166
|
+
sz = chunk.size
|
167
|
+
#if sz < 32
|
168
|
+
# # FixRaw
|
169
|
+
# sock.write [0xa0 | sz].pack('C')
|
170
|
+
#elsif sz < 65536
|
171
|
+
# # raw 16
|
172
|
+
# sock.write [0xda, sz].pack('Cn')
|
173
|
+
#else
|
174
|
+
# raw 32
|
175
|
+
sock.write [0xdb, sz].pack('CN')
|
176
|
+
#end
|
177
|
+
|
178
|
+
# writeRawBody(packed_es)
|
179
|
+
chunk.write_to(sock)
|
180
|
+
ensure
|
181
|
+
sock.close
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def connect(node)
|
186
|
+
# TODO unix socket?
|
187
|
+
TCPSocket.new(node.host, node.port)
|
188
|
+
end
|
189
|
+
|
190
|
+
class HeartbeatRequestTimer < Coolio::TimerWatcher
|
191
|
+
def initialize(interval, callback)
|
192
|
+
super(interval, true)
|
193
|
+
@callback = callback
|
194
|
+
end
|
195
|
+
|
196
|
+
def on_timer
|
197
|
+
@callback.call
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def on_timer
|
202
|
+
return if @finished
|
203
|
+
@nodes.each_pair {|sockaddr,n|
|
204
|
+
n.tick
|
205
|
+
@usock.send "", 0, sockaddr
|
206
|
+
}
|
207
|
+
end
|
208
|
+
|
209
|
+
class HeartbeatHandler < Coolio::IO
|
210
|
+
def initialize(io, callback)
|
211
|
+
super(io)
|
212
|
+
@io = io
|
213
|
+
@callback = callback
|
214
|
+
end
|
215
|
+
|
216
|
+
def on_readable
|
217
|
+
msg, addr = @io.recvfrom(1024)
|
218
|
+
host = addr[3]
|
219
|
+
port = addr[1]
|
220
|
+
sockaddr = Socket.pack_sockaddr_in(port, host)
|
221
|
+
@callback.call(sockaddr, msg)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def on_heartbeat(sockaddr, msg)
|
226
|
+
if node = @nodes[sockaddr]
|
227
|
+
$log.trace "heartbeat from '#{node.name}'", :host=>node.host, :port=>node.port
|
228
|
+
node.heartbeat
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class Node
|
233
|
+
def initialize(name, host, port, weight, failure,
|
234
|
+
phi_threshold, recover_sample_size)
|
235
|
+
@name = name
|
236
|
+
@host = host
|
237
|
+
@port = port
|
238
|
+
@weight = weight
|
239
|
+
@failure = failure
|
240
|
+
@phi_threshold = phi_threshold
|
241
|
+
@recover_sample_size = recover_sample_size
|
242
|
+
@available = true
|
243
|
+
end
|
244
|
+
|
245
|
+
attr_reader :name, :host, :port
|
246
|
+
attr_reader :weight
|
247
|
+
attr_writer :available
|
248
|
+
|
249
|
+
def available?
|
250
|
+
@available
|
251
|
+
end
|
252
|
+
|
253
|
+
def tick
|
254
|
+
now = Time.now.to_f
|
255
|
+
phi = @failure.phi(now)
|
256
|
+
$log.trace "phi '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
|
257
|
+
if phi > @phi_threshold
|
258
|
+
$log.info "detached forwarding server '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
|
259
|
+
@available = false
|
260
|
+
@failure.clear
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def heartbeat
|
265
|
+
now = Time.now.to_f
|
266
|
+
@failure.add(now)
|
267
|
+
if !@available && @failure.sample_size > @recover_sample_size &&
|
268
|
+
@failure.phi(now) <= @phi_threshold
|
269
|
+
$log.info "recovered forwarding server '#{@name}'", :host=>@host, :port=>@port
|
270
|
+
@available = true
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def to_msgpack(out = '')
|
275
|
+
[@host, @port, @weight, @available].to_msgpack(out)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
class FailureDetector
|
280
|
+
PHI_FACTOR = 1.0 / Math.log(10.0)
|
281
|
+
SAMPLE_SIZE = 1000
|
282
|
+
|
283
|
+
def initialize(init_int, hard_timeout)
|
284
|
+
@last = 0
|
285
|
+
@init_int = init_int
|
286
|
+
@hard_timeout = hard_timeout
|
287
|
+
@window = []
|
288
|
+
@failure = false
|
289
|
+
end
|
290
|
+
|
291
|
+
def add(now)
|
292
|
+
if @last > 0
|
293
|
+
int = now - @last
|
294
|
+
else
|
295
|
+
int = @init_int
|
296
|
+
end
|
297
|
+
if int <= @hard_timeout
|
298
|
+
@window << int
|
299
|
+
@window.shift if @window.length > SAMPLE_SIZE
|
300
|
+
@last = now
|
301
|
+
true
|
302
|
+
else
|
303
|
+
false
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def phi(now)
|
308
|
+
size = @window.size
|
309
|
+
return 0.0 if size == 0
|
310
|
+
t = now - @last
|
311
|
+
mean = @window.inject(0) {|r,v| r + v } / size
|
312
|
+
return PHI_FACTOR * t / mean
|
313
|
+
end
|
314
|
+
|
315
|
+
def sample_size
|
316
|
+
@window.size
|
317
|
+
end
|
318
|
+
|
319
|
+
def clear
|
320
|
+
@window.clear
|
321
|
+
@last = 0
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
## TODO
|
326
|
+
#class RPC
|
327
|
+
# def initialize(this)
|
328
|
+
# @this = this
|
329
|
+
# end
|
330
|
+
#
|
331
|
+
# def list_nodes
|
332
|
+
# @this.nodes
|
333
|
+
# end
|
334
|
+
#
|
335
|
+
# def list_fault_nodes
|
336
|
+
# list_nodes.select {|n| !n.available? }
|
337
|
+
# end
|
338
|
+
#
|
339
|
+
# def list_available_nodes
|
340
|
+
# list_nodes.select {|n| n.available? }
|
341
|
+
# end
|
342
|
+
#
|
343
|
+
# def add_node(name, host, port, weight)
|
344
|
+
# end
|
345
|
+
#
|
346
|
+
# def recover_node(host, port)
|
347
|
+
# end
|
348
|
+
#
|
349
|
+
# def remove_node(host, port)
|
350
|
+
# end
|
351
|
+
#end
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
end
|
356
|
+
|