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,115 @@
|
|
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 TextParser
|
22
|
+
TEMPLATES = {
|
23
|
+
'apache' => [/^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/, "%d/%b/%Y:%H:%M:%S %z"],
|
24
|
+
'syslog' => [/^(?<time>[^ ]* [^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?[^\:]*\: *(?<message>.*)$/, "%b %d %H:%M:%S"],
|
25
|
+
}
|
26
|
+
|
27
|
+
def self.register_template(name, regexp, time_format=nil)
|
28
|
+
TEMPLATES[name] = [regexp, time_format]
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.get_template(name)
|
32
|
+
return *TEMPLATES[name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
require 'time' # Time.strptime, Time.parse
|
37
|
+
@regexp = nil
|
38
|
+
@time_format = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_accessor :regexp, :time_format
|
42
|
+
|
43
|
+
def use_template(name)
|
44
|
+
@regexp, @time_format = TextParser.get_template(name)
|
45
|
+
unless @regexp
|
46
|
+
raise ConfigError, "Unknown format template '#{name}'"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def configure(conf, required=true)
|
51
|
+
if format = conf['format']
|
52
|
+
if format[0] == ?/ && format[format.length-1] == ?/
|
53
|
+
# regexp
|
54
|
+
begin
|
55
|
+
@regexp = Regexp.new(format[1..-2])
|
56
|
+
if @regexp.named_captures.empty?
|
57
|
+
raise "No named captures"
|
58
|
+
end
|
59
|
+
rescue
|
60
|
+
raise ConfigError, "Invalid regexp '#{format[1..-2]}': #{$!}"
|
61
|
+
end
|
62
|
+
|
63
|
+
else
|
64
|
+
# template
|
65
|
+
use_template(format)
|
66
|
+
end
|
67
|
+
else
|
68
|
+
return nil if !required
|
69
|
+
raise ConfigError, "'format' parameter is required"
|
70
|
+
end
|
71
|
+
|
72
|
+
if time_format = conf['time_format']
|
73
|
+
unless @regexp.names.include?('time')
|
74
|
+
raise ConfigError, "'time_format' parameter is invalid when format doesn't have 'time' capture"
|
75
|
+
end
|
76
|
+
@time_format = time_format
|
77
|
+
end
|
78
|
+
|
79
|
+
return true
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse(text)
|
83
|
+
m = @regexp.match(text)
|
84
|
+
unless m
|
85
|
+
$log.debug "pattern not match: #{text}"
|
86
|
+
# TODO?
|
87
|
+
return nil, nil
|
88
|
+
end
|
89
|
+
|
90
|
+
time = nil
|
91
|
+
record = {}
|
92
|
+
|
93
|
+
m.names.each {|name|
|
94
|
+
if value = m[name]
|
95
|
+
case name
|
96
|
+
when "time"
|
97
|
+
if @time_format
|
98
|
+
time = Time.strptime(value, @time_format).to_i
|
99
|
+
else
|
100
|
+
time = Time.parse(value).to_i
|
101
|
+
end
|
102
|
+
else
|
103
|
+
record[name] = value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
}
|
107
|
+
|
108
|
+
time ||= Engine.now
|
109
|
+
|
110
|
+
return time, record
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,145 @@
|
|
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 PluginClass
|
22
|
+
def initialize
|
23
|
+
@input = {}
|
24
|
+
@output = {}
|
25
|
+
@buffer = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def register_input(type, klass)
|
29
|
+
register_impl('input', @input, type, klass)
|
30
|
+
end
|
31
|
+
|
32
|
+
def register_output(type, klass)
|
33
|
+
register_impl('output', @output, type, klass)
|
34
|
+
end
|
35
|
+
|
36
|
+
def register_buffer(type, klass)
|
37
|
+
register_impl('buffer', @buffer, type, klass)
|
38
|
+
end
|
39
|
+
|
40
|
+
def new_input(type)
|
41
|
+
new_impl('input', @input, type)
|
42
|
+
end
|
43
|
+
|
44
|
+
def new_output(type)
|
45
|
+
new_impl('output', @output, type)
|
46
|
+
end
|
47
|
+
|
48
|
+
def new_buffer(type)
|
49
|
+
new_impl('buffer', @buffer, type)
|
50
|
+
end
|
51
|
+
|
52
|
+
def load_plugins
|
53
|
+
dir = File.join(File.dirname(__FILE__), "plugin")
|
54
|
+
load_plugin_dir(dir)
|
55
|
+
load_gem_plugins
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_plugin_dir(dir)
|
59
|
+
dir = File.expand_path(dir)
|
60
|
+
Dir.entries(dir).sort.each {|fname|
|
61
|
+
if fname =~ /\.rb$/
|
62
|
+
require File.join(dir, fname)
|
63
|
+
end
|
64
|
+
}
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def load_gem_plugins
|
70
|
+
return unless defined? Gem
|
71
|
+
plugins = Gem.find_files('fluent_plugin')
|
72
|
+
|
73
|
+
plugins.each {|plugin|
|
74
|
+
begin
|
75
|
+
load plugin
|
76
|
+
rescue ::Exception => e
|
77
|
+
msg = "#{plugin.inspect}: #{e.message} (#{e.class})"
|
78
|
+
$log.warn "Error loading Fluent plugin #{msg}"
|
79
|
+
end
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def register_impl(name, map, type, klass)
|
84
|
+
map[type] = klass
|
85
|
+
$log.trace { "registered #{name} plugin '#{type}'" }
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def new_impl(name, map, type)
|
90
|
+
if klass = map[type]
|
91
|
+
return klass.new
|
92
|
+
end
|
93
|
+
try_load_plugin(name, type)
|
94
|
+
if klass = map[type]
|
95
|
+
return klass.new
|
96
|
+
end
|
97
|
+
raise ConfigError, "Unknown #{name} plugin '#{type}'. Run 'gem search fluent-plugin' to find plugins"
|
98
|
+
end
|
99
|
+
|
100
|
+
def try_load_plugin(name, type)
|
101
|
+
case name
|
102
|
+
when 'input'
|
103
|
+
path = "fluent/plugin/in_#{type}"
|
104
|
+
when 'output'
|
105
|
+
path = "fluent/plugin/out_#{type}"
|
106
|
+
when 'buffer'
|
107
|
+
path = "fluent/plugin/buf_#{type}"
|
108
|
+
else
|
109
|
+
return
|
110
|
+
end
|
111
|
+
|
112
|
+
# prefer LOAD_PATH
|
113
|
+
files = $LOAD_PATH.map {|lp|
|
114
|
+
lpath = File.join(lp, "#{path}.rb")
|
115
|
+
File.exist?(lpath) ? lpath : nil
|
116
|
+
}.compact
|
117
|
+
unless files.empty?
|
118
|
+
# prefer newer version
|
119
|
+
require files.sort.last
|
120
|
+
return
|
121
|
+
end
|
122
|
+
|
123
|
+
# search gems
|
124
|
+
if defined?(::Gem) && ::Gem.respond_to?(:searcher)
|
125
|
+
#files = Gem.find_files(path).sort
|
126
|
+
specs = Gem.searcher.find_all(path)
|
127
|
+
specs = specs.sort_by {|spec| spec.version }
|
128
|
+
|
129
|
+
# prefer newer version
|
130
|
+
specs.reverse_each {|spec|
|
131
|
+
files = Gem.searcher.matching_files(spec, path)
|
132
|
+
unless files.empty?
|
133
|
+
require files.first
|
134
|
+
return
|
135
|
+
end
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
Plugin = PluginClass.new
|
142
|
+
|
143
|
+
|
144
|
+
end
|
145
|
+
|
@@ -0,0 +1,181 @@
|
|
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 FileBufferChunk < BufferChunk
|
22
|
+
def initialize(key, path, mode="a+")
|
23
|
+
super(key)
|
24
|
+
@path = path
|
25
|
+
@file = File.open(@path, mode)
|
26
|
+
@size = @file.stat.size
|
27
|
+
end
|
28
|
+
|
29
|
+
def <<(data)
|
30
|
+
while true
|
31
|
+
n = @file.syswrite(data)
|
32
|
+
@size += n
|
33
|
+
if data.bytesize <= n
|
34
|
+
break
|
35
|
+
else
|
36
|
+
data = data[n..-1]
|
37
|
+
#data.slice!(0, n-1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
#@file.write(data)
|
41
|
+
#@size += data.bytesize
|
42
|
+
#@file.flush
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
@size
|
47
|
+
end
|
48
|
+
|
49
|
+
def empty?
|
50
|
+
@size == 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def close
|
54
|
+
@file.close
|
55
|
+
if @file.stat.size == 0
|
56
|
+
File.unlink(@path)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def purge
|
61
|
+
@file.close
|
62
|
+
File.unlink(@path) rescue nil # TODO rescue?
|
63
|
+
end
|
64
|
+
|
65
|
+
def read
|
66
|
+
@file.pos = 0
|
67
|
+
@file.read
|
68
|
+
end
|
69
|
+
|
70
|
+
def open(&block)
|
71
|
+
@file.pos = 0
|
72
|
+
yield @file
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :path
|
76
|
+
|
77
|
+
def mv(path)
|
78
|
+
File.rename(@path, path)
|
79
|
+
@path = path
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
class FileBuffer < BasicBuffer
|
85
|
+
Plugin.register_buffer('file', self)
|
86
|
+
|
87
|
+
def initialize
|
88
|
+
require 'uri'
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
config_param :buffer_path, :string
|
93
|
+
|
94
|
+
def configure(conf)
|
95
|
+
super
|
96
|
+
|
97
|
+
if pos = @buffer_path.index('*')
|
98
|
+
@buffer_path_prefix = @buffer_path[0,pos]
|
99
|
+
@buffer_path_suffix = @buffer_path[pos+1..-1]
|
100
|
+
else
|
101
|
+
@buffer_path_prefix = @buffer_path+"."
|
102
|
+
@buffer_path_suffix = ".log"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def start
|
107
|
+
FileUtils.mkdir_p File.dirname(@buffer_path_prefix+"path")
|
108
|
+
super
|
109
|
+
end
|
110
|
+
|
111
|
+
PATH_MATCH = /^(.*)[\._](b|q)([0-9a-fA-F]{1,16})$/
|
112
|
+
|
113
|
+
def new_chunk(key)
|
114
|
+
tsuffix = Engine.now.to_i.to_s(16)
|
115
|
+
encoded_key = encode_key(key)
|
116
|
+
path = "#{@buffer_path_prefix}#{encoded_key}.b#{tsuffix}#{@buffer_path_suffix}"
|
117
|
+
FileBufferChunk.new(key, path)
|
118
|
+
end
|
119
|
+
|
120
|
+
def resume
|
121
|
+
maps = []
|
122
|
+
queues = []
|
123
|
+
|
124
|
+
Dir.glob("#{@buffer_path_prefix}*#{@buffer_path_suffix}") {|path|
|
125
|
+
match = path[@buffer_path_prefix.length..-(@buffer_path_suffix.length+1)]
|
126
|
+
if m = PATH_MATCH.match(match)
|
127
|
+
key = decode_key(m[1])
|
128
|
+
bq = m[2]
|
129
|
+
tsuffix = m[3].to_i(16)
|
130
|
+
|
131
|
+
if bq == 'b'
|
132
|
+
chunk = FileBufferChunk.new(key, path, "a+")
|
133
|
+
maps << [tsuffix, chunk]
|
134
|
+
elsif bq == 'q'
|
135
|
+
chunk = FileBufferChunk.new(key, path, "r")
|
136
|
+
queues << [tsuffix, chunk]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
}
|
140
|
+
|
141
|
+
map = {}
|
142
|
+
maps.sort_by {|(tsuffix,chunk)|
|
143
|
+
tsuffix
|
144
|
+
}.each {|(tsuffix,chunk)|
|
145
|
+
map[chunk.key] = chunk
|
146
|
+
}
|
147
|
+
|
148
|
+
queue = queues.sort_by {|(tsuffix,chunk)|
|
149
|
+
tsuffix
|
150
|
+
}.map {|(tsuffix,chunk)|
|
151
|
+
chunk
|
152
|
+
}
|
153
|
+
|
154
|
+
return queue, map
|
155
|
+
end
|
156
|
+
|
157
|
+
def enqueue(chunk)
|
158
|
+
path = chunk.path
|
159
|
+
mp = path[@buffer_path_prefix.length..-(@buffer_path_suffix.length+1)]
|
160
|
+
|
161
|
+
m = PATH_MATCH.match(mp)
|
162
|
+
encoded_key = m ? m[1] : ""
|
163
|
+
tsuffix = Engine.now.to_i.to_s(16)
|
164
|
+
|
165
|
+
path = "#{@buffer_path_prefix}#{encoded_key}.q#{tsuffix}#{@buffer_path_suffix}"
|
166
|
+
chunk.mv(path)
|
167
|
+
end
|
168
|
+
|
169
|
+
protected
|
170
|
+
def encode_key(key)
|
171
|
+
URI.encode(key, /[^-_.a-zA-Z0-9]/n)
|
172
|
+
end
|
173
|
+
|
174
|
+
def decode_key(encoded_key)
|
175
|
+
URI.decode(encoded_key, /[^-_.a-zA-Z0-9]/n)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
end
|
181
|
+
|
@@ -0,0 +1,97 @@
|
|
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 MemoryBufferChunk < BufferChunk
|
22
|
+
def initialize(key, data='')
|
23
|
+
@data = data
|
24
|
+
super(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(data)
|
28
|
+
@data << data
|
29
|
+
end
|
30
|
+
|
31
|
+
def size
|
32
|
+
@data.bytesize
|
33
|
+
end
|
34
|
+
|
35
|
+
def close
|
36
|
+
end
|
37
|
+
|
38
|
+
def purge
|
39
|
+
end
|
40
|
+
|
41
|
+
def read
|
42
|
+
@data
|
43
|
+
end
|
44
|
+
|
45
|
+
def open(&block)
|
46
|
+
StringIO.open(@data, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# optimize
|
50
|
+
def write_to(io)
|
51
|
+
io.write @data
|
52
|
+
end
|
53
|
+
|
54
|
+
# optimize
|
55
|
+
def msgpack_each(&block)
|
56
|
+
u = MessagePack::Unpacker.new(io)
|
57
|
+
u.feed_each(@data, &block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
class MemoryBuffer < BasicBuffer
|
63
|
+
Plugin.register_buffer('memory', self)
|
64
|
+
|
65
|
+
def initialize
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
69
|
+
def configure(conf)
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def before_shutdown(out)
|
74
|
+
synchronize do
|
75
|
+
@map.each_key {|key|
|
76
|
+
push(key)
|
77
|
+
}
|
78
|
+
while pop(out)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def new_chunk(key)
|
84
|
+
MemoryBufferChunk.new(key)
|
85
|
+
end
|
86
|
+
|
87
|
+
def resume
|
88
|
+
return [], {}
|
89
|
+
end
|
90
|
+
|
91
|
+
def enqueue(chunk)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end
|
97
|
+
|