fluentd 0.12.0.pre.1 → 0.12.0.pre.2
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.travis.yml +1 -0
- data/ChangeLog +21 -0
- data/README.md +10 -2
- data/Rakefile +4 -13
- data/example/v1_literal_example.conf +36 -0
- data/fluentd.gemspec +4 -1
- data/lib/fluent/buffer.rb +73 -46
- data/lib/fluent/command/fluentd.rb +7 -2
- data/lib/fluent/config/basic_parser.rb +5 -0
- data/lib/fluent/config/element.rb +2 -5
- data/lib/fluent/config/literal_parser.rb +26 -7
- data/lib/fluent/config/section.rb +2 -0
- data/lib/fluent/config/v1_parser.rb +9 -2
- data/lib/fluent/formatter.rb +2 -1
- data/lib/fluent/mixin.rb +22 -7
- data/lib/fluent/output.rb +17 -8
- data/lib/fluent/parser.rb +14 -3
- data/lib/fluent/plugin/buf_file.rb +30 -15
- data/lib/fluent/plugin/filter_grep.rb +69 -0
- data/lib/fluent/plugin/filter_record_transformer.rb +183 -0
- data/lib/fluent/plugin/in_exec.rb +6 -0
- data/lib/fluent/plugin/in_forward.rb +34 -4
- data/lib/fluent/plugin/in_http.rb +1 -1
- data/lib/fluent/plugin/out_exec.rb +1 -1
- data/lib/fluent/plugin/out_exec_filter.rb +8 -1
- data/lib/fluent/plugin/out_forward.rb +82 -4
- data/lib/fluent/supervisor.rb +1 -1
- data/lib/fluent/timezone.rb +131 -0
- data/lib/fluent/version.rb +1 -1
- data/test/config/assertions.rb +42 -0
- data/test/config/test_config_parser.rb +385 -0
- data/test/config/test_configurable.rb +530 -0
- data/test/config/test_configure_proxy.rb +99 -0
- data/test/config/test_dsl.rb +237 -0
- data/test/config/test_literal_parser.rb +293 -0
- data/test/config/test_section.rb +112 -0
- data/test/config/test_system_config.rb +49 -0
- data/test/helper.rb +25 -0
- data/test/plugin/test_buf_file.rb +604 -0
- data/test/plugin/test_buf_memory.rb +204 -0
- data/test/plugin/test_filter_grep.rb +124 -0
- data/test/plugin/test_filter_record_transformer.rb +251 -0
- data/test/plugin/test_in_exec.rb +1 -0
- data/test/plugin/test_in_forward.rb +205 -2
- data/test/plugin/test_in_gc_stat.rb +1 -0
- data/test/plugin/test_in_http.rb +58 -2
- data/test/plugin/test_in_object_space.rb +1 -0
- data/test/plugin/test_in_status.rb +1 -0
- data/test/plugin/test_in_stream.rb +1 -1
- data/test/plugin/test_in_syslog.rb +1 -1
- data/test/plugin/test_in_tail.rb +1 -0
- data/test/plugin/test_in_tcp.rb +1 -1
- data/test/plugin/test_in_udp.rb +1 -1
- data/test/plugin/test_out_copy.rb +1 -0
- data/test/plugin/test_out_exec.rb +1 -0
- data/test/plugin/test_out_exec_filter.rb +1 -0
- data/test/plugin/test_out_file.rb +36 -0
- data/test/plugin/test_out_forward.rb +279 -8
- data/test/plugin/test_out_roundrobin.rb +1 -0
- data/test/plugin/test_out_stdout.rb +1 -0
- data/test/plugin/test_out_stream.rb +1 -1
- data/test/test_buffer.rb +530 -0
- data/test/test_config.rb +1 -1
- data/test/test_configdsl.rb +1 -1
- data/test/test_formatter.rb +223 -0
- data/test/test_match.rb +1 -2
- data/test/test_mixin.rb +74 -2
- data/test/test_parser.rb +7 -1
- metadata +88 -35
- data/lib/fluent/plugin/buf_zfile.rb +0 -75
- data/spec/config/config_parser_spec.rb +0 -314
- data/spec/config/configurable_spec.rb +0 -524
- data/spec/config/configure_proxy_spec.rb +0 -96
- data/spec/config/dsl_spec.rb +0 -239
- data/spec/config/helper.rb +0 -49
- data/spec/config/literal_parser_spec.rb +0 -222
- data/spec/config/section_spec.rb +0 -97
- data/spec/config/system_config_spec.rb +0 -49
- data/spec/spec_helper.rb +0 -60
@@ -0,0 +1,69 @@
|
|
1
|
+
module Fluent
|
2
|
+
class GrepFilter < Filter
|
3
|
+
Fluent::Plugin.register_filter('grep', self)
|
4
|
+
|
5
|
+
REGEXP_MAX_NUM = 20
|
6
|
+
|
7
|
+
(1..REGEXP_MAX_NUM).each {|i| config_param :"regexp#{i}", :string, :default => nil }
|
8
|
+
(1..REGEXP_MAX_NUM).each {|i| config_param :"exclude#{i}", :string, :default => nil }
|
9
|
+
|
10
|
+
# for test
|
11
|
+
attr_reader :regexps
|
12
|
+
attr_reader :excludes
|
13
|
+
|
14
|
+
def configure(conf)
|
15
|
+
super
|
16
|
+
|
17
|
+
@regexps = {}
|
18
|
+
(1..REGEXP_MAX_NUM).each do |i|
|
19
|
+
next unless conf["regexp#{i}"]
|
20
|
+
key, regexp = conf["regexp#{i}"].split(/ /, 2)
|
21
|
+
raise ConfigError, "regexp#{i} does not contain 2 parameters" unless regexp
|
22
|
+
raise ConfigError, "regexp#{i} contains a duplicated key, #{key}" if @regexps[key]
|
23
|
+
@regexps[key] = Regexp.compile(regexp)
|
24
|
+
end
|
25
|
+
|
26
|
+
@excludes = {}
|
27
|
+
(1..REGEXP_MAX_NUM).each do |i|
|
28
|
+
next unless conf["exclude#{i}"]
|
29
|
+
key, exclude = conf["exclude#{i}"].split(/ /, 2)
|
30
|
+
raise ConfigError, "exclude#{i} does not contain 2 parameters" unless exclude
|
31
|
+
raise ConfigError, "exclude#{i} contains a duplicated key, #{key}" if @excludes[key]
|
32
|
+
@excludes[key] = Regexp.compile(exclude)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def filter_stream(tag, es)
|
37
|
+
result_es = MultiEventStream.new
|
38
|
+
es.each do |time, record|
|
39
|
+
catch(:break_loop) do
|
40
|
+
@regexps.each do |key, regexp|
|
41
|
+
throw :break_loop unless match(regexp, record[key].to_s)
|
42
|
+
end
|
43
|
+
@excludes.each do |key, exclude|
|
44
|
+
throw :break_loop if match(exclude, record[key].to_s)
|
45
|
+
end
|
46
|
+
result_es.add(time, record)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
result_es
|
50
|
+
rescue => e
|
51
|
+
log.warn "failed to grep events", :error_class => e.class, :error => e.message
|
52
|
+
log.warn_backtrace
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def match(regexp, string)
|
58
|
+
begin
|
59
|
+
return regexp.match(string)
|
60
|
+
rescue ArgumentError => e
|
61
|
+
raise e unless e.message.index("invalid byte sequence in".freeze).zero?
|
62
|
+
log.info "invalid byte sequence is replaced in `#{string}`"
|
63
|
+
string = string.scrub('?')
|
64
|
+
retry
|
65
|
+
end
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
class RecordTransformerFilter < Filter
|
5
|
+
Fluent::Plugin.register_filter('record_transformer', self)
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
require 'socket'
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
config_param :remove_keys, :string, :default => nil
|
13
|
+
config_param :keep_keys, :string, :default => nil
|
14
|
+
config_param :renew_record, :bool, :default => false
|
15
|
+
config_param :enable_ruby, :bool, :default => false
|
16
|
+
|
17
|
+
def configure(conf)
|
18
|
+
super
|
19
|
+
|
20
|
+
@map = {}
|
21
|
+
# <record></record> directive
|
22
|
+
conf.elements.select { |element| element.name == 'record' }.each do |element|
|
23
|
+
element.each_pair do |k, v|
|
24
|
+
element.has_key?(k) # to suppress unread configuration warning
|
25
|
+
@map[k] = v
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
if @remove_keys
|
30
|
+
@remove_keys = @remove_keys.split(',')
|
31
|
+
end
|
32
|
+
|
33
|
+
if @keep_keys
|
34
|
+
raise Fluent::ConfigError, "`renew_record` must be true to use `keep_keys`" unless @renew_record
|
35
|
+
@keep_keys = @keep_keys.split(',')
|
36
|
+
end
|
37
|
+
|
38
|
+
@placeholder_expander =
|
39
|
+
if @enable_ruby
|
40
|
+
# require utilities which would be used in ruby placeholders
|
41
|
+
require 'pathname'
|
42
|
+
require 'uri'
|
43
|
+
require 'cgi'
|
44
|
+
RubyPlaceholderExpander.new(log)
|
45
|
+
else
|
46
|
+
PlaceholderExpander.new(log)
|
47
|
+
end
|
48
|
+
|
49
|
+
@hostname = Socket.gethostname
|
50
|
+
end
|
51
|
+
|
52
|
+
def filter_stream(tag, es)
|
53
|
+
new_es = MultiEventStream.new
|
54
|
+
tag_parts = tag.split('.')
|
55
|
+
tag_prefix = tag_prefix(tag_parts)
|
56
|
+
tag_suffix = tag_suffix(tag_parts)
|
57
|
+
placeholders = {
|
58
|
+
'tag' => tag,
|
59
|
+
'tag_parts' => tag_parts,
|
60
|
+
'tag_prefix' => tag_prefix,
|
61
|
+
'tag_suffix' => tag_suffix,
|
62
|
+
'hostname' => @hostname,
|
63
|
+
}
|
64
|
+
last_record = nil
|
65
|
+
es.each do |time, record|
|
66
|
+
last_record = record # for debug log
|
67
|
+
new_record = reform(time, record, placeholders)
|
68
|
+
new_es.add(time, new_record)
|
69
|
+
end
|
70
|
+
new_es
|
71
|
+
rescue => e
|
72
|
+
log.warn "failed to reform records", :error_class => e.class, :error => e.message
|
73
|
+
log.warn_backtrace
|
74
|
+
log.debug "map:#{@map} record:#{last_record} placeholders:#{placeholders}"
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def reform(time, record, opts)
|
80
|
+
@placeholder_expander.prepare_placeholders(time, record, opts)
|
81
|
+
|
82
|
+
new_record = @renew_record ? {} : record.dup
|
83
|
+
@keep_keys.each {|k| new_record[k] = record[k]} if @keep_keys and @renew_record
|
84
|
+
@map.each_pair {|k, v| new_record[k] = @placeholder_expander.expand(v) }
|
85
|
+
@remove_keys.each {|k| new_record.delete(k) } if @remove_keys
|
86
|
+
|
87
|
+
new_record
|
88
|
+
end
|
89
|
+
|
90
|
+
def tag_prefix(tag_parts)
|
91
|
+
return [] if tag_parts.empty?
|
92
|
+
tag_prefix = [tag_parts.first]
|
93
|
+
1.upto(tag_parts.size-1).each do |i|
|
94
|
+
tag_prefix[i] = "#{tag_prefix[i-1]}.#{tag_parts[i]}"
|
95
|
+
end
|
96
|
+
tag_prefix
|
97
|
+
end
|
98
|
+
|
99
|
+
def tag_suffix(tag_parts)
|
100
|
+
return [] if tag_parts.empty?
|
101
|
+
rev_tag_parts = tag_parts.reverse
|
102
|
+
rev_tag_suffix = [rev_tag_parts.first]
|
103
|
+
1.upto(tag_parts.size-1).each do |i|
|
104
|
+
rev_tag_suffix[i] = "#{rev_tag_parts[i]}.#{rev_tag_suffix[i-1]}"
|
105
|
+
end
|
106
|
+
rev_tag_suffix.reverse!
|
107
|
+
end
|
108
|
+
|
109
|
+
class PlaceholderExpander
|
110
|
+
attr_reader :placeholders, :log
|
111
|
+
|
112
|
+
def initialize(log)
|
113
|
+
@log = log
|
114
|
+
end
|
115
|
+
|
116
|
+
def prepare_placeholders(time, record, opts)
|
117
|
+
placeholders = { '${time}' => Time.at(time).to_s }
|
118
|
+
record.each {|key, value| placeholders.store("${#{key}}", value) }
|
119
|
+
|
120
|
+
opts.each do |key, value|
|
121
|
+
if value.kind_of?(Array) # tag_parts, etc
|
122
|
+
size = value.size
|
123
|
+
value.each_with_index { |v, idx|
|
124
|
+
placeholders.store("${#{key}[#{idx}]}", v)
|
125
|
+
placeholders.store("${#{key}[#{idx-size}]}", v) # support [-1]
|
126
|
+
}
|
127
|
+
else # string, interger, float, and others?
|
128
|
+
placeholders.store("${#{key}}", value)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
@placeholders = placeholders
|
133
|
+
end
|
134
|
+
|
135
|
+
def expand(str)
|
136
|
+
str.gsub(/(\${[a-zA-Z0-9_]+(\[-?[0-9]+\])?}|__[A-Z_]+__)/) {
|
137
|
+
log.warn "unknown placeholder `#{$1}` found" unless @placeholders.include?($1)
|
138
|
+
@placeholders[$1]
|
139
|
+
}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class RubyPlaceholderExpander
|
144
|
+
attr_reader :placeholders, :log
|
145
|
+
|
146
|
+
def initialize(log)
|
147
|
+
@log = log
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get placeholders as a struct
|
151
|
+
#
|
152
|
+
# @param [Time] time the time
|
153
|
+
# @param [Hash] record the record
|
154
|
+
# @param [Hash] opts others
|
155
|
+
def prepare_placeholders(time, record, opts)
|
156
|
+
struct = UndefOpenStruct.new(record)
|
157
|
+
struct.time = Time.at(time)
|
158
|
+
opts.each {|key, value| struct.__send__("#{key}=", value) }
|
159
|
+
@placeholders = struct
|
160
|
+
end
|
161
|
+
|
162
|
+
# Replace placeholders in a string
|
163
|
+
#
|
164
|
+
# @param [String] str the string to be replaced
|
165
|
+
def expand(str)
|
166
|
+
interpolated = str.gsub(/\$\{([^}]+)\}/, '#{\1}') # ${..} => #{..}
|
167
|
+
begin
|
168
|
+
eval "\"#{interpolated}\"", @placeholders.instance_eval { binding }
|
169
|
+
rescue => e
|
170
|
+
log.warn "failed to expand `#{str}`", :error_class => e.class, :error => e.message
|
171
|
+
log.warn_backtrace
|
172
|
+
nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class UndefOpenStruct < OpenStruct
|
177
|
+
(Object.instance_methods).each do |m|
|
178
|
+
undef_method m unless m.to_s =~ /^__|respond_to_missing\?|object_id|public_methods|instance_eval|method_missing|define_singleton_method|respond_to\?|new_ostruct_member/
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -21,6 +21,7 @@ module Fluent
|
|
21
21
|
def initialize
|
22
22
|
super
|
23
23
|
require 'fluent/plugin/exec_util'
|
24
|
+
require 'fluent/timezone'
|
24
25
|
end
|
25
26
|
|
26
27
|
SUPPORTED_FORMAT = {
|
@@ -53,6 +54,11 @@ module Fluent
|
|
53
54
|
@localtime = false
|
54
55
|
end
|
55
56
|
|
57
|
+
if conf['timezone']
|
58
|
+
@timezone = conf['timezone']
|
59
|
+
Fluent::Timezone.validate!(@timezone)
|
60
|
+
end
|
61
|
+
|
56
62
|
if !@tag && !@tag_key
|
57
63
|
raise ConfigError, "'tag' or 'tag_key' option is required on exec input"
|
58
64
|
end
|
@@ -54,7 +54,16 @@ module Fluent
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def shutdown
|
57
|
-
|
57
|
+
# In test cases it occasionally appeared that when detaching a watcher, another watcher is also detached.
|
58
|
+
# In the case in the iteration of watchers, a watcher that has been already detached is intended to be detached
|
59
|
+
# and therfore RuntimeError occurs saying that it is not attached to a loop.
|
60
|
+
# It occures only when testing for sending responses to ForwardOutput.
|
61
|
+
# Sending responses needs to write the socket that is previously used only to read
|
62
|
+
# and a handler has 2 watchers that is used to read and to write.
|
63
|
+
# This problem occurs possibly because those watchers are thought to be related to each other
|
64
|
+
# and when detaching one of them the other is also detached for some reasons.
|
65
|
+
# As a workaround, check if watchers are attached before detaching them.
|
66
|
+
@loop.watchers.each {|w| w.detach if w.attached? }
|
58
67
|
@loop.stop
|
59
68
|
@usock.close
|
60
69
|
@thread.join
|
@@ -95,17 +104,20 @@ module Fluent
|
|
95
104
|
# message Forward {
|
96
105
|
# 1: string tag
|
97
106
|
# 2: list<Entry> entries
|
107
|
+
# 3: object option (optional)
|
98
108
|
# }
|
99
109
|
#
|
100
110
|
# message PackedForward {
|
101
111
|
# 1: string tag
|
102
112
|
# 2: raw entries # msgpack stream of Entry
|
113
|
+
# 3: object option (optional)
|
103
114
|
# }
|
104
115
|
#
|
105
116
|
# message Message {
|
106
117
|
# 1: string tag
|
107
118
|
# 2: long? time
|
108
119
|
# 3: object record
|
120
|
+
# 4: object option (optional)
|
109
121
|
# }
|
110
122
|
def on_message(msg, chunk_size, source)
|
111
123
|
if msg.nil?
|
@@ -128,6 +140,7 @@ module Fluent
|
|
128
140
|
# PackedForward
|
129
141
|
es = MessagePackEventStream.new(entries)
|
130
142
|
router.emit_stream(tag, es)
|
143
|
+
option = msg[2]
|
131
144
|
|
132
145
|
elsif entries.class == Array
|
133
146
|
# Forward
|
@@ -140,6 +153,7 @@ module Fluent
|
|
140
153
|
es.add(time, record)
|
141
154
|
}
|
142
155
|
router.emit_stream(tag, es)
|
156
|
+
option = msg[2]
|
143
157
|
|
144
158
|
else
|
145
159
|
# Message
|
@@ -148,7 +162,11 @@ module Fluent
|
|
148
162
|
time = msg[1]
|
149
163
|
time = Engine.now if time == 0
|
150
164
|
router.emit(tag, time, record)
|
165
|
+
option = msg[3]
|
151
166
|
end
|
167
|
+
|
168
|
+
# return option for response
|
169
|
+
option
|
152
170
|
end
|
153
171
|
|
154
172
|
class Handler < Coolio::Socket
|
@@ -186,13 +204,16 @@ module Fluent
|
|
186
204
|
first = data[0]
|
187
205
|
if first == '{' || first == '['
|
188
206
|
m = method(:on_read_json)
|
207
|
+
@serializer = :to_json.to_proc
|
189
208
|
@y = Yajl::Parser.new
|
190
209
|
@y.on_parse_complete = lambda { |obj|
|
191
|
-
@on_message.call(obj, @chunk_counter, @source)
|
210
|
+
option = @on_message.call(obj, @chunk_counter, @source)
|
211
|
+
respond option if option
|
192
212
|
@chunk_counter = 0
|
193
213
|
}
|
194
214
|
else
|
195
215
|
m = method(:on_read_msgpack)
|
216
|
+
@serializer = :to_msgpack.to_proc
|
196
217
|
@u = MessagePack::Unpacker.new
|
197
218
|
end
|
198
219
|
|
@@ -214,7 +235,8 @@ module Fluent
|
|
214
235
|
def on_read_msgpack(data)
|
215
236
|
@chunk_counter += data.bytesize
|
216
237
|
@u.feed_each(data) do |obj|
|
217
|
-
@on_message.call(obj, @chunk_counter, @source)
|
238
|
+
option = @on_message.call(obj, @chunk_counter, @source)
|
239
|
+
respond option if option
|
218
240
|
@chunk_counter = 0
|
219
241
|
end
|
220
242
|
rescue => e
|
@@ -223,8 +245,16 @@ module Fluent
|
|
223
245
|
close
|
224
246
|
end
|
225
247
|
|
248
|
+
def respond(option)
|
249
|
+
if option && option['chunk']
|
250
|
+
res = { 'ack' => option['chunk'] }
|
251
|
+
write @serializer.call(res)
|
252
|
+
@log.trace { "sent response to fluent socket" }
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
226
256
|
def on_close
|
227
|
-
@log.trace { "closed
|
257
|
+
@log.trace { "closed socket" }
|
228
258
|
end
|
229
259
|
end
|
230
260
|
|
@@ -190,7 +190,6 @@ module Fluent
|
|
190
190
|
@km = km
|
191
191
|
@callback = callback
|
192
192
|
@body_size_limit = body_size_limit
|
193
|
-
@content_type = ""
|
194
193
|
@next_close = false
|
195
194
|
@format = format
|
196
195
|
@log = log
|
@@ -235,6 +234,7 @@ module Fluent
|
|
235
234
|
@keep_alive = false
|
236
235
|
end
|
237
236
|
@env = {}
|
237
|
+
@content_type = ""
|
238
238
|
headers.each_pair {|k,v|
|
239
239
|
@env["HTTP_#{k.gsub('-','_').upcase}"] = v
|
240
240
|
case k
|
@@ -56,7 +56,7 @@ module Fluent
|
|
56
56
|
|
57
57
|
if @time_key
|
58
58
|
if @time_format
|
59
|
-
tf = TimeFormatter.new(@time_format, @localtime)
|
59
|
+
tf = TimeFormatter.new(@time_format, @localtime, @timezone)
|
60
60
|
@time_format_proc = tf.method(:format)
|
61
61
|
else
|
62
62
|
@time_format_proc = Proc.new { |time| time.to_s }
|
@@ -22,6 +22,7 @@ module Fluent
|
|
22
22
|
|
23
23
|
def initialize
|
24
24
|
super
|
25
|
+
require 'fluent/timezone'
|
25
26
|
end
|
26
27
|
|
27
28
|
config_param :command, :string
|
@@ -59,6 +60,7 @@ module Fluent
|
|
59
60
|
config_param :time_format, :string, :default => nil
|
60
61
|
|
61
62
|
config_param :localtime, :bool, :default => true
|
63
|
+
config_param :timezone, :string, :default => nil
|
62
64
|
config_param :num_children, :integer, :default => 1
|
63
65
|
|
64
66
|
# nil, 'none' or 0: no respawn, 'inf' or -1: infinite times, positive integer: try to respawn specified times only
|
@@ -96,13 +98,18 @@ module Fluent
|
|
96
98
|
@localtime = false
|
97
99
|
end
|
98
100
|
|
101
|
+
if conf['timezone']
|
102
|
+
@timezone = conf['timezone']
|
103
|
+
Fluent::Timezone.validate!(@timezone)
|
104
|
+
end
|
105
|
+
|
99
106
|
if !@tag && !@out_tag_key
|
100
107
|
raise ConfigError, "'tag' or 'out_tag_key' option is required on exec_filter output"
|
101
108
|
end
|
102
109
|
|
103
110
|
if @in_time_key
|
104
111
|
if f = @in_time_format
|
105
|
-
tf = TimeFormatter.new(f, @localtime)
|
112
|
+
tf = TimeFormatter.new(f, @localtime, @timezone)
|
106
113
|
@time_format_proc = tf.method(:format)
|
107
114
|
else
|
108
115
|
@time_format_proc = Proc.new {|time| time.to_s }
|
@@ -15,11 +15,21 @@
|
|
15
15
|
#
|
16
16
|
|
17
17
|
module Fluent
|
18
|
+
class ForwardOutputError < StandardError
|
19
|
+
end
|
20
|
+
|
21
|
+
class ForwardOutputResponseError < ForwardOutputError
|
22
|
+
end
|
23
|
+
|
24
|
+
class ForwardOutputACKTimeoutError < ForwardOutputResponseError
|
25
|
+
end
|
26
|
+
|
18
27
|
class ForwardOutput < ObjectBufferedOutput
|
19
28
|
Plugin.register_output('forward', self)
|
20
29
|
|
21
30
|
def initialize
|
22
31
|
super
|
32
|
+
require "base64"
|
23
33
|
require 'socket'
|
24
34
|
require 'fileutils'
|
25
35
|
require 'fluent/plugin/socket_util'
|
@@ -43,12 +53,22 @@ module Fluent
|
|
43
53
|
config_param :expire_dns_cache, :time, :default => nil # 0 means disable cache
|
44
54
|
config_param :phi_threshold, :integer, :default => 16
|
45
55
|
config_param :phi_failure_detector, :bool, :default => true
|
56
|
+
|
57
|
+
# if any options added that requires extended forward api, fix @extend_internal_protocol
|
58
|
+
|
59
|
+
config_param :require_ack_response, :bool, :default => false # require in_forward to respond with ack
|
60
|
+
config_param :ack_response_timeout, :time, :default => 190 # 0 means do not wait for ack responses
|
61
|
+
# Linux default tcp_syn_retries is 5 (in many environment)
|
62
|
+
# 3 + 6 + 12 + 24 + 48 + 96 -> 189 (sec)
|
63
|
+
|
46
64
|
attr_reader :nodes
|
47
65
|
|
48
66
|
# backward compatibility
|
49
67
|
config_param :port, :integer, :default => DEFAULT_LISTEN_PORT
|
50
68
|
config_param :host, :string, :default => nil
|
51
69
|
|
70
|
+
attr_accessor :extend_internal_protocol
|
71
|
+
|
52
72
|
def configure(conf)
|
53
73
|
super
|
54
74
|
|
@@ -64,6 +84,12 @@ module Fluent
|
|
64
84
|
|
65
85
|
recover_sample_size = @recover_wait / @heartbeat_interval
|
66
86
|
|
87
|
+
# add options here if any options addes which uses extended protocol
|
88
|
+
@extend_internal_protocol = if @require_ack_response
|
89
|
+
true
|
90
|
+
else
|
91
|
+
false
|
92
|
+
end
|
67
93
|
conf.elements.each {|e|
|
68
94
|
next if e.name != "server"
|
69
95
|
|
@@ -86,7 +112,7 @@ module Fluent
|
|
86
112
|
node_conf = NodeConfig.new(name, host, port, weight, standby, failure,
|
87
113
|
@phi_threshold, recover_sample_size, @expire_dns_cache, @phi_failure_detector)
|
88
114
|
@nodes << Node.new(log, node_conf)
|
89
|
-
log.info "adding forwarding server '#{name}'", :host=>host, :port=>port, :weight=>weight
|
115
|
+
log.info "adding forwarding server '#{name}'", :host=>host, :port=>port, :weight=>weight, :plugin_id=>plugin_id
|
90
116
|
}
|
91
117
|
end
|
92
118
|
|
@@ -200,8 +226,17 @@ module Fluent
|
|
200
226
|
@weight_array = weight_array
|
201
227
|
end
|
202
228
|
|
203
|
-
# MessagePack FixArray length =
|
204
|
-
|
229
|
+
# MessagePack FixArray length = 3 (if @extend_internal_protocol)
|
230
|
+
# = 2 (else)
|
231
|
+
FORWARD_HEADER = [0x92].pack('C').freeze
|
232
|
+
FORWARD_HEADER_EXT = [0x93].pack('C').freeze
|
233
|
+
def forward_header
|
234
|
+
if @extend_internal_protocol
|
235
|
+
FORWARD_HEADER_EXT
|
236
|
+
else
|
237
|
+
FORWARD_HEADER
|
238
|
+
end
|
239
|
+
end
|
205
240
|
|
206
241
|
#FORWARD_TCP_HEARTBEAT_DATA = FORWARD_HEADER + ''.to_msgpack + [].to_msgpack
|
207
242
|
def send_heartbeat_tcp(node)
|
@@ -229,7 +264,7 @@ module Fluent
|
|
229
264
|
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
230
265
|
|
231
266
|
# beginArray(2)
|
232
|
-
sock.write
|
267
|
+
sock.write forward_header
|
233
268
|
|
234
269
|
# writeRaw(tag)
|
235
270
|
sock.write tag.to_msgpack # tag
|
@@ -250,7 +285,46 @@ module Fluent
|
|
250
285
|
# writeRawBody(packed_es)
|
251
286
|
chunk.write_to(sock)
|
252
287
|
|
288
|
+
if @extend_internal_protocol
|
289
|
+
option = {}
|
290
|
+
option['chunk'] = Base64.encode64(chunk.unique_id) if @require_ack_response
|
291
|
+
sock.write option.to_msgpack
|
292
|
+
|
293
|
+
if @require_ack_response && @ack_response_timeout > 0
|
294
|
+
# Waiting for a response here results in a decrease of throughput because a chunk queue is locked.
|
295
|
+
# To avoid a decrease of troughput, it is necessary to prepare a list of chunks that wait for responses
|
296
|
+
# and process them asynchronously.
|
297
|
+
if IO.select([sock], nil, nil, @ack_response_timeout)
|
298
|
+
raw_data = sock.recv(1024)
|
299
|
+
|
300
|
+
# When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
|
301
|
+
# In this case, the node is available and successfully close connection without sending responses.
|
302
|
+
# ForwardInput is not expected to do so, but some alternatives may do so.
|
303
|
+
# Therefore do not send the chunk again.
|
304
|
+
unless raw_data.empty?
|
305
|
+
# Serialization type of the response is same as sent data.
|
306
|
+
res = MessagePack.unpack(raw_data)
|
307
|
+
|
308
|
+
if res['ack'] != option['chunk']
|
309
|
+
# Some errors may have occured when ack and chunk id is different, so send the chunk again.
|
310
|
+
raise ForwardOutputResponseError, "ack in response and chunk id in sent data are different"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
else
|
315
|
+
# IO.select returns nil on timeout.
|
316
|
+
# There are 2 types of cases when no response has been received:
|
317
|
+
# (1) the node does not support sending responses
|
318
|
+
# (2) the node does support sending response but responses have not arrived for some reasons.
|
319
|
+
@log.warn "no response from #{node.host}:#{node.port}. regard it as unavailable."
|
320
|
+
node.disable!
|
321
|
+
raise ForwardOutputACKTimeoutError, "node #{node.host}:#{node.port} does not return ACK"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
253
326
|
node.heartbeat(false)
|
327
|
+
return res # for test
|
254
328
|
ensure
|
255
329
|
sock.close
|
256
330
|
end
|
@@ -354,6 +428,10 @@ module Fluent
|
|
354
428
|
@available
|
355
429
|
end
|
356
430
|
|
431
|
+
def disable!
|
432
|
+
@available = false
|
433
|
+
end
|
434
|
+
|
357
435
|
def standby?
|
358
436
|
@conf.standby
|
359
437
|
end
|