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,84 @@
|
|
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 ZFileBufferChunk < FileBufferChunk
|
22
|
+
def initialize(path)
|
23
|
+
super(path)
|
24
|
+
@z = Zlib::Deflate.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(data)
|
28
|
+
zdata = @z.deflate(data, Z_NO_FLUSH)
|
29
|
+
unless zdata.empty?
|
30
|
+
super(zdata)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def close_write
|
35
|
+
zdata = @z.flush
|
36
|
+
unless zdata.empty?
|
37
|
+
@file.write(zdata)
|
38
|
+
@size += zdata.bytesize
|
39
|
+
end
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
@z.close
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def purge
|
49
|
+
@z.close
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
# TODO
|
54
|
+
#def open(&block)
|
55
|
+
#end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
class ZFileBuffer < FileBuffer
|
60
|
+
# TODO
|
61
|
+
#Plugin.register_buffer('zfile', self)
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
require 'zlib'
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def resume_queue
|
69
|
+
queue = resume_queue_paths.map {|path|
|
70
|
+
ZFileBufferChunk.new(path)
|
71
|
+
}
|
72
|
+
top = new_chunk
|
73
|
+
return queue, top
|
74
|
+
end
|
75
|
+
|
76
|
+
def new_chunk
|
77
|
+
path = new_chunk_path
|
78
|
+
ZFileBufferChunk.new(path)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,282 @@
|
|
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 HttpInput < Input
|
22
|
+
Plugin.register_input('http', self)
|
23
|
+
|
24
|
+
require 'http/parser'
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
require 'webrick/httputils'
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
config_param :port, :integer, :default => 9880
|
32
|
+
config_param :bind, :string, :default => '0.0.0.0'
|
33
|
+
config_param :body_size_limit, :size, :default => 32*1024*1024 # TODO default
|
34
|
+
config_param :keepalive_timeout, :time, :default => 10 # TODO default
|
35
|
+
|
36
|
+
def configure(conf)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
class KeepaliveManager < Coolio::TimerWatcher
|
41
|
+
class TimerValue
|
42
|
+
def initialize
|
43
|
+
@value = 0
|
44
|
+
end
|
45
|
+
attr_accessor :value
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(timeout)
|
49
|
+
super(1, true)
|
50
|
+
@cons = {}
|
51
|
+
@timeout = timeout.to_i
|
52
|
+
end
|
53
|
+
|
54
|
+
def add(sock)
|
55
|
+
@cons[sock] = sock
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete(sock)
|
59
|
+
@cons.delete(sock)
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_timer
|
63
|
+
@cons.each_pair {|sock,val|
|
64
|
+
if sock.step_idle > @timeout
|
65
|
+
sock.close
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# TODO multithreading
|
72
|
+
def start
|
73
|
+
$log.debug "listening http on #{@bind}:#{@port}"
|
74
|
+
|
75
|
+
@km = KeepaliveManager.new(@keepalive_timeout)
|
76
|
+
@lsock = Coolio::TCPServer.new(@bind, @port, Handler, @km, method(:on_request), @body_size_limit)
|
77
|
+
|
78
|
+
@loop = Coolio::Loop.new
|
79
|
+
@loop.attach(@km)
|
80
|
+
@loop.attach(@lsock)
|
81
|
+
|
82
|
+
@thread = Thread.new(&method(:run))
|
83
|
+
end
|
84
|
+
|
85
|
+
def shutdown
|
86
|
+
@lsock.close
|
87
|
+
@loop.stop
|
88
|
+
@thread.join
|
89
|
+
end
|
90
|
+
|
91
|
+
def run
|
92
|
+
@loop.run
|
93
|
+
rescue
|
94
|
+
$log.error "unexpected error", :error=>$!.to_s
|
95
|
+
$log.error_backtrace
|
96
|
+
end
|
97
|
+
|
98
|
+
def on_request(path_info, params)
|
99
|
+
begin
|
100
|
+
path = path_info[1..-1] # remove /
|
101
|
+
tag = path.split('/').join('.')
|
102
|
+
|
103
|
+
if msgpack = params['msgpack']
|
104
|
+
record = MessagePack.unpack(msgpack)
|
105
|
+
|
106
|
+
elsif js = params['json']
|
107
|
+
record = JSON.parse(js)
|
108
|
+
|
109
|
+
else
|
110
|
+
raise "'json' or 'msgpack' parameter is required"
|
111
|
+
end
|
112
|
+
|
113
|
+
time = params['time']
|
114
|
+
time = time.to_i
|
115
|
+
if time == 0
|
116
|
+
time = Engine.now
|
117
|
+
end
|
118
|
+
|
119
|
+
rescue
|
120
|
+
return ["400 Bad Request", {'Content-type'=>'text/plain'}, "400 Bad Request\n#{$!}\n"]
|
121
|
+
end
|
122
|
+
|
123
|
+
# TODO server error
|
124
|
+
begin
|
125
|
+
Engine.emit(tag, time, record)
|
126
|
+
rescue
|
127
|
+
return ["500 Internal Server Error", {'Content-type'=>'text/plain'}, "500 Internal Server Error\n#{$!}\n"]
|
128
|
+
end
|
129
|
+
|
130
|
+
return ["200 OK", {'Content-type'=>'text/plain'}, ""]
|
131
|
+
end
|
132
|
+
|
133
|
+
class Handler < Coolio::Socket
|
134
|
+
def initialize(io, km, callback, body_size_limit)
|
135
|
+
super(io)
|
136
|
+
@km = km
|
137
|
+
@callback = callback
|
138
|
+
@body_size_limit = body_size_limit
|
139
|
+
@content_type = ""
|
140
|
+
@next_close = false
|
141
|
+
|
142
|
+
@idle = 0
|
143
|
+
@km.add(self)
|
144
|
+
end
|
145
|
+
|
146
|
+
def step_idle
|
147
|
+
@idle += 1
|
148
|
+
end
|
149
|
+
|
150
|
+
def on_close
|
151
|
+
@km.delete(self)
|
152
|
+
end
|
153
|
+
|
154
|
+
def on_connect
|
155
|
+
@parser = Http::Parser.new(self)
|
156
|
+
end
|
157
|
+
|
158
|
+
def on_read(data)
|
159
|
+
@idle = 0
|
160
|
+
@parser << data
|
161
|
+
rescue
|
162
|
+
$log.warn "unexpected error", :error=>$!.to_s
|
163
|
+
$log.warn_backtrace
|
164
|
+
close
|
165
|
+
end
|
166
|
+
|
167
|
+
def on_message_begin
|
168
|
+
@body = ''
|
169
|
+
end
|
170
|
+
|
171
|
+
def on_headers_complete(headers)
|
172
|
+
expect = nil
|
173
|
+
size = nil
|
174
|
+
if @parser.http_version == [1, 1]
|
175
|
+
@keep_alive = true
|
176
|
+
else
|
177
|
+
@keep_alive = false
|
178
|
+
end
|
179
|
+
headers.each_pair {|k,v|
|
180
|
+
case k
|
181
|
+
when /Expect/i
|
182
|
+
expect = v
|
183
|
+
when /Content-Length/i
|
184
|
+
size = v.to_i
|
185
|
+
when /Content-Type/i
|
186
|
+
@content_type = v
|
187
|
+
when /Connection/i
|
188
|
+
if v =~ /close/i
|
189
|
+
@keep_alive = false
|
190
|
+
elsif v =~ /Keep-alive/i
|
191
|
+
@keep_alive = true
|
192
|
+
end
|
193
|
+
end
|
194
|
+
}
|
195
|
+
if expect
|
196
|
+
if expect == '100-continue'
|
197
|
+
if !size || size < @body_size_limit
|
198
|
+
send_response_nobody("100 Continue", {})
|
199
|
+
else
|
200
|
+
send_response_and_close("413 Request Entity Too Large", {}, "Too large")
|
201
|
+
end
|
202
|
+
else
|
203
|
+
send_response_and_close("417 Expectation Failed", {}, "")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def on_body(chunk)
|
209
|
+
if @body.bytesize + chunk.bytesize > @body_size_limit
|
210
|
+
unless closing?
|
211
|
+
send_response_and_close("413 Request Entity Too Large", {}, "Too large")
|
212
|
+
end
|
213
|
+
return
|
214
|
+
end
|
215
|
+
@body << chunk
|
216
|
+
end
|
217
|
+
|
218
|
+
def on_message_complete
|
219
|
+
return if closing?
|
220
|
+
|
221
|
+
params = WEBrick::HTTPUtils.parse_query(@parser.query_string)
|
222
|
+
|
223
|
+
if @content_type =~ /^application\/x-www-form-urlencoded/
|
224
|
+
params.update WEBrick::HTTPUtils.parse_query(@body)
|
225
|
+
elsif @content_type =~ /^multipart\/form-data; boundary=(.+)/
|
226
|
+
boundary = WEBrick::HTTPUtils.dequote($1)
|
227
|
+
params.update WEBrick::HTTPUtils.parse_form_data(@body, boundary)
|
228
|
+
end
|
229
|
+
path_info = @parser.request_path
|
230
|
+
|
231
|
+
code, header, body = *@callback.call(path_info, params)
|
232
|
+
body = body.to_s
|
233
|
+
|
234
|
+
if @keep_alive
|
235
|
+
header['Connection'] = 'Keep-Alive'
|
236
|
+
send_response(code, header, body)
|
237
|
+
else
|
238
|
+
send_response_and_close(code, header, body)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def on_write_complete
|
243
|
+
close if @next_close
|
244
|
+
end
|
245
|
+
|
246
|
+
def send_response_and_close(code, header, body)
|
247
|
+
send_response(code, header, body)
|
248
|
+
@next_close = true
|
249
|
+
end
|
250
|
+
|
251
|
+
def closing?
|
252
|
+
@next_close
|
253
|
+
end
|
254
|
+
|
255
|
+
def send_response(code, header, body)
|
256
|
+
header['Content-length'] ||= body.bytesize
|
257
|
+
header['Content-type'] ||= 'text/plain'
|
258
|
+
|
259
|
+
data = %[HTTP/1.1 #{code}\r\n]
|
260
|
+
header.each_pair {|k,v|
|
261
|
+
data << "#{k}: #{v}\r\n"
|
262
|
+
}
|
263
|
+
data << "\r\n"
|
264
|
+
write data
|
265
|
+
|
266
|
+
write body
|
267
|
+
end
|
268
|
+
|
269
|
+
def send_response_nobody(code, header)
|
270
|
+
data = %[HTTP/1.1 #{code}\r\n]
|
271
|
+
header.each_pair {|k,v|
|
272
|
+
data << "#{k}: #{v}\r\n"
|
273
|
+
}
|
274
|
+
data << "\r\n"
|
275
|
+
write data
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
end
|
282
|
+
|
@@ -0,0 +1,187 @@
|
|
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 StreamInput < Input
|
22
|
+
def initialize
|
23
|
+
require 'socket'
|
24
|
+
require 'yajl'
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def start
|
29
|
+
@loop = Coolio::Loop.new
|
30
|
+
@lsock = listen
|
31
|
+
@loop.attach(@lsock)
|
32
|
+
@thread = Thread.new(&method(:run))
|
33
|
+
@cached_unpacker = MessagePack::Unpacker.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def shutdown
|
37
|
+
@lsock.close
|
38
|
+
@loop.stop
|
39
|
+
@thread.join
|
40
|
+
end
|
41
|
+
|
42
|
+
#def listen
|
43
|
+
#end
|
44
|
+
|
45
|
+
def run
|
46
|
+
@loop.run
|
47
|
+
rescue
|
48
|
+
$log.error "unexpected error", :error=>$!.to_s
|
49
|
+
$log.error_backtrace
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
# message Entry {
|
54
|
+
# 1: long time
|
55
|
+
# 2: object record
|
56
|
+
# }
|
57
|
+
#
|
58
|
+
# message Forward {
|
59
|
+
# 1: string tag
|
60
|
+
# 2: list<Entry> entries
|
61
|
+
# }
|
62
|
+
#
|
63
|
+
# message PackedForward {
|
64
|
+
# 1: string tag
|
65
|
+
# 2: raw entries # msgpack stream of Entry
|
66
|
+
# }
|
67
|
+
#
|
68
|
+
# message Message {
|
69
|
+
# 1: string tag
|
70
|
+
# 2: long? time
|
71
|
+
# 3: object record
|
72
|
+
# }
|
73
|
+
def on_message(msg)
|
74
|
+
# TODO format error
|
75
|
+
tag = msg[0].to_s
|
76
|
+
entries = msg[1]
|
77
|
+
|
78
|
+
if entries.class == String
|
79
|
+
# PackedForward
|
80
|
+
es = MessagePackEventStream.new(entries, @cached_unpacker)
|
81
|
+
Engine.emit_stream(tag, es)
|
82
|
+
|
83
|
+
elsif entries.class == Array
|
84
|
+
# Forward
|
85
|
+
es = MultiEventStream.new
|
86
|
+
entries.each {|e|
|
87
|
+
time = e[0].to_i
|
88
|
+
time = (now ||= Engine.now) if time == 0
|
89
|
+
record = e[1]
|
90
|
+
es.add(time, record)
|
91
|
+
}
|
92
|
+
Engine.emit_stream(tag, es)
|
93
|
+
|
94
|
+
else
|
95
|
+
# Message
|
96
|
+
time = msg[1]
|
97
|
+
time = Engine.now if time == 0
|
98
|
+
record = msg[2]
|
99
|
+
Engine.emit(tag, time, record)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Handler < Coolio::Socket
|
104
|
+
def initialize(io, on_message)
|
105
|
+
super(io)
|
106
|
+
if io.is_a?(TCPSocket)
|
107
|
+
opt = [1, @timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
108
|
+
io.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
109
|
+
end
|
110
|
+
$log.trace { "accepted fluent socket object_id=#{self.object_id}" }
|
111
|
+
@on_message = on_message
|
112
|
+
end
|
113
|
+
|
114
|
+
def on_connect
|
115
|
+
end
|
116
|
+
|
117
|
+
def on_read(data)
|
118
|
+
first = data[0]
|
119
|
+
if first == '{' || first == '['
|
120
|
+
m = method(:on_read_json)
|
121
|
+
@y = Yajl::Parser.new
|
122
|
+
@y.on_parse_complete = @on_message
|
123
|
+
else
|
124
|
+
m = method(:on_read_msgpack)
|
125
|
+
@u = MessagePack::Unpacker.new
|
126
|
+
end
|
127
|
+
|
128
|
+
(class<<self;self;end).module_eval do
|
129
|
+
define_method(:on_read, m)
|
130
|
+
end
|
131
|
+
m.call(data)
|
132
|
+
end
|
133
|
+
|
134
|
+
def on_read_json(data)
|
135
|
+
@y << data
|
136
|
+
end
|
137
|
+
|
138
|
+
def on_read_msgpack(data)
|
139
|
+
@u.feed_each(data, &@on_message)
|
140
|
+
end
|
141
|
+
|
142
|
+
def on_close
|
143
|
+
$log.trace { "closed fluent socket object_id=#{self.object_id}" }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
class TcpInput < StreamInput
|
150
|
+
Plugin.register_input('tcp', self)
|
151
|
+
|
152
|
+
config_param :port, :integer, :default => DEFAULT_LISTEN_PORT
|
153
|
+
config_param :bind, :string, :default => '0.0.0.0'
|
154
|
+
|
155
|
+
def configure(conf)
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
def listen
|
160
|
+
$log.debug "listening fluent socket on #{@bind}:#{@port}"
|
161
|
+
Coolio::TCPServer.new(@bind, @port, Handler, method(:on_message))
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
class UnixInput < StreamInput
|
167
|
+
Plugin.register_input('unix', self)
|
168
|
+
|
169
|
+
config_param :path, :string, :default => DEFAULT_SOCKET_PATH
|
170
|
+
|
171
|
+
def configure(conf)
|
172
|
+
super
|
173
|
+
end
|
174
|
+
|
175
|
+
def listen
|
176
|
+
if File.exist?(@path)
|
177
|
+
File.unlink(@path)
|
178
|
+
end
|
179
|
+
FileUtils.mkdir_p File.dirname(@path)
|
180
|
+
$log.debug "listening fluent socket on #{@path}"
|
181
|
+
Coolio::UNIXServer.new(@path, Handler, method(:on_message))
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
end
|
187
|
+
|