fluentd 0.14.7-x86-mingw32 → 0.14.10-x86-mingw32
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 +2 -0
- data/.travis.yml +2 -0
- data/CONTRIBUTING.md +6 -1
- data/ChangeLog +95 -0
- data/Rakefile +21 -0
- data/appveyor.yml +1 -0
- data/code-of-conduct.md +3 -0
- data/example/out_exec_filter.conf +42 -0
- data/fluentd.gemspec +1 -1
- data/lib/fluent/agent.rb +2 -2
- data/lib/fluent/command/binlog_reader.rb +1 -1
- data/lib/fluent/command/cat.rb +15 -4
- data/lib/fluent/compat/output.rb +14 -9
- data/lib/fluent/compat/parser.rb +141 -11
- data/lib/fluent/config/configure_proxy.rb +2 -11
- data/lib/fluent/config/section.rb +8 -1
- data/lib/fluent/configurable.rb +1 -3
- data/lib/fluent/env.rb +1 -1
- data/lib/fluent/log.rb +1 -1
- data/lib/fluent/plugin/base.rb +17 -0
- data/lib/fluent/plugin/filter_parser.rb +108 -0
- data/lib/fluent/plugin/filter_record_transformer.rb +14 -35
- data/lib/fluent/plugin/filter_stdout.rb +1 -1
- data/lib/fluent/plugin/formatter.rb +5 -0
- data/lib/fluent/plugin/formatter_msgpack.rb +4 -0
- data/lib/fluent/plugin/formatter_stdout.rb +3 -2
- data/lib/fluent/plugin/formatter_tsv.rb +34 -0
- data/lib/fluent/plugin/in_exec.rb +48 -93
- data/lib/fluent/plugin/in_forward.rb +66 -265
- data/lib/fluent/plugin/in_http.rb +68 -65
- data/lib/fluent/plugin/in_monitor_agent.rb +8 -4
- data/lib/fluent/plugin/in_syslog.rb +42 -58
- data/lib/fluent/plugin/in_tail.rb +29 -14
- data/lib/fluent/plugin/in_tcp.rb +54 -14
- data/lib/fluent/plugin/in_udp.rb +49 -13
- data/lib/fluent/plugin/multi_output.rb +1 -3
- data/lib/fluent/plugin/out_exec.rb +58 -71
- data/lib/fluent/plugin/out_exec_filter.rb +199 -279
- data/lib/fluent/plugin/out_file.rb +172 -81
- data/lib/fluent/plugin/out_forward.rb +229 -206
- data/lib/fluent/plugin/out_stdout.rb +6 -21
- data/lib/fluent/plugin/output.rb +90 -59
- data/lib/fluent/plugin/parser.rb +121 -61
- data/lib/fluent/plugin/parser_csv.rb +9 -3
- data/lib/fluent/plugin/parser_json.rb +37 -35
- data/lib/fluent/plugin/parser_ltsv.rb +11 -19
- data/lib/fluent/plugin/parser_msgpack.rb +50 -0
- data/lib/fluent/plugin/parser_regexp.rb +15 -42
- data/lib/fluent/plugin/parser_tsv.rb +8 -3
- data/lib/fluent/plugin_helper.rb +10 -1
- data/lib/fluent/plugin_helper/child_process.rb +139 -73
- data/lib/fluent/plugin_helper/compat_parameters.rb +93 -4
- data/lib/fluent/plugin_helper/event_emitter.rb +14 -1
- data/lib/fluent/plugin_helper/event_loop.rb +24 -6
- data/lib/fluent/plugin_helper/extract.rb +16 -4
- data/lib/fluent/plugin_helper/formatter.rb +9 -11
- data/lib/fluent/plugin_helper/inject.rb +16 -1
- data/lib/fluent/plugin_helper/parser.rb +3 -3
- data/lib/fluent/plugin_helper/server.rb +494 -0
- data/lib/fluent/plugin_helper/socket.rb +101 -0
- data/lib/fluent/plugin_helper/socket_option.rb +84 -0
- data/lib/fluent/plugin_helper/timer.rb +1 -0
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/test/driver/base.rb +95 -49
- data/lib/fluent/test/driver/base_owner.rb +18 -8
- data/lib/fluent/test/driver/multi_output.rb +2 -1
- data/lib/fluent/test/driver/output.rb +29 -6
- data/lib/fluent/test/helpers.rb +3 -1
- data/lib/fluent/test/log.rb +4 -0
- data/lib/fluent/test/startup_shutdown.rb +13 -0
- data/lib/fluent/time.rb +14 -8
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +1 -1
- data/test/command/test_binlog_reader.rb +5 -1
- data/test/compat/test_parser.rb +10 -0
- data/test/config/test_configurable.rb +193 -0
- data/test/config/test_configure_proxy.rb +0 -43
- data/test/helper.rb +36 -1
- data/test/plugin/test_base.rb +16 -0
- data/test/plugin/test_filter_parser.rb +665 -0
- data/test/plugin/test_filter_record_transformer.rb +36 -100
- data/test/plugin/test_filter_stdout.rb +18 -27
- data/test/plugin/test_in_dummy.rb +1 -1
- data/test/plugin/test_in_exec.rb +206 -94
- data/test/plugin/test_in_forward.rb +268 -347
- data/test/plugin/test_in_http.rb +310 -186
- data/test/plugin/test_in_monitor_agent.rb +65 -35
- data/test/plugin/test_in_syslog.rb +39 -3
- data/test/plugin/test_in_tcp.rb +78 -62
- data/test/plugin/test_in_udp.rb +101 -80
- data/test/plugin/test_out_exec.rb +223 -68
- data/test/plugin/test_out_exec_filter.rb +520 -169
- data/test/plugin/test_out_file.rb +637 -177
- data/test/plugin/test_out_forward.rb +242 -234
- data/test/plugin/test_out_null.rb +1 -1
- data/test/plugin/test_out_secondary_file.rb +4 -2
- data/test/plugin/test_out_stdout.rb +14 -35
- data/test/plugin/test_output_as_buffered.rb +60 -2
- data/test/plugin/test_parser.rb +359 -0
- data/test/plugin/test_parser_csv.rb +1 -2
- data/test/plugin/test_parser_json.rb +3 -4
- data/test/plugin/test_parser_labeled_tsv.rb +1 -2
- data/test/plugin/test_parser_none.rb +1 -2
- data/test/plugin/test_parser_regexp.rb +8 -4
- data/test/plugin/test_parser_tsv.rb +4 -3
- data/test/plugin_helper/test_child_process.rb +184 -0
- data/test/plugin_helper/test_compat_parameters.rb +88 -1
- data/test/plugin_helper/test_extract.rb +0 -1
- data/test/plugin_helper/test_formatter.rb +5 -2
- data/test/plugin_helper/test_inject.rb +21 -0
- data/test/plugin_helper/test_parser.rb +6 -5
- data/test/plugin_helper/test_server.rb +905 -0
- data/test/test_event_time.rb +3 -1
- data/test/test_output.rb +53 -2
- data/test/test_plugin_classes.rb +20 -0
- data/test/test_root_agent.rb +139 -0
- data/test/test_test_drivers.rb +135 -0
- metadata +28 -8
- data/test/plugin/test_parser_base.rb +0 -32
@@ -48,10 +48,19 @@ module Fluent
|
|
48
48
|
}
|
49
49
|
|
50
50
|
PARSER_PARAMS = {
|
51
|
-
"format" =>
|
51
|
+
"format" => nil,
|
52
|
+
"types" => nil,
|
53
|
+
"types_delimiter" => nil,
|
54
|
+
"types_label_delimiter" => nil,
|
55
|
+
"keys" => "keys", # CSVParser, TSVParser (old ValuesParser)
|
52
56
|
"time_key" => "time_key",
|
53
57
|
"time_format" => "time_format",
|
58
|
+
"localtim" => nil,
|
59
|
+
"utc" => nil,
|
54
60
|
"delimiter" => "delimiter",
|
61
|
+
"keep_time_key" => "keep_time_key",
|
62
|
+
"null_empty_string" => "null_empty_string",
|
63
|
+
"null_value_pattern" => "null_value_pattern",
|
55
64
|
"json_parser" => "json_parser", # JSONParser
|
56
65
|
"label_delimiter" => "label_delimiter", # LabeledTSVParser
|
57
66
|
"format_firstline" => "format_firstline", # MultilineParser
|
@@ -62,19 +71,29 @@ module Fluent
|
|
62
71
|
|
63
72
|
INJECT_PARAMS = {
|
64
73
|
"include_time_key" => nil,
|
65
|
-
"time_key"
|
66
|
-
"time_format"
|
67
|
-
"timezone"
|
74
|
+
"time_key" => "time_key",
|
75
|
+
"time_format" => "time_format",
|
76
|
+
"timezone" => "timezone",
|
68
77
|
"include_tag_key" => nil,
|
69
78
|
"tag_key" => "tag_key",
|
70
79
|
"localtime" => nil,
|
71
80
|
"utc" => nil,
|
72
81
|
}
|
73
82
|
|
83
|
+
EXTRACT_PARAMS = {
|
84
|
+
"time_key" => "time_key",
|
85
|
+
"time_format" => "time_format",
|
86
|
+
"timezone" => "timezone",
|
87
|
+
"tag_key" => "tag_key",
|
88
|
+
"localtime" => nil,
|
89
|
+
"utc" => nil,
|
90
|
+
}
|
91
|
+
|
74
92
|
FORMATTER_PARAMS = {
|
75
93
|
"format" => "@type",
|
76
94
|
"delimiter" => "delimiter",
|
77
95
|
"force_quotes" => "force_quotes", # CsvFormatter
|
96
|
+
"keys" => "keys", # TSVFormatter
|
78
97
|
"fields" => "fields", # CsvFormatter
|
79
98
|
"json_parser" => "json_parser", # JSONFormatter
|
80
99
|
"label_delimiter" => "label_delimiter", # LabeledTSVFormatter
|
@@ -95,6 +114,8 @@ module Fluent
|
|
95
114
|
compat_parameters_buffer(conf, **kwargs)
|
96
115
|
when :inject
|
97
116
|
compat_parameters_inject(conf)
|
117
|
+
when :extract
|
118
|
+
compat_parameters_extract(conf)
|
98
119
|
when :parser
|
99
120
|
compat_parameters_parser(conf)
|
100
121
|
when :formatter
|
@@ -167,6 +188,7 @@ module Fluent
|
|
167
188
|
hash['time_type'] ||= 'string'
|
168
189
|
end
|
169
190
|
if conf.has_key?('time_as_epoch') && Fluent::Config.bool_value(conf['time_as_epoch'])
|
191
|
+
hash['time_key'] ||= 'time'
|
170
192
|
hash['time_type'] = 'unixtime'
|
171
193
|
end
|
172
194
|
if conf.has_key?('localtime') || conf.has_key?('utc')
|
@@ -193,6 +215,40 @@ module Fluent
|
|
193
215
|
conf
|
194
216
|
end
|
195
217
|
|
218
|
+
def compat_parameters_extract(conf)
|
219
|
+
return unless conf.elements('extract').empty?
|
220
|
+
return if EXTRACT_PARAMS.keys.all?{|k| !conf.has_key?(k) } && !conf.has_key?('format')
|
221
|
+
|
222
|
+
# TODO: warn obsolete parameters if these are deprecated
|
223
|
+
hash = compat_parameters_copy_to_subsection_attributes(conf, EXTRACT_PARAMS)
|
224
|
+
|
225
|
+
if conf.has_key?('time_as_epoch') && Fluent::Config.bool_value(conf['time_as_epoch'])
|
226
|
+
hash['time_key'] ||= 'time'
|
227
|
+
hash['time_type'] = 'unixtime'
|
228
|
+
elsif conf.has_key?('format') && conf["format"].start_with?("/") && conf["format"].end_with?("/") # old-style regexp parser
|
229
|
+
hash['time_key'] ||= 'time'
|
230
|
+
hash['time_type'] ||= 'string'
|
231
|
+
end
|
232
|
+
if conf.has_key?('localtime') || conf.has_key?('utc')
|
233
|
+
if conf.has_key?('localtime') && conf.has_key?('utc')
|
234
|
+
raise Fluent::ConfigError, "both of utc and localtime are specified, use only one of them"
|
235
|
+
elsif conf.has_key?('localtime')
|
236
|
+
hash['localtime'] = Fluent::Config.bool_value(conf['localtime'])
|
237
|
+
elsif conf.has_key?('utc')
|
238
|
+
hash['localtime'] = !(Fluent::Config.bool_value(conf['utc']))
|
239
|
+
# Specifying "localtime false" means using UTC in TimeFormatter
|
240
|
+
# And specifying "utc" is different from specifying "timezone +0000"(it's not always UTC).
|
241
|
+
# There are difference between "Z" and "+0000" in timezone formatting.
|
242
|
+
# TODO: add kwargs to TimeFormatter to specify "using localtime", "using UTC" or "using specified timezone" in more explicit way
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
e = Fluent::Config::Element.new('extract', '', hash, [])
|
247
|
+
conf.elements << e
|
248
|
+
|
249
|
+
conf
|
250
|
+
end
|
251
|
+
|
196
252
|
def compat_parameters_parser(conf)
|
197
253
|
return unless conf.elements('parse').empty?
|
198
254
|
return if PARSER_PARAMS.keys.all?{|k| !conf.has_key?(k) }
|
@@ -200,6 +256,39 @@ module Fluent
|
|
200
256
|
# TODO: warn obsolete parameters if these are deprecated
|
201
257
|
hash = compat_parameters_copy_to_subsection_attributes(conf, PARSER_PARAMS)
|
202
258
|
|
259
|
+
if conf["format"]
|
260
|
+
if conf["format"].start_with?("/") && conf["format"].end_with?("/")
|
261
|
+
hash["@type"] = "regexp"
|
262
|
+
hash["expression"] = conf["format"][1..-2]
|
263
|
+
else
|
264
|
+
hash["@type"] = conf["format"]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
if conf["types"]
|
269
|
+
delimiter = conf["types_delimiter"] || ','
|
270
|
+
label_delimiter = conf["types_label_delimiter"] || ':'
|
271
|
+
types = {}
|
272
|
+
conf['types'].split(delimiter).each do |pair|
|
273
|
+
key, value = pair.split(label_delimiter, 2)
|
274
|
+
types[key] = value
|
275
|
+
end
|
276
|
+
hash["types"] = JSON.dump(types)
|
277
|
+
end
|
278
|
+
if conf.has_key?('localtime') || conf.has_key?('utc')
|
279
|
+
if conf.has_key?('localtime') && conf.has_key?('utc')
|
280
|
+
raise Fluent::ConfigError, "both of utc and localtime are specified, use only one of them"
|
281
|
+
elsif conf.has_key?('localtime')
|
282
|
+
hash['localtime'] = Fluent::Config.bool_value(conf['localtime'])
|
283
|
+
elsif conf.has_key?('utc')
|
284
|
+
hash['localtime'] = !(Fluent::Config.bool_value(conf['utc']))
|
285
|
+
# Specifying "localtime false" means using UTC in TimeFormatter
|
286
|
+
# And specifying "utc" is different from specifying "timezone +0000"(it's not always UTC).
|
287
|
+
# There are difference between "Z" and "+0000" in timezone formatting.
|
288
|
+
# TODO: add kwargs to TimeFormatter to specify "using localtime", "using UTC" or "using specified timezone" in more explicit way
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
203
292
|
e = Fluent::Config::Element.new('parse', '', hash, [])
|
204
293
|
conf.elements << e
|
205
294
|
|
@@ -26,10 +26,14 @@ module Fluent
|
|
26
26
|
|
27
27
|
def router
|
28
28
|
@_event_emitter_used_actually = true
|
29
|
+
if @_event_emitter_lazy_init
|
30
|
+
@router = @primary_instance.router
|
31
|
+
end
|
29
32
|
@router
|
30
33
|
end
|
31
34
|
|
32
35
|
def router=(r)
|
36
|
+
# not recommended now...
|
33
37
|
@router = r
|
34
38
|
end
|
35
39
|
|
@@ -44,14 +48,23 @@ module Fluent
|
|
44
48
|
def event_emitter_router(label_name)
|
45
49
|
if label_name
|
46
50
|
Engine.root_agent.find_label(label_name).event_router
|
51
|
+
elsif self.respond_to?(:as_secondary) && self.as_secondary
|
52
|
+
if @primary_instance.has_router?
|
53
|
+
@_event_emitter_lazy_init = true
|
54
|
+
nil # primary plugin's event router is not initialized yet, here.
|
55
|
+
else
|
56
|
+
@primary_instance.context_router
|
57
|
+
end
|
47
58
|
else
|
48
|
-
Engine.root_agent.event_router
|
59
|
+
# `Engine.root_agent.event_router` is for testing
|
60
|
+
self.context_router || Engine.root_agent.event_router
|
49
61
|
end
|
50
62
|
end
|
51
63
|
|
52
64
|
def initialize
|
53
65
|
super
|
54
66
|
@_event_emitter_used_actually = false
|
67
|
+
@_event_emitter_lazy_init = false
|
55
68
|
@router = nil
|
56
69
|
end
|
57
70
|
|
@@ -30,12 +30,16 @@ module Fluent
|
|
30
30
|
# terminate: initialize internal state
|
31
31
|
|
32
32
|
EVENT_LOOP_RUN_DEFAULT_TIMEOUT = 0.5
|
33
|
+
EVENT_LOOP_SHUTDOWN_TIMEOUT = 5
|
34
|
+
EVENT_LOOP_CLOCK_ID = Process::CLOCK_MONOTONIC_RAW rescue Process::CLOCK_MONOTONIC
|
33
35
|
|
34
36
|
attr_reader :_event_loop # for tests
|
35
37
|
|
36
38
|
def event_loop_attach(watcher)
|
37
39
|
@_event_loop_mutex.synchronize do
|
38
40
|
@_event_loop.attach(watcher)
|
41
|
+
@_event_loop_attached_watchers << watcher
|
42
|
+
watcher
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
@@ -58,6 +62,7 @@ module Fluent
|
|
58
62
|
@_event_loop_mutex = Mutex.new
|
59
63
|
# plugin MAY configure loop run timeout in #configure
|
60
64
|
@_event_loop_run_timeout = EVENT_LOOP_RUN_DEFAULT_TIMEOUT
|
65
|
+
@_event_loop_attached_watchers = []
|
61
66
|
end
|
62
67
|
|
63
68
|
def start
|
@@ -65,19 +70,32 @@ module Fluent
|
|
65
70
|
|
66
71
|
# event loop does not run here, so mutex lock is not required
|
67
72
|
thread_create :event_loop do
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
+
begin
|
74
|
+
default_watcher = DefaultWatcher.new
|
75
|
+
event_loop_attach(default_watcher)
|
76
|
+
@_event_loop_running = true
|
77
|
+
@_event_loop.run(@_event_loop_run_timeout) # this method blocks
|
78
|
+
ensure
|
79
|
+
@_event_loop_running = false
|
80
|
+
end
|
73
81
|
end
|
74
82
|
end
|
75
83
|
|
76
84
|
def shutdown
|
77
85
|
@_event_loop_mutex.synchronize do
|
78
|
-
@
|
86
|
+
@_event_loop_attached_watchers.reverse.each do |w|
|
87
|
+
if w.attached?
|
88
|
+
w.detach
|
89
|
+
end
|
90
|
+
end
|
79
91
|
end
|
92
|
+
timeout_at = Process.clock_gettime(EVENT_LOOP_CLOCK_ID) + EVENT_LOOP_SHUTDOWN_TIMEOUT
|
80
93
|
while @_event_loop_running
|
94
|
+
if Process.clock_gettime(EVENT_LOOP_CLOCK_ID) >= timeout_at
|
95
|
+
log.warn "event loop does NOT exit until hard timeout."
|
96
|
+
raise "event loop does NOT exit until hard timeout." if @under_plugin_development
|
97
|
+
break
|
98
|
+
end
|
81
99
|
sleep 0.1
|
82
100
|
end
|
83
101
|
|
@@ -25,7 +25,8 @@ module Fluent
|
|
25
25
|
return nil unless @_extract_enabled
|
26
26
|
|
27
27
|
if @_extract_tag_key && record.has_key?(@_extract_tag_key)
|
28
|
-
|
28
|
+
v = @_extract_keep_tag_key ? record[@_extract_tag_key] : record.delete(@_extract_tag_key)
|
29
|
+
return v.to_s
|
29
30
|
end
|
30
31
|
|
31
32
|
nil
|
@@ -35,7 +36,8 @@ module Fluent
|
|
35
36
|
return nil unless @_extract_enabled
|
36
37
|
|
37
38
|
if @_extract_time_key && record.has_key?(@_extract_time_key)
|
38
|
-
|
39
|
+
v = @_extract_keep_time_key ? record[@_extract_time_key] : record.delete(@_extract_time_key)
|
40
|
+
return @_extract_time_parser.call(v)
|
39
41
|
end
|
40
42
|
|
41
43
|
nil
|
@@ -45,7 +47,9 @@ module Fluent
|
|
45
47
|
include Fluent::Configurable
|
46
48
|
config_section :extract, required: false, multi: false, param_name: :extract_config do
|
47
49
|
config_param :tag_key, :string, default: nil
|
50
|
+
config_param :keep_tag_key, :bool, default: false
|
48
51
|
config_param :time_key, :string, default: nil
|
52
|
+
config_param :keep_time_key, :bool, default: false
|
49
53
|
|
50
54
|
# To avoid defining :time_type twice
|
51
55
|
config_param :time_type, :enum, list: [:float, :unixtime, :string], default: :float
|
@@ -64,7 +68,9 @@ module Fluent
|
|
64
68
|
super
|
65
69
|
@_extract_enabled = false
|
66
70
|
@_extract_tag_key = nil
|
71
|
+
@_extract_keep_tag_key = nil
|
67
72
|
@_extract_time_key = nil
|
73
|
+
@_extract_keep_time_key = nil
|
68
74
|
@_extract_time_parser = nil
|
69
75
|
end
|
70
76
|
|
@@ -73,15 +79,21 @@ module Fluent
|
|
73
79
|
|
74
80
|
if @extract_config
|
75
81
|
@_extract_tag_key = @extract_config.tag_key
|
82
|
+
@_extract_keep_tag_key = @extract_config.keep_tag_key
|
76
83
|
@_extract_time_key = @extract_config.time_key
|
77
84
|
if @_extract_time_key
|
85
|
+
@_extract_keep_time_key = @extract_config.keep_time_key
|
78
86
|
@_extract_time_parser = case @extract_config.time_type
|
79
|
-
when :float then
|
80
|
-
when :unixtime then
|
87
|
+
when :float then Fluent::NumericTimeParser.new(:float)
|
88
|
+
when :unixtime then Fluent::NumericTimeParser.new(:unixtime)
|
81
89
|
else
|
82
90
|
localtime = @extract_config.localtime && !@extract_config.utc
|
83
91
|
Fluent::TimeParser.new(@extract_config.time_format, localtime, @extract_config.timezone)
|
84
92
|
end
|
93
|
+
else
|
94
|
+
if @extract_config.time_format
|
95
|
+
log.warn "'time_format' specified without 'time_key', will be ignored"
|
96
|
+
end
|
85
97
|
end
|
86
98
|
|
87
99
|
@_extract_enabled = @_extract_tag_key || @_extract_time_key
|
@@ -24,7 +24,7 @@ module Fluent
|
|
24
24
|
module Formatter
|
25
25
|
def formatter_create(usage: '', type: nil, conf: nil, default_type: nil)
|
26
26
|
formatter = @_formatters[usage]
|
27
|
-
return formatter if formatter
|
27
|
+
return formatter if formatter && !type && !conf
|
28
28
|
|
29
29
|
type = if type
|
30
30
|
type
|
@@ -61,9 +61,9 @@ module Fluent
|
|
61
61
|
module FormatterParams
|
62
62
|
include Fluent::Configurable
|
63
63
|
# minimum section definition to instantiate formatter plugin instances
|
64
|
-
config_section :format, required: false, multi: true, param_name: :formatter_configs do
|
64
|
+
config_section :format, required: false, multi: true, init: true, param_name: :formatter_configs do
|
65
65
|
config_argument :usage, :string, default: ''
|
66
|
-
config_param :@type, :string
|
66
|
+
config_param :@type, :string # config_set_default required for :@type
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -82,15 +82,13 @@ module Fluent
|
|
82
82
|
def configure(conf)
|
83
83
|
super
|
84
84
|
|
85
|
-
|
86
|
-
@
|
87
|
-
|
88
|
-
raise Fluent::ConfigError, "duplicated formatter configured: #{section.usage}"
|
89
|
-
end
|
90
|
-
formatter = Plugin.new_formatter(section[:@type], parent: self)
|
91
|
-
formatter.configure(section.corresponding_config_element)
|
92
|
-
@_formatters[section.usage] = formatter
|
85
|
+
@formatter_configs.each do |section|
|
86
|
+
if @_formatters[section.usage]
|
87
|
+
raise Fluent::ConfigError, "duplicated formatter configured: #{section.usage}"
|
93
88
|
end
|
89
|
+
formatter = Plugin.new_formatter(section[:@type], parent: self)
|
90
|
+
formatter.configure(section.corresponding_config_element)
|
91
|
+
@_formatters[section.usage] = formatter
|
94
92
|
end
|
95
93
|
end
|
96
94
|
|
@@ -97,9 +97,20 @@ module Fluent
|
|
97
97
|
if @inject_config
|
98
98
|
@_inject_hostname_key = @inject_config.hostname_key
|
99
99
|
if @_inject_hostname_key
|
100
|
+
if self.respond_to?(:buffer_config)
|
101
|
+
# Output plugin cannot use "hostname"(specified by @hostname_key),
|
102
|
+
# injected by this plugin helper, in chunk keys.
|
103
|
+
# This plugin helper works in `#format` (in many cases), but modified record
|
104
|
+
# don't have any side effect in chunking of output plugin.
|
105
|
+
if self.buffer_config.chunk_keys.include?(@_inject_hostname_key)
|
106
|
+
log.error "Use filters to inject hostname to use it in buffer chunking."
|
107
|
+
raise Fluent::ConfigError, "the key specified by 'hostname_key' in <inject> cannot be used in buffering chunk key."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
100
111
|
@_inject_hostname = @inject_config.hostname
|
101
112
|
unless @_inject_hostname
|
102
|
-
@_inject_hostname = Socket.gethostname
|
113
|
+
@_inject_hostname = ::Socket.gethostname
|
103
114
|
log.info "using hostname for specified field", host_key: @_inject_hostname_key, host_name: @_inject_hostname
|
104
115
|
end
|
105
116
|
end
|
@@ -113,6 +124,10 @@ module Fluent
|
|
113
124
|
localtime = @inject_config.localtime && !@inject_config.utc
|
114
125
|
Fluent::TimeFormatter.new(@inject_config.time_format, localtime, @inject_config.timezone)
|
115
126
|
end
|
127
|
+
else
|
128
|
+
if @inject_config.time_format
|
129
|
+
log.warn "'time_format' specified without 'time_key', will be ignored"
|
130
|
+
end
|
116
131
|
end
|
117
132
|
|
118
133
|
@_inject_enabled = @_inject_hostname_key || @_inject_tag_key || @_inject_time_key
|
@@ -24,7 +24,7 @@ module Fluent
|
|
24
24
|
module Parser
|
25
25
|
def parser_create(usage: '', type: nil, conf: nil, default_type: nil)
|
26
26
|
parser = @_parsers[usage]
|
27
|
-
return parser if parser
|
27
|
+
return parser if parser && !type && !conf
|
28
28
|
|
29
29
|
type = if type
|
30
30
|
type
|
@@ -61,9 +61,9 @@ module Fluent
|
|
61
61
|
module ParserParams
|
62
62
|
include Fluent::Configurable
|
63
63
|
# minimum section definition to instantiate parser plugin instances
|
64
|
-
config_section :parse, required: false, multi: true, param_name: :parser_configs do
|
64
|
+
config_section :parse, required: false, multi: true, init: true, param_name: :parser_configs do
|
65
65
|
config_argument :usage, :string, default: ''
|
66
|
-
config_param :@type, :string
|
66
|
+
config_param :@type, :string # config_set_default required for :@type
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -0,0 +1,494 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'fluent/plugin_helper/event_loop'
|
18
|
+
|
19
|
+
require 'serverengine'
|
20
|
+
require 'cool.io'
|
21
|
+
require 'socket'
|
22
|
+
require 'ipaddr'
|
23
|
+
require 'fcntl'
|
24
|
+
|
25
|
+
require_relative 'socket_option'
|
26
|
+
|
27
|
+
module Fluent
|
28
|
+
module PluginHelper
|
29
|
+
module Server
|
30
|
+
include Fluent::PluginHelper::EventLoop
|
31
|
+
include Fluent::PluginHelper::SocketOption
|
32
|
+
|
33
|
+
# This plugin helper doesn't support these things for now:
|
34
|
+
# * SSL/TLS (TBD)
|
35
|
+
# * TCP/TLS keepalive
|
36
|
+
|
37
|
+
# stop : [-]
|
38
|
+
# shutdown : detach server event handler from event loop (event_loop)
|
39
|
+
# close : close listening sockets
|
40
|
+
# terminate: remote all server instances
|
41
|
+
|
42
|
+
attr_reader :_servers # for tests
|
43
|
+
|
44
|
+
def server_wait_until_start
|
45
|
+
# event_loop_wait_until_start works well for this
|
46
|
+
end
|
47
|
+
|
48
|
+
def server_wait_until_stop
|
49
|
+
sleep 0.1 while @_servers.any?{|si| si.server.attached? }
|
50
|
+
@_servers.each{|si| si.server.close rescue nil }
|
51
|
+
end
|
52
|
+
|
53
|
+
PROTOCOLS = [:tcp, :udp, :tls, :unix]
|
54
|
+
CONNECTION_PROTOCOLS = [:tcp, :tls, :unix]
|
55
|
+
|
56
|
+
# server_create_connection(:title, @port) do |conn|
|
57
|
+
# # on connection
|
58
|
+
# source_addr = conn.remote_host
|
59
|
+
# source_port = conn.remote_port
|
60
|
+
# conn.data do |data|
|
61
|
+
# # on data
|
62
|
+
# conn.write resp # ...
|
63
|
+
# conn.close
|
64
|
+
# end
|
65
|
+
# end
|
66
|
+
def server_create_connection(title, port, proto: :tcp, bind: '0.0.0.0', shared: true, backlog: nil, **socket_options, &block)
|
67
|
+
raise ArgumentError, "BUG: title must be a symbol" unless title && title.is_a?(Symbol)
|
68
|
+
raise ArgumentError, "BUG: port must be an integer" unless port && port.is_a?(Integer)
|
69
|
+
raise ArgumentError, "BUG: invalid protocol name" unless PROTOCOLS.include?(proto)
|
70
|
+
raise ArgumentError, "BUG: cannot create connection for UDP" unless CONNECTION_PROTOCOLS.include?(proto)
|
71
|
+
|
72
|
+
raise ArgumentError, "BUG: block not specified which handles connection" unless block_given?
|
73
|
+
raise ArgumentError, "BUG: block must have just one argument" unless block.arity == 1
|
74
|
+
|
75
|
+
if proto == :tcp || proto == :tls # default linger_timeout only for server
|
76
|
+
socket_options[:linger_timeout] ||= 0
|
77
|
+
end
|
78
|
+
|
79
|
+
socket_option_validate!(proto, **socket_options)
|
80
|
+
socket_option_setter = ->(sock){ socket_option_set(sock, **socket_options) }
|
81
|
+
|
82
|
+
case proto
|
83
|
+
when :tcp
|
84
|
+
server = server_create_for_tcp_connection(shared, bind, port, backlog, socket_option_setter, &block)
|
85
|
+
when :tls
|
86
|
+
raise ArgumentError, "BUG: certopts (certificate options) not specified for TLS" unless certopts
|
87
|
+
# server_certopts_validate!(certopts)
|
88
|
+
# sock = server_create_tls_socket(shared, bind, port)
|
89
|
+
# server = nil # ...
|
90
|
+
raise "not implemented yet"
|
91
|
+
when :unix
|
92
|
+
raise "not implemented yet"
|
93
|
+
else
|
94
|
+
raise "unknown protocol #{proto}"
|
95
|
+
end
|
96
|
+
|
97
|
+
server_attach(title, proto, port, bind, shared, server)
|
98
|
+
end
|
99
|
+
|
100
|
+
# server_create(:title, @port) do |data|
|
101
|
+
# # ...
|
102
|
+
# end
|
103
|
+
# server_create(:title, @port) do |data, conn|
|
104
|
+
# # ...
|
105
|
+
# end
|
106
|
+
# server_create(:title, @port, proto: :udp, max_bytes: 2048) do |data, sock|
|
107
|
+
# sock.remote_host
|
108
|
+
# sock.remote_port
|
109
|
+
# # ...
|
110
|
+
# end
|
111
|
+
def server_create(title, port, proto: :tcp, bind: '0.0.0.0', shared: true, socket: nil, backlog: nil, max_bytes: nil, flags: 0, **socket_options, &callback)
|
112
|
+
raise ArgumentError, "BUG: title must be a symbol" unless title && title.is_a?(Symbol)
|
113
|
+
raise ArgumentError, "BUG: port must be an integer" unless port && port.is_a?(Integer)
|
114
|
+
raise ArgumentError, "BUG: invalid protocol name" unless PROTOCOLS.include?(proto)
|
115
|
+
|
116
|
+
raise ArgumentError, "BUG: socket option is available only for udp" if socket && proto != :udp
|
117
|
+
|
118
|
+
raise ArgumentError, "BUG: block not specified which handles received data" unless block_given?
|
119
|
+
raise ArgumentError, "BUG: block must have 1 or 2 arguments" unless callback.arity == 1 || callback.arity == 2
|
120
|
+
|
121
|
+
if proto == :tcp || proto == :tls # default linger_timeout only for server
|
122
|
+
socket_options[:linger_timeout] ||= 0
|
123
|
+
end
|
124
|
+
|
125
|
+
unless socket
|
126
|
+
socket_option_validate!(proto, **socket_options)
|
127
|
+
socket_option_setter = ->(sock){ socket_option_set(sock, **socket_options) }
|
128
|
+
end
|
129
|
+
|
130
|
+
if proto != :tcp && proto != :tls && proto != :unix # options to listen/accept connections
|
131
|
+
raise ArgumentError, "BUG: backlog is available for tcp/tls" if backlog
|
132
|
+
end
|
133
|
+
if proto != :udp # UDP options
|
134
|
+
raise ArgumentError, "BUG: max_bytes is available only for udp" if max_bytes
|
135
|
+
raise ArgumentError, "BUG: flags is available only for udp" if flags != 0
|
136
|
+
end
|
137
|
+
|
138
|
+
case proto
|
139
|
+
when :tcp
|
140
|
+
server = server_create_for_tcp_connection(shared, bind, port, backlog, socket_option_setter) do |conn|
|
141
|
+
conn.data(&callback)
|
142
|
+
end
|
143
|
+
when :tls
|
144
|
+
raise "not implemented yet"
|
145
|
+
when :udp
|
146
|
+
raise ArgumentError, "BUG: max_bytes must be specified for UDP" unless max_bytes
|
147
|
+
if socket
|
148
|
+
sock = socket
|
149
|
+
close_socket = false
|
150
|
+
else
|
151
|
+
sock = server_create_udp_socket(shared, bind, port)
|
152
|
+
socket_option_setter.call(sock)
|
153
|
+
close_socket = true
|
154
|
+
end
|
155
|
+
server = EventHandler::UDPServer.new(sock, max_bytes, flags, close_socket, @log, @under_plugin_development, &callback)
|
156
|
+
when :unix
|
157
|
+
raise "not implemented yet"
|
158
|
+
else
|
159
|
+
raise "BUG: unknown protocol #{proto}"
|
160
|
+
end
|
161
|
+
|
162
|
+
server_attach(title, proto, port, bind, shared, server)
|
163
|
+
end
|
164
|
+
|
165
|
+
def server_create_tcp(title, port, **kwargs, &callback)
|
166
|
+
server_create(title, port, proto: :tcp, **kwargs, &callback)
|
167
|
+
end
|
168
|
+
|
169
|
+
def server_create_udp(title, port, **kwargs, &callback)
|
170
|
+
server_create(title, port, proto: :udp, **kwargs, &callback)
|
171
|
+
end
|
172
|
+
|
173
|
+
def server_create_tls(title, port, **kwargs, &callback)
|
174
|
+
server_create(title, port, proto: :tls, **kwargs, &callback)
|
175
|
+
end
|
176
|
+
|
177
|
+
def server_create_unix(title, port, **kwargs, &callback)
|
178
|
+
server_create(title, port, proto: :unix, **kwargs, &callback)
|
179
|
+
end
|
180
|
+
|
181
|
+
ServerInfo = Struct.new(:title, :proto, :port, :bind, :shared, :server)
|
182
|
+
|
183
|
+
def server_attach(title, proto, port, bind, shared, server)
|
184
|
+
@_servers << ServerInfo.new(title, proto, port, bind, shared, server)
|
185
|
+
event_loop_attach(server)
|
186
|
+
end
|
187
|
+
|
188
|
+
def server_create_for_tcp_connection(shared, bind, port, backlog, socket_option_setter, &block)
|
189
|
+
sock = server_create_tcp_socket(shared, bind, port)
|
190
|
+
socket_option_setter.call(sock)
|
191
|
+
close_callback = ->(conn){ @_server_mutex.synchronize{ @_server_connections.delete(conn) } }
|
192
|
+
server = Coolio::TCPServer.new(sock, nil, EventHandler::TCPServer, socket_option_setter, close_callback, @log, @under_plugin_development, block) do |conn|
|
193
|
+
@_server_mutex.synchronize do
|
194
|
+
@_server_connections << conn
|
195
|
+
end
|
196
|
+
end
|
197
|
+
server.listen(backlog) if backlog
|
198
|
+
server
|
199
|
+
end
|
200
|
+
|
201
|
+
def initialize
|
202
|
+
super
|
203
|
+
@_servers = []
|
204
|
+
@_server_connections = []
|
205
|
+
@_server_mutex = Mutex.new
|
206
|
+
end
|
207
|
+
|
208
|
+
def shutdown
|
209
|
+
@_server_connections.each do |conn|
|
210
|
+
conn.close rescue nil
|
211
|
+
end
|
212
|
+
@_server_mutex.synchronize do
|
213
|
+
@_servers.each do |si|
|
214
|
+
si.server.detach if si.server.attached?
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
super
|
219
|
+
end
|
220
|
+
|
221
|
+
def close
|
222
|
+
@_server_connections.each do |conn|
|
223
|
+
conn.close rescue nil
|
224
|
+
end
|
225
|
+
@_server_mutex.synchronize do
|
226
|
+
@_servers.each do |si|
|
227
|
+
si.server.close rescue nil
|
228
|
+
end
|
229
|
+
end
|
230
|
+
super
|
231
|
+
end
|
232
|
+
|
233
|
+
def terminate
|
234
|
+
@_servers = []
|
235
|
+
super
|
236
|
+
end
|
237
|
+
|
238
|
+
def server_certopts_validate!(certopts)
|
239
|
+
raise "not implemented yet"
|
240
|
+
end
|
241
|
+
|
242
|
+
def server_socket_manager_client
|
243
|
+
socket_manager_path = ENV['SERVERENGINE_SOCKETMANAGER_PATH']
|
244
|
+
if Fluent.windows?
|
245
|
+
socket_manager_path = socket_manager_path.to_i
|
246
|
+
end
|
247
|
+
ServerEngine::SocketManager::Client.new(socket_manager_path)
|
248
|
+
end
|
249
|
+
|
250
|
+
def server_create_tcp_socket(shared, bind, port)
|
251
|
+
sock = if shared
|
252
|
+
server_socket_manager_client.listen_tcp(bind, port)
|
253
|
+
else
|
254
|
+
TCPServer.new(bind, port) # this method call can create sockets for AF_INET6
|
255
|
+
end
|
256
|
+
# close-on-exec is set by default in Ruby 2.0 or later (, and it's unavailable on Windows)
|
257
|
+
sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK) # nonblock
|
258
|
+
sock
|
259
|
+
end
|
260
|
+
|
261
|
+
def server_create_udp_socket(shared, bind, port)
|
262
|
+
sock = if shared
|
263
|
+
server_socket_manager_client.listen_udp(bind, port)
|
264
|
+
else
|
265
|
+
family = IPAddr.new(IPSocket.getaddress(bind)).ipv4? ? ::Socket::AF_INET : ::Socket::AF_INET6
|
266
|
+
usock = UDPSocket.new(family)
|
267
|
+
usock.bind(bind, port)
|
268
|
+
usock
|
269
|
+
end
|
270
|
+
# close-on-exec is set by default in Ruby 2.0 or later (, and it's unavailable on Windows)
|
271
|
+
sock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK) # nonblock
|
272
|
+
sock
|
273
|
+
end
|
274
|
+
|
275
|
+
def server_create_tls_socket(shared, bind, port)
|
276
|
+
raise "not implemented yet"
|
277
|
+
end
|
278
|
+
|
279
|
+
class CallbackSocket
|
280
|
+
def initialize(server_type, sock, enabled_events = [], close_socket: true)
|
281
|
+
@server_type = server_type
|
282
|
+
@sock = sock
|
283
|
+
@enabled_events = enabled_events
|
284
|
+
@close_socket = close_socket
|
285
|
+
end
|
286
|
+
|
287
|
+
def remote_addr
|
288
|
+
@sock.peeraddr[3]
|
289
|
+
end
|
290
|
+
|
291
|
+
def remote_host
|
292
|
+
@sock.peeraddr[2]
|
293
|
+
end
|
294
|
+
|
295
|
+
def remote_port
|
296
|
+
@sock.peeraddr[1]
|
297
|
+
end
|
298
|
+
|
299
|
+
def send(data, flags = 0)
|
300
|
+
@sock.send(data, flags)
|
301
|
+
end
|
302
|
+
|
303
|
+
def write(data)
|
304
|
+
raise "not implemented here"
|
305
|
+
end
|
306
|
+
|
307
|
+
def close
|
308
|
+
@sock.close if @close_socket
|
309
|
+
end
|
310
|
+
|
311
|
+
def data(&callback)
|
312
|
+
on(:data, &callback)
|
313
|
+
end
|
314
|
+
|
315
|
+
def on(event, &callback)
|
316
|
+
raise "BUG: this event is disabled for #{@server_type}: #{event}" unless @enabled_events.include?(event)
|
317
|
+
case event
|
318
|
+
when :data
|
319
|
+
@sock.data(&callback)
|
320
|
+
when :write_complete
|
321
|
+
cb = ->(){ callback.call(self) }
|
322
|
+
@sock.on_write_complete(&cb)
|
323
|
+
when :close
|
324
|
+
cb = ->(){ callback.call(self) }
|
325
|
+
@sock.on_close(&cb)
|
326
|
+
else
|
327
|
+
raise "BUG: unknown event: #{event}"
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
class TCPCallbackSocket < CallbackSocket
|
333
|
+
def initialize(sock)
|
334
|
+
super("tcp", sock, [:data, :write_complete, :close])
|
335
|
+
end
|
336
|
+
|
337
|
+
def write(data)
|
338
|
+
@sock.write(data)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class UDPCallbackSocket < CallbackSocket
|
343
|
+
def initialize(sock, peeraddr, **kwargs)
|
344
|
+
super("udp", sock, [], **kwargs)
|
345
|
+
@peeraddr = peeraddr
|
346
|
+
end
|
347
|
+
|
348
|
+
def remote_addr
|
349
|
+
@peeraddr[3]
|
350
|
+
end
|
351
|
+
|
352
|
+
def remote_host
|
353
|
+
@peeraddr[2]
|
354
|
+
end
|
355
|
+
|
356
|
+
def remote_port
|
357
|
+
@peeraddr[1]
|
358
|
+
end
|
359
|
+
|
360
|
+
def write(data)
|
361
|
+
@sock.send(data, 0, @peeraddr[3], @peeraddr[1])
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
module EventHandler
|
366
|
+
class UDPServer < Coolio::IO
|
367
|
+
def initialize(sock, max_bytes, flags, close_socket, log, under_plugin_development, &callback)
|
368
|
+
raise ArgumentError, "socket must be a UDPSocket: sock = #{sock}" unless sock.is_a?(UDPSocket)
|
369
|
+
|
370
|
+
super(sock)
|
371
|
+
|
372
|
+
@sock = sock
|
373
|
+
@max_bytes = max_bytes
|
374
|
+
@flags = flags
|
375
|
+
@close_socket = close_socket
|
376
|
+
@log = log
|
377
|
+
@under_plugin_development = under_plugin_development
|
378
|
+
@callback = callback
|
379
|
+
|
380
|
+
on_readable_impl = case @callback.arity
|
381
|
+
when 1 then :on_readable_without_sock
|
382
|
+
when 2 then :on_readable_with_sock
|
383
|
+
else
|
384
|
+
raise "BUG: callback block must have 1 or 2 arguments"
|
385
|
+
end
|
386
|
+
self.define_singleton_method(:on_readable, method(on_readable_impl))
|
387
|
+
end
|
388
|
+
|
389
|
+
def on_readable_without_sock
|
390
|
+
begin
|
391
|
+
data = @sock.recv(@max_bytes, @flags)
|
392
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNRESET
|
393
|
+
return
|
394
|
+
end
|
395
|
+
@callback.call(data)
|
396
|
+
rescue => e
|
397
|
+
@log.error "unexpected error in processing UDP data", error: e
|
398
|
+
@log.error_backtrace
|
399
|
+
raise if @under_plugin_development
|
400
|
+
end
|
401
|
+
|
402
|
+
def on_readable_with_sock
|
403
|
+
begin
|
404
|
+
data, addr = @sock.recvfrom(@max_bytes)
|
405
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNRESET
|
406
|
+
return
|
407
|
+
end
|
408
|
+
@callback.call(data, UDPCallbackSocket.new(@sock, addr, close_socket: @close_socket))
|
409
|
+
rescue => e
|
410
|
+
@log.error "unexpected error in processing UDP data", error: e
|
411
|
+
@log.error_backtrace
|
412
|
+
raise if @under_plugin_development
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
class TCPServer < Coolio::TCPSocket
|
417
|
+
def initialize(sock, socket_option_setter, close_callback, log, under_plugin_development, connect_callback)
|
418
|
+
raise ArgumentError, "socket must be a TCPSocket: sock=#{sock}" unless sock.is_a?(TCPSocket)
|
419
|
+
|
420
|
+
socket_option_setter.call(sock)
|
421
|
+
|
422
|
+
@_handler_socket = sock
|
423
|
+
super(sock)
|
424
|
+
|
425
|
+
@log = log
|
426
|
+
@under_plugin_development = under_plugin_development
|
427
|
+
|
428
|
+
@connect_callback = connect_callback
|
429
|
+
@data_callback = nil
|
430
|
+
@close_callback = close_callback
|
431
|
+
|
432
|
+
@callback_connection = nil
|
433
|
+
@closing = false
|
434
|
+
|
435
|
+
@mutex = Mutex.new # to serialize #write and #close
|
436
|
+
end
|
437
|
+
|
438
|
+
def data(&callback)
|
439
|
+
raise "data callback can be registered just once, but registered twice" if self.singleton_methods.include?(:on_read)
|
440
|
+
@data_callback = callback
|
441
|
+
on_read_impl = case callback.arity
|
442
|
+
when 1 then :on_read_without_connection
|
443
|
+
when 2 then :on_read_with_connection
|
444
|
+
else
|
445
|
+
raise "BUG: callback block must have 1 or 2 arguments"
|
446
|
+
end
|
447
|
+
self.define_singleton_method(:on_read, method(on_read_impl))
|
448
|
+
end
|
449
|
+
|
450
|
+
def write(data)
|
451
|
+
@mutex.synchronize do
|
452
|
+
super
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def on_connect
|
457
|
+
@callback_connection = TCPCallbackSocket.new(self)
|
458
|
+
@connect_callback.call(@callback_connection)
|
459
|
+
unless @data_callback
|
460
|
+
raise "connection callback must call #data to set data callback"
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def on_read_without_connection(data)
|
465
|
+
@data_callback.call(data)
|
466
|
+
rescue => e
|
467
|
+
@log.error "unexpected error on reading data", host: remote_host, port: remote_port, error: e
|
468
|
+
@log.error_backtrace
|
469
|
+
close(true) rescue nil
|
470
|
+
raise if @under_plugin_development
|
471
|
+
end
|
472
|
+
|
473
|
+
def on_read_with_connection(data)
|
474
|
+
@data_callback.call(data, @callback_connection)
|
475
|
+
rescue => e
|
476
|
+
@log.error "unexpected error on reading data", host: remote_host, port: remote_port, error: e
|
477
|
+
@log.error_backtrace
|
478
|
+
close(true) rescue nil
|
479
|
+
raise if @under_plugin_development
|
480
|
+
end
|
481
|
+
|
482
|
+
def close
|
483
|
+
@mutex.synchronize do
|
484
|
+
return if @closing
|
485
|
+
@closing = true
|
486
|
+
@close_callback.call(self)
|
487
|
+
super
|
488
|
+
end
|
489
|
+
end
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|