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,174 @@
|
|
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 SyslogInput < Input
|
22
|
+
Plugin.register_input('syslog', self)
|
23
|
+
|
24
|
+
SYSLOG_REGEXP = /^\<([0-9]+)\>(.*)/
|
25
|
+
SYSLOG_ALL_REGEXP = /^\<(?<pri>[0-9]+)\>(?<time>[^ ]* [^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?[^\:]*\: *(?<message>.*)$/
|
26
|
+
TIME_FORMAT = "%b %d %H:%M:%S"
|
27
|
+
|
28
|
+
FACILITY_MAP = {
|
29
|
+
0 => 'kern',
|
30
|
+
1 => 'user',
|
31
|
+
2 => 'mail',
|
32
|
+
3 => 'daemon',
|
33
|
+
4 => 'auth',
|
34
|
+
5 => 'syslog',
|
35
|
+
6 => 'lpr',
|
36
|
+
7 => 'news',
|
37
|
+
8 => 'uucp',
|
38
|
+
9 => 'cron',
|
39
|
+
10 => 'authpriv',
|
40
|
+
11 => 'ftp',
|
41
|
+
12 => 'ntp',
|
42
|
+
13 => 'audit',
|
43
|
+
14 => 'alert',
|
44
|
+
15 => 'at',
|
45
|
+
16 => 'local0',
|
46
|
+
17 => 'local1',
|
47
|
+
18 => 'local2',
|
48
|
+
19 => 'local3',
|
49
|
+
20 => 'local4',
|
50
|
+
21 => 'local5',
|
51
|
+
22 => 'local6',
|
52
|
+
23 => 'local7'
|
53
|
+
}
|
54
|
+
|
55
|
+
PRIORITY_MAP = {
|
56
|
+
0 => 'emerg',
|
57
|
+
1 => 'alert',
|
58
|
+
2 => 'crit',
|
59
|
+
3 => 'err',
|
60
|
+
4 => 'warn',
|
61
|
+
5 => 'notice',
|
62
|
+
6 => 'info',
|
63
|
+
7 => 'debug'
|
64
|
+
}
|
65
|
+
|
66
|
+
def initialize
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
config_param :port, :integer, :default => 5140
|
71
|
+
config_param :bind, :string, :default => '0.0.0.0'
|
72
|
+
config_param :tag, :string
|
73
|
+
|
74
|
+
def configure(conf)
|
75
|
+
super
|
76
|
+
|
77
|
+
parser = TextParser.new
|
78
|
+
if parser.configure(conf, false)
|
79
|
+
parser.use_template('syslog')
|
80
|
+
@parser = parser
|
81
|
+
else
|
82
|
+
@parser = nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def start
|
87
|
+
if @parser
|
88
|
+
callback = method(:receive_data_parser)
|
89
|
+
else
|
90
|
+
callback = method(:receive_data)
|
91
|
+
end
|
92
|
+
EventMachine.open_datagram_socket(@bind, @port, UdpHandler, callback)
|
93
|
+
end
|
94
|
+
|
95
|
+
def receive_data_parser(data)
|
96
|
+
m = SYSLOG_REGEXP.match(data)
|
97
|
+
unless m
|
98
|
+
$log.debug "invalid syslog message: #{data.dump}"
|
99
|
+
return
|
100
|
+
end
|
101
|
+
pri = m[1].to_i
|
102
|
+
text = m[2]
|
103
|
+
|
104
|
+
time, record = @parser.parse(text)
|
105
|
+
unless time && record
|
106
|
+
return
|
107
|
+
end
|
108
|
+
|
109
|
+
emit(pri, time, record)
|
110
|
+
|
111
|
+
rescue
|
112
|
+
$log.warn data.dump, :error=>$!.to_s
|
113
|
+
$log.debug_backtrace
|
114
|
+
end
|
115
|
+
|
116
|
+
def receive_data(data)
|
117
|
+
m = SYSLOG_ALL_REGEXP.match(data)
|
118
|
+
unless m
|
119
|
+
$log.debug "invalid syslog message", :data=>data
|
120
|
+
return
|
121
|
+
end
|
122
|
+
|
123
|
+
pri = nil
|
124
|
+
time = nil
|
125
|
+
record = {}
|
126
|
+
|
127
|
+
m.names.each {|name|
|
128
|
+
if value = m[name]
|
129
|
+
case name
|
130
|
+
when "pri"
|
131
|
+
pri = value.to_i
|
132
|
+
when "time"
|
133
|
+
time = Time.strptime(value, TIME_FORMAT).to_i
|
134
|
+
else
|
135
|
+
record[name] = value
|
136
|
+
end
|
137
|
+
end
|
138
|
+
}
|
139
|
+
|
140
|
+
time ||= Engine.now
|
141
|
+
|
142
|
+
emit(pri, time, record)
|
143
|
+
|
144
|
+
rescue
|
145
|
+
$log.warn data.dump, :error=>$!.to_s
|
146
|
+
$log.debug_backtrace
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
def emit(pri, time, record)
|
151
|
+
facility = FACILITY_MAP[pri >> 3]
|
152
|
+
priority = PRIORITY_MAP[pri & 0b111]
|
153
|
+
|
154
|
+
tag = "#{@tag}.#{facility}.#{priority}"
|
155
|
+
|
156
|
+
Engine.emit(tag, time, record)
|
157
|
+
end
|
158
|
+
|
159
|
+
class UdpHandler < EventMachine::Connection
|
160
|
+
def initialize(callback)
|
161
|
+
super()
|
162
|
+
@callback = callback
|
163
|
+
end
|
164
|
+
|
165
|
+
def receive_data(data)
|
166
|
+
EventMachine.defer {
|
167
|
+
@callback.call(data)
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
end
|
@@ -0,0 +1,150 @@
|
|
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 TailInput < Input
|
22
|
+
Plugin.register_input('tail', self)
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
super
|
26
|
+
@paths = []
|
27
|
+
end
|
28
|
+
|
29
|
+
config_param :path, :string
|
30
|
+
config_param :tag, :string
|
31
|
+
|
32
|
+
def configure(conf)
|
33
|
+
super
|
34
|
+
|
35
|
+
@paths = @path.split(',').map {|path| path.strip }
|
36
|
+
if @paths.empty?
|
37
|
+
raise ConfigError, "tail: 'path' parameter is required on tail input"
|
38
|
+
end
|
39
|
+
|
40
|
+
configure_parser(conf)
|
41
|
+
end
|
42
|
+
|
43
|
+
def configure_parser(conf)
|
44
|
+
@parser = TextParser.new
|
45
|
+
@parser.configure(conf)
|
46
|
+
end
|
47
|
+
|
48
|
+
def start
|
49
|
+
@loop = Coolio::Loop.new
|
50
|
+
@paths.each {|path|
|
51
|
+
$log.debug "following tail of #{path}"
|
52
|
+
@loop.attach Handler.new(path, method(:receive_lines))
|
53
|
+
}
|
54
|
+
@thread = Thread.new(&method(:run))
|
55
|
+
end
|
56
|
+
|
57
|
+
def shutdown
|
58
|
+
@loop.stop
|
59
|
+
@thread.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def run
|
63
|
+
@loop.run
|
64
|
+
rescue
|
65
|
+
$log.error "unexpected error", :error=>$!.to_s
|
66
|
+
$log.error_backtrace
|
67
|
+
end
|
68
|
+
|
69
|
+
def receive_lines(lines)
|
70
|
+
es = MultiEventStream.new
|
71
|
+
lines.each {|line|
|
72
|
+
begin
|
73
|
+
line.rstrip! # remove \n
|
74
|
+
time, record = parse_line(line)
|
75
|
+
if time && record
|
76
|
+
es.add(time, record)
|
77
|
+
end
|
78
|
+
rescue
|
79
|
+
$log.warn line.dump, :error=>$!.to_s
|
80
|
+
$log.debug_backtrace
|
81
|
+
end
|
82
|
+
}
|
83
|
+
|
84
|
+
unless es.empty?
|
85
|
+
Engine.emit_stream(@tag, es)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_line(line)
|
90
|
+
return @parser.parse(line)
|
91
|
+
end
|
92
|
+
|
93
|
+
# seek to the end of file first.
|
94
|
+
# logs never duplicate but may be lost if fluent is down.
|
95
|
+
class Handler < Coolio::StatWatcher
|
96
|
+
def initialize(path, callback)
|
97
|
+
@pos = File.stat(path).size
|
98
|
+
@buffer = ''
|
99
|
+
@callback = callback
|
100
|
+
super(path)
|
101
|
+
end
|
102
|
+
|
103
|
+
def on_change
|
104
|
+
lines = []
|
105
|
+
|
106
|
+
File.open(path) {|f|
|
107
|
+
if f.lstat.size < @pos
|
108
|
+
# moved or deleted
|
109
|
+
@pos = 0
|
110
|
+
else
|
111
|
+
f.seek(@pos)
|
112
|
+
end
|
113
|
+
|
114
|
+
line = f.gets
|
115
|
+
unless line
|
116
|
+
return
|
117
|
+
end
|
118
|
+
|
119
|
+
@buffer << line
|
120
|
+
unless line[line.length-1] == ?\n
|
121
|
+
@pos = f.pos
|
122
|
+
return
|
123
|
+
end
|
124
|
+
|
125
|
+
lines << @buffer
|
126
|
+
@buffer = ''
|
127
|
+
|
128
|
+
while line = f.gets
|
129
|
+
unless line[line.length-1] == ?\n
|
130
|
+
@buffer = line
|
131
|
+
break
|
132
|
+
end
|
133
|
+
lines << line
|
134
|
+
end
|
135
|
+
|
136
|
+
@pos = f.pos
|
137
|
+
}
|
138
|
+
|
139
|
+
@callback.call(lines)
|
140
|
+
|
141
|
+
rescue Errno::ENOENT
|
142
|
+
# moved or deleted
|
143
|
+
@pos = 0
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
end
|
150
|
+
|
@@ -0,0 +1,72 @@
|
|
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 CopyOutput < MultiOutput
|
22
|
+
Plugin.register_output('copy', self)
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@outputs = []
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :outputs
|
29
|
+
|
30
|
+
def configure(conf)
|
31
|
+
conf.elements.select {|e|
|
32
|
+
e.name == 'store'
|
33
|
+
}.each {|e|
|
34
|
+
type = e['type']
|
35
|
+
unless type
|
36
|
+
raise ConfigError, "Missing 'type' parameter on <store> directive"
|
37
|
+
end
|
38
|
+
$log.debug "adding store type=#{type.dump}"
|
39
|
+
|
40
|
+
output = Plugin.new_output(type)
|
41
|
+
output.configure(e)
|
42
|
+
@outputs << output
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
def start
|
47
|
+
@outputs.each {|o|
|
48
|
+
o.start
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def shutdown
|
53
|
+
@outputs.each {|o|
|
54
|
+
o.shutdown
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def emit(tag, es, chain)
|
59
|
+
unless es.repeatable?
|
60
|
+
es = MultiEventStream.new
|
61
|
+
array = es.map {|time,record|
|
62
|
+
es.add(time, record)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
chain = OutputChain.new(@outputs, tag, es, chain)
|
66
|
+
chain.next
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,111 @@
|
|
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 FileOutput < TimeSlicedOutput
|
22
|
+
Plugin.register_output('file', self)
|
23
|
+
|
24
|
+
SUPPORTED_COMPRESS = {
|
25
|
+
:gz => :gz,
|
26
|
+
:gzip => :gz,
|
27
|
+
}
|
28
|
+
|
29
|
+
config_param :path, :string
|
30
|
+
|
31
|
+
config_param :time_format, :string, :default => nil
|
32
|
+
|
33
|
+
config_param :compress, :default => nil do |val|
|
34
|
+
c = SUPPORTED_COMPRESS[val.to_sym]
|
35
|
+
unless c
|
36
|
+
raise ConfigError, "Unsupported compression algorithm '#{compress}'"
|
37
|
+
end
|
38
|
+
c
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
require 'zlib'
|
43
|
+
require 'time'
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def configure(conf)
|
48
|
+
if path = conf['path']
|
49
|
+
@path = path
|
50
|
+
end
|
51
|
+
unless @path
|
52
|
+
raise ConfigError, "'path' parameter is required on file output"
|
53
|
+
end
|
54
|
+
|
55
|
+
if pos = @path.index('*')
|
56
|
+
@path_prefix = @path[0,pos]
|
57
|
+
@path_suffix = @path[pos+1..-1]
|
58
|
+
conf['buffer_path'] ||= "#{@path}"
|
59
|
+
else
|
60
|
+
@path_prefix = @path+"."
|
61
|
+
@path_suffix = ".log"
|
62
|
+
conf['buffer_path'] ||= "#{@path}.*"
|
63
|
+
end
|
64
|
+
|
65
|
+
super
|
66
|
+
|
67
|
+
@timef = TimeFormatter.new(@time_format, @localtime)
|
68
|
+
end
|
69
|
+
|
70
|
+
def format(tag, time, record)
|
71
|
+
time_str = @timef.format(time)
|
72
|
+
"#{time_str}\t#{tag}\t#{record.to_json}\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
def write(chunk)
|
76
|
+
case @compress
|
77
|
+
when nil
|
78
|
+
suffix = ''
|
79
|
+
when :gz
|
80
|
+
suffix = ".gz"
|
81
|
+
end
|
82
|
+
|
83
|
+
i = 0
|
84
|
+
begin
|
85
|
+
path = "#{@path_prefix}#{chunk.key}_#{i}#{@path_suffix}#{suffix}"
|
86
|
+
i += 1
|
87
|
+
end while File.exist?(path)
|
88
|
+
FileUtils.mkdir_p File.dirname(path)
|
89
|
+
|
90
|
+
case @compress
|
91
|
+
when nil
|
92
|
+
File.open(path, "a") {|f|
|
93
|
+
chunk.write_to(f)
|
94
|
+
}
|
95
|
+
when :gz
|
96
|
+
Zlib::GzipWriter.open(path) {|f|
|
97
|
+
chunk.write_to(f)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
return path # for test
|
102
|
+
end
|
103
|
+
|
104
|
+
def secondary_init(primary)
|
105
|
+
# don't warn even if primary.class is not FileOutput
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
end
|
111
|
+
|