fluentd 0.14.8 → 0.14.9
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/CONTRIBUTING.md +6 -1
- data/ChangeLog +38 -0
- data/Rakefile +21 -0
- data/example/out_exec_filter.conf +42 -0
- data/lib/fluent/agent.rb +2 -2
- data/lib/fluent/command/binlog_reader.rb +1 -1
- data/lib/fluent/command/cat.rb +5 -2
- data/lib/fluent/compat/output.rb +7 -8
- data/lib/fluent/compat/parser.rb +139 -11
- data/lib/fluent/config/configure_proxy.rb +2 -11
- data/lib/fluent/config/section.rb +7 -0
- data/lib/fluent/configurable.rb +1 -3
- 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 +4 -7
- 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 +25 -105
- data/lib/fluent/plugin/in_http.rb +68 -65
- data/lib/fluent/plugin/in_syslog.rb +29 -51
- 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 +155 -80
- data/lib/fluent/plugin/out_forward.rb +44 -47
- data/lib/fluent/plugin/out_stdout.rb +6 -21
- data/lib/fluent/plugin/output.rb +23 -17
- 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 +8 -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/extract.rb +16 -4
- data/lib/fluent/plugin_helper/formatter.rb +9 -11
- data/lib/fluent/plugin_helper/inject.rb +4 -0
- data/lib/fluent/plugin_helper/parser.rb +3 -3
- data/lib/fluent/root_agent.rb +1 -1
- data/lib/fluent/test/driver/base.rb +51 -37
- 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/test/command/test_binlog_reader.rb +5 -1
- data/test/config/test_configurable.rb +173 -0
- data/test/config/test_configure_proxy.rb +0 -43
- 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 +11 -3
- 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 +310 -327
- data/test/plugin/test_in_http.rb +310 -186
- 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 +620 -177
- data/test/plugin/test_out_forward.rb +110 -132
- 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_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_parser.rb +6 -5
- data/test/test_output.rb +24 -2
- data/test/test_plugin_classes.rb +20 -0
- data/test/test_root_agent.rb +139 -0
- data/test/test_test_drivers.rb +132 -0
- metadata +12 -4
- data/test/plugin/test_parser_base.rb +0 -32
@@ -25,8 +25,9 @@ module Fluent::Plugin
|
|
25
25
|
class SyslogInput < Input
|
26
26
|
Fluent::Plugin.register_input('syslog', self)
|
27
27
|
|
28
|
-
helpers :parser, :event_loop
|
28
|
+
helpers :parser, :compat_parameters, :event_loop
|
29
29
|
|
30
|
+
DEFAULT_PARSER = 'syslog'
|
30
31
|
SYSLOG_REGEXP = /^\<([0-9]+)\>(.*)/
|
31
32
|
|
32
33
|
FACILITY_MAP = {
|
@@ -79,16 +80,7 @@ module Fluent::Plugin
|
|
79
80
|
desc 'The prefix of the tag. The tag itself is generated by the tag prefix, facility level, and priority.'
|
80
81
|
config_param :tag, :string
|
81
82
|
desc 'The transport protocol used to receive logs.(udp, tcp)'
|
82
|
-
config_param :protocol_type,
|
83
|
-
case val.downcase
|
84
|
-
when 'tcp'
|
85
|
-
:tcp
|
86
|
-
when 'udp'
|
87
|
-
:udp
|
88
|
-
else
|
89
|
-
raise Fluent::ConfigError, "syslog input protocol type should be 'tcp' or 'udp'"
|
90
|
-
end
|
91
|
-
end
|
83
|
+
config_param :protocol_type, :enum, list: [:tcp, :udp], default: :udp
|
92
84
|
desc 'If true, add source host to event record.'
|
93
85
|
config_param :include_source_host, :bool, default: false
|
94
86
|
desc 'Specify key of source host when include_source_host is true.'
|
@@ -96,31 +88,28 @@ module Fluent::Plugin
|
|
96
88
|
config_param :blocking_timeout, :time, default: 0.5
|
97
89
|
config_param :message_length_limit, :size, default: 2048
|
98
90
|
|
91
|
+
config_section :parse do
|
92
|
+
config_set_default :@type, DEFAULT_PARSER
|
93
|
+
config_param :with_priority, :bool, default: true
|
94
|
+
end
|
95
|
+
|
99
96
|
def configure(conf)
|
97
|
+
compat_parameters_convert(conf, :parser)
|
98
|
+
|
100
99
|
super
|
101
100
|
|
102
101
|
@use_default = false
|
103
102
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
conf['with_priority'] = true
|
108
|
-
@parser = parser_create(usage: 'syslog_input', type: 'syslog', conf: conf)
|
109
|
-
@use_default = true
|
110
|
-
end
|
103
|
+
@parser = parser_create
|
104
|
+
@parser_parse_priority = @parser.respond_to?(:with_priority) && @parser.with_priority
|
105
|
+
|
111
106
|
@_event_loop_run_timeout = @blocking_timeout
|
112
107
|
end
|
113
108
|
|
114
109
|
def start
|
115
110
|
super
|
116
111
|
|
117
|
-
|
118
|
-
method(:receive_data)
|
119
|
-
else
|
120
|
-
method(:receive_data_parser)
|
121
|
-
end
|
122
|
-
|
123
|
-
@handler = listen(callback)
|
112
|
+
@handler = listen(method(:message_handler))
|
124
113
|
event_loop_attach(@handler)
|
125
114
|
end
|
126
115
|
|
@@ -132,42 +121,31 @@ module Fluent::Plugin
|
|
132
121
|
|
133
122
|
private
|
134
123
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
text = m[2]
|
143
|
-
|
144
|
-
@parser.parse(text) { |time, record|
|
145
|
-
unless time && record
|
146
|
-
log.warn "pattern not match: #{text.inspect}"
|
124
|
+
def message_handler(data, addr)
|
125
|
+
pri = nil
|
126
|
+
text = data
|
127
|
+
unless @parser_parse_priority
|
128
|
+
m = SYSLOG_REGEXP.match(data)
|
129
|
+
unless m
|
130
|
+
log.warn "invalid syslog message: #{data.dump}"
|
147
131
|
return
|
148
132
|
end
|
133
|
+
pri = m[1].to_i
|
134
|
+
text = m[2]
|
135
|
+
end
|
149
136
|
|
150
|
-
|
151
|
-
emit(pri, time, record)
|
152
|
-
}
|
153
|
-
rescue => e
|
154
|
-
log.error data.dump, error: e.to_s
|
155
|
-
log.error_backtrace
|
156
|
-
end
|
157
|
-
|
158
|
-
def receive_data(data, addr)
|
159
|
-
@parser.parse(data) { |time, record|
|
137
|
+
@parser.parse(text) do |time, record|
|
160
138
|
unless time && record
|
161
|
-
log.warn "
|
139
|
+
log.warn "failed to parse message", data: data
|
162
140
|
return
|
163
141
|
end
|
164
142
|
|
165
|
-
pri
|
143
|
+
pri ||= record.delete('pri')
|
166
144
|
record[@source_host_key] = addr[2] if @include_source_host
|
167
145
|
emit(pri, time, record)
|
168
|
-
|
146
|
+
end
|
169
147
|
rescue => e
|
170
|
-
log.error data
|
148
|
+
log.error "invalid input", data: data, error: e
|
171
149
|
log.error_backtrace
|
172
150
|
end
|
173
151
|
|
@@ -69,9 +69,7 @@ module Fluent
|
|
69
69
|
log.debug "adding store", type: type
|
70
70
|
|
71
71
|
output = Fluent::Plugin.new_output(type)
|
72
|
-
|
73
|
-
output.router = router
|
74
|
-
end
|
72
|
+
output.context_router = self.context_router
|
75
73
|
output.configure(store_conf)
|
76
74
|
@outputs << output
|
77
75
|
end
|
@@ -18,97 +18,84 @@ require 'tempfile'
|
|
18
18
|
|
19
19
|
require 'fluent/plugin/output'
|
20
20
|
require 'fluent/config/error'
|
21
|
-
require 'fluent/plugin/exec_util'
|
22
|
-
require 'fluent/mixin' # for TimeFormatter
|
23
21
|
|
24
22
|
module Fluent::Plugin
|
25
23
|
class ExecOutput < Output
|
26
24
|
Fluent::Plugin.register_output('exec', self)
|
27
25
|
|
28
|
-
helpers :compat_parameters
|
26
|
+
helpers :inject, :formatter, :compat_parameters, :child_process
|
29
27
|
|
30
|
-
desc 'The command (program) to execute. The exec plugin passes the path of a TSV file as the last
|
28
|
+
desc 'The command (program) to execute. The exec plugin passes the path of a TSV file as the last argument'
|
31
29
|
config_param :command, :string
|
32
|
-
|
33
|
-
config_param :
|
34
|
-
|
30
|
+
|
31
|
+
config_param :command_timeout, :time, default: 270 # 4min 30sec
|
32
|
+
|
33
|
+
config_section :format do
|
34
|
+
config_set_default :@type, 'tsv'
|
35
35
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
desc 'The format for event time used when the time_key parameter is specified. The default is UNIX time (integer).'
|
41
|
-
config_param :time_format, :string, default: nil
|
42
|
-
desc "The format used to map the incoming events to the program input. (#{Fluent::ExecUtil::SUPPORTED_FORMAT.keys.join(',')})"
|
43
|
-
config_param :format, default: :tsv, skip_accessor: true do |val|
|
44
|
-
f = Fluent::ExecUtil::SUPPORTED_FORMAT[val]
|
45
|
-
raise Fluent::ConfigError, "Unsupported format '#{val}'" unless f
|
46
|
-
f
|
36
|
+
|
37
|
+
config_section :inject do
|
38
|
+
config_set_default :time_type, :string
|
39
|
+
config_set_default :localtime, false
|
47
40
|
end
|
48
|
-
config_param :localtime, :bool, default: false
|
49
|
-
config_param :timezone, :string, default: nil
|
50
41
|
|
51
|
-
|
52
|
-
|
42
|
+
config_section :buffer do
|
43
|
+
config_set_default :delayed_commit_timeout, 300 # 5 min
|
53
44
|
end
|
54
45
|
|
55
|
-
|
56
|
-
compat_parameters_convert(conf, :buffer, default_chunk_key: 'time')
|
46
|
+
attr_reader :formatter # for tests
|
57
47
|
|
48
|
+
def configure(conf)
|
49
|
+
compat_parameters_convert(conf, :inject, :formatter, :buffer, default_chunk_key: 'time')
|
58
50
|
super
|
59
|
-
|
60
|
-
@formatter = case @format
|
61
|
-
when :tsv
|
62
|
-
if @keys.empty?
|
63
|
-
raise Fluent::ConfigError, "keys option is required on exec output for tsv format"
|
64
|
-
end
|
65
|
-
Fluent::ExecUtil::TSVFormatter.new(@keys)
|
66
|
-
when :json
|
67
|
-
Fluent::ExecUtil::JSONFormatter.new
|
68
|
-
when :msgpack
|
69
|
-
Fluent::ExecUtil::MessagePackFormatter.new
|
70
|
-
end
|
71
|
-
|
72
|
-
if @time_key
|
73
|
-
if @time_format
|
74
|
-
tf = Fluent::TimeFormatter.new(@time_format, @localtime, @timezone)
|
75
|
-
@time_format_proc = tf.method(:format)
|
76
|
-
else
|
77
|
-
@time_format_proc = Proc.new { |time| time.to_s }
|
78
|
-
end
|
79
|
-
end
|
51
|
+
@formatter = formatter_create
|
80
52
|
end
|
81
53
|
|
82
|
-
|
83
|
-
out = ''
|
84
|
-
if @time_key
|
85
|
-
record[@time_key] = @time_format_proc.call(time)
|
86
|
-
end
|
87
|
-
if @tag_key
|
88
|
-
record[@tag_key] = tag
|
89
|
-
end
|
90
|
-
@formatter.call(record, out)
|
91
|
-
out
|
92
|
-
end
|
54
|
+
NEWLINE = "\n"
|
93
55
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
56
|
+
def format(tag, time, record)
|
57
|
+
record = inject_values_to_record(tag, time, record)
|
58
|
+
if @formatter.formatter_type == :text_per_line
|
59
|
+
@formatter.format(tag, time, record).chomp + NEWLINE
|
97
60
|
else
|
98
|
-
|
99
|
-
tmpfile.binmode
|
100
|
-
chunk.write_to(tmpfile)
|
101
|
-
tmpfile.close
|
102
|
-
prog = "#{@command} #{tmpfile.path}"
|
61
|
+
@formatter.format(tag, time, record)
|
103
62
|
end
|
63
|
+
end
|
104
64
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
65
|
+
def try_write(chunk)
|
66
|
+
tmpfile = nil
|
67
|
+
prog = if chunk.respond_to?(:path)
|
68
|
+
"#{@command} #{chunk.path}"
|
69
|
+
else
|
70
|
+
tmpfile = Tempfile.new("fluent-plugin-out-exec-")
|
71
|
+
tmpfile.binmode
|
72
|
+
chunk.write_to(tmpfile)
|
73
|
+
tmpfile.close
|
74
|
+
"#{@command} #{tmpfile.path}"
|
75
|
+
end
|
76
|
+
chunk_id = chunk.unique_id
|
77
|
+
callback = ->(status){
|
78
|
+
begin
|
79
|
+
if tmpfile
|
80
|
+
tmpfile.delete rescue nil
|
81
|
+
end
|
82
|
+
if status && status.success?
|
83
|
+
commit_write(chunk_id)
|
84
|
+
elsif status
|
85
|
+
# #rollback_write will be done automatically if it isn't called at here.
|
86
|
+
# But it's after command_timeout, and this timeout should be longer than users expectation.
|
87
|
+
# So here, this plugin calls it explicitly.
|
88
|
+
rollback_write(chunk_id)
|
89
|
+
log.warn "command exits with error code", prog: prog, status: status.exitstatus, signal: status.termsig
|
90
|
+
else
|
91
|
+
rollback_write(chunk_id)
|
92
|
+
log.warn "command unexpectedly exits without exit status", prog: prog
|
93
|
+
end
|
94
|
+
rescue => e
|
95
|
+
log.error "unexpected error in child process callback", error: e
|
96
|
+
end
|
97
|
+
}
|
98
|
+
child_process_execute(:out_exec_process, prog, stderr: :connect, immediate: true, parallel: true, mode: [], wait_timeout: @command_timeout, on_exit_callback: callback)
|
112
99
|
end
|
113
100
|
end
|
114
101
|
end
|
@@ -13,148 +13,142 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
17
|
-
require 'yajl'
|
18
|
-
|
19
|
-
require 'fluent/output'
|
16
|
+
require 'fluent/plugin/output'
|
20
17
|
require 'fluent/env'
|
21
|
-
require 'fluent/time'
|
22
|
-
require 'fluent/timezone'
|
23
|
-
require 'fluent/plugin/exec_util'
|
24
18
|
require 'fluent/config/error'
|
25
19
|
|
26
|
-
|
27
|
-
class ExecFilterOutput < BufferedOutput
|
28
|
-
Plugin.register_output('exec_filter', self)
|
20
|
+
require 'yajl'
|
29
21
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
module Fluent::Plugin
|
23
|
+
class ExecFilterOutput < Output
|
24
|
+
Fluent::Plugin.register_output('exec_filter', self)
|
25
|
+
|
26
|
+
helpers :compat_parameters, :inject, :formatter, :parser, :extract, :child_process, :event_emitter
|
34
27
|
|
35
28
|
desc 'The command (program) to execute.'
|
36
29
|
config_param :command, :string
|
37
30
|
|
38
|
-
config_param :remove_prefix, :string, default: nil
|
39
|
-
config_param :add_prefix, :string, default: nil
|
31
|
+
config_param :remove_prefix, :string, default: nil, deprecated: "use @label instead for event routing"
|
32
|
+
config_param :add_prefix, :string, default: nil, deprecated: "use @label instead for event routing"
|
33
|
+
|
34
|
+
config_section :inject do
|
35
|
+
config_set_default :time_type, :unixtime
|
36
|
+
end
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
raise ConfigError, "Unsupported in_format '#{val}'" unless f
|
45
|
-
f
|
38
|
+
config_section :format do
|
39
|
+
config_set_default :@type, 'tsv'
|
40
|
+
config_set_default :localtime, true
|
46
41
|
end
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
|
43
|
+
config_section :parse do
|
44
|
+
config_set_default :@type, 'tsv'
|
45
|
+
config_set_default :time_key, nil
|
46
|
+
config_set_default :time_format, nil
|
47
|
+
config_set_default :localtime, true
|
48
|
+
config_set_default :estimate_current_event, false
|
50
49
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
config_param :in_time_key, default: nil
|
55
|
-
desc 'The format for event time used when the in_time_key parameter is specified.(Defauls is UNIX time)'
|
56
|
-
config_param :in_time_format, default: nil
|
57
|
-
|
58
|
-
desc "The format used to process the program output.(#{Fluent::ExecUtil::SUPPORTED_FORMAT.keys.join(',')})"
|
59
|
-
config_param :out_format, default: :tsv do |val|
|
60
|
-
f = Fluent::ExecUtil::SUPPORTED_FORMAT[val]
|
61
|
-
raise ConfigError, "Unsupported out_format '#{val}'" unless f
|
62
|
-
f
|
50
|
+
|
51
|
+
config_section :extract do
|
52
|
+
config_set_default :time_type, :float
|
63
53
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
54
|
+
|
55
|
+
config_section :buffer do
|
56
|
+
config_set_default :flush_mode, :interval
|
57
|
+
config_set_default :flush_interval, 1
|
67
58
|
end
|
68
|
-
desc 'The name of the key to use as the event tag.'
|
69
|
-
config_param :out_tag_key, default: nil
|
70
|
-
desc 'The name of the key to use as the event time.'
|
71
|
-
config_param :out_time_key, default: nil
|
72
|
-
desc 'The format for event time used when the in_time_key parameter is specified.(Defauls is UNIX time)'
|
73
|
-
config_param :out_time_format, default: nil
|
74
59
|
|
75
60
|
config_param :tag, :string, default: nil
|
76
61
|
|
77
|
-
config_param :
|
78
|
-
config_param :
|
62
|
+
config_param :tag_key, :string, default: nil, deprecated: "use 'tag_key' in <inject>/<extract> instead"
|
63
|
+
config_param :time_key, :string, default: nil, deprecated: "use 'time_key' in <inject>/<extract> instead"
|
64
|
+
config_param :time_format, :string, default: nil, deprecated: "use 'time_format' in <inject>/<extract> instead"
|
65
|
+
|
66
|
+
desc 'The default block size to read if parser requires partial read.'
|
67
|
+
config_param :read_block_size, :size, default: 10240 # 10k
|
79
68
|
|
80
|
-
desc 'If true, use localtime with in_time_format.'
|
81
|
-
config_param :localtime, :bool, default: true
|
82
|
-
desc 'If true, use timezone with in_time_format.'
|
83
|
-
config_param :timezone, :string, default: nil
|
84
69
|
desc 'The number of spawned process for command.'
|
85
70
|
config_param :num_children, :integer, default: 1
|
86
71
|
|
87
|
-
desc 'Respawn command when command exit.'
|
72
|
+
desc 'Respawn command when command exit. ["none", "inf" or positive integer for times to respawn (defaut: none)]'
|
88
73
|
# nil, 'none' or 0: no respawn, 'inf' or -1: infinite times, positive integer: try to respawn specified times only
|
89
74
|
config_param :child_respawn, :string, default: nil
|
90
75
|
|
91
76
|
# 0: output logs for all of messages to emit
|
92
77
|
config_param :suppress_error_log_interval, :time, default: 0
|
93
78
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
79
|
+
attr_reader :formatter, :parser # for tests
|
80
|
+
|
81
|
+
KEYS_FOR_IN_AND_OUT = {
|
82
|
+
'tag_key' => ['in_tag_key', 'out_tag_key'],
|
83
|
+
'time_key' => ['in_time_key', 'out_time_key'],
|
84
|
+
'time_format' => ['in_time_format', 'out_time_format'],
|
85
|
+
}
|
86
|
+
COMPAT_INJECT_PARAMS = {
|
87
|
+
'in_tag_key' => 'tag_key',
|
88
|
+
'in_time_key' => 'time_key',
|
89
|
+
'in_time_format' => 'time_format',
|
90
|
+
}
|
91
|
+
COMPAT_FORMAT_PARAMS = {
|
92
|
+
'in_format' => '@type',
|
93
|
+
'in_keys' => 'keys',
|
94
|
+
}
|
95
|
+
COMPAT_PARSE_PARAMS = {
|
96
|
+
'out_format' => '@type',
|
97
|
+
'out_keys' => 'keys',
|
98
|
+
}
|
99
|
+
COMPAT_EXTRACT_PARAMS = {
|
100
|
+
'out_tag_key' => 'tag_key',
|
101
|
+
'out_time_key' => 'time_key',
|
102
|
+
'out_time_format' => 'time_format',
|
103
|
+
}
|
104
|
+
|
105
|
+
def exec_filter_compat_parameters_copy_to_subsection!(conf, subsection_name, params)
|
106
|
+
return unless conf.elements(subsection_name).empty?
|
107
|
+
return unless params.keys.any?{|k| conf.has_key?(k) }
|
108
|
+
hash = {}
|
109
|
+
params.each_pair do |compat, current|
|
110
|
+
hash[current] = conf[compat] if conf.has_key?(compat)
|
107
111
|
end
|
112
|
+
conf.elements << Fluent::Config::Element.new(subsection_name, '', hash, [])
|
113
|
+
end
|
108
114
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
115
|
+
def exec_filter_compat_parameters_convert!(conf)
|
116
|
+
KEYS_FOR_IN_AND_OUT.each_pair do |inout, keys|
|
117
|
+
if conf.has_key?(inout)
|
118
|
+
keys.each do |k|
|
119
|
+
conf[k] = conf[inout]
|
120
|
+
end
|
121
|
+
end
|
113
122
|
end
|
123
|
+
exec_filter_compat_parameters_copy_to_subsection!(conf, 'inject', COMPAT_INJECT_PARAMS)
|
124
|
+
exec_filter_compat_parameters_copy_to_subsection!(conf, 'format', COMPAT_FORMAT_PARAMS)
|
125
|
+
exec_filter_compat_parameters_copy_to_subsection!(conf, 'parse', COMPAT_PARSE_PARAMS)
|
126
|
+
exec_filter_compat_parameters_copy_to_subsection!(conf, 'extract', COMPAT_EXTRACT_PARAMS)
|
127
|
+
end
|
114
128
|
|
115
|
-
|
129
|
+
def configure(conf)
|
130
|
+
exec_filter_compat_parameters_convert!(conf)
|
131
|
+
compat_parameters_convert(conf, :buffer)
|
116
132
|
|
117
|
-
if conf
|
118
|
-
|
119
|
-
|
120
|
-
|
133
|
+
if inject_section = conf.elements('inject').first
|
134
|
+
if inject_section.has_key?('time_format')
|
135
|
+
inject_section['time_type'] ||= 'string'
|
136
|
+
end
|
121
137
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
138
|
+
if extract_section = conf.elements('extract').first
|
139
|
+
if extract_section.has_key?('time_format')
|
140
|
+
extract_section['time_type'] ||= 'string'
|
141
|
+
end
|
126
142
|
end
|
127
143
|
|
128
|
-
|
129
|
-
raise ConfigError, "'tag' or 'out_tag_key' option is required on exec_filter output"
|
130
|
-
end
|
144
|
+
super
|
131
145
|
|
132
|
-
if
|
133
|
-
|
134
|
-
tf = TimeFormatter.new(f, @localtime, @timezone)
|
135
|
-
@time_format_proc = tf.method(:format)
|
136
|
-
else
|
137
|
-
@time_format_proc = Proc.new {|time| time.to_s }
|
138
|
-
end
|
139
|
-
elsif @in_time_format
|
140
|
-
log.warn "in_time_format effects nothing when in_time_key is not specified: #{conf}"
|
146
|
+
if !@tag && (!@extract_config || !@extract_config.tag_key)
|
147
|
+
raise Fluent::ConfigError, "'tag' or '<extract> tag_key </extract>' option is required on exec_filter output"
|
141
148
|
end
|
142
149
|
|
143
|
-
|
144
|
-
|
145
|
-
@time_parse_proc =
|
146
|
-
begin
|
147
|
-
strptime = Strptime.new(f)
|
148
|
-
Proc.new { |str| Fluent::EventTime.from_time(strptime.exec(str)) }
|
149
|
-
rescue
|
150
|
-
Proc.new {|str| Fluent::EventTime.from_time(Time.strptime(str, f)) }
|
151
|
-
end
|
152
|
-
else
|
153
|
-
@time_parse_proc = Proc.new {|str| Fluent::EventTime.from_time(Time.at(str.to_f)) }
|
154
|
-
end
|
155
|
-
elsif @out_time_format
|
156
|
-
log.warn "out_time_format effects nothing when out_time_key is not specified: #{conf}"
|
157
|
-
end
|
150
|
+
@formatter = formatter_create
|
151
|
+
@parser = parser_create
|
158
152
|
|
159
153
|
if @remove_prefix
|
160
154
|
@removed_prefix_string = @remove_prefix + '.'
|
@@ -164,30 +158,6 @@ module Fluent
|
|
164
158
|
@added_prefix_string = @add_prefix + '.'
|
165
159
|
end
|
166
160
|
|
167
|
-
case @in_format
|
168
|
-
when :tsv
|
169
|
-
if @in_keys.empty?
|
170
|
-
raise ConfigError, "in_keys option is required on exec_filter output for tsv in_format"
|
171
|
-
end
|
172
|
-
@formatter = Fluent::ExecUtil::TSVFormatter.new(@in_keys)
|
173
|
-
when :json
|
174
|
-
@formatter = Fluent::ExecUtil::JSONFormatter.new
|
175
|
-
when :msgpack
|
176
|
-
@formatter = Fluent::ExecUtil::MessagePackFormatter.new
|
177
|
-
end
|
178
|
-
|
179
|
-
case @out_format
|
180
|
-
when :tsv
|
181
|
-
if @out_keys.empty?
|
182
|
-
raise ConfigError, "out_keys option is required on exec_filter output for tsv in_format"
|
183
|
-
end
|
184
|
-
@parser = Fluent::ExecUtil::TSVParser.new(@out_keys, method(:on_message))
|
185
|
-
when :json
|
186
|
-
@parser = Fluent::ExecUtil::JSONParser.new(method(:on_message))
|
187
|
-
when :msgpack
|
188
|
-
@parser = Fluent::ExecUtil::MessagePackParser.new(method(:on_message))
|
189
|
-
end
|
190
|
-
|
191
161
|
@respawns = if @child_respawn.nil? or @child_respawn == 'none' or @child_respawn == '0'
|
192
162
|
0
|
193
163
|
elsif @child_respawn == 'inf' or @child_respawn == '-1'
|
@@ -202,192 +172,142 @@ module Fluent
|
|
202
172
|
@next_log_time = Time.now.to_i
|
203
173
|
end
|
204
174
|
|
175
|
+
ExecutedProcess = Struct.new(:mutex, :pid, :respawns, :readio, :writeio)
|
176
|
+
|
205
177
|
def start
|
206
178
|
super
|
207
179
|
|
180
|
+
@children_mutex = Mutex.new
|
208
181
|
@children = []
|
209
182
|
@rr = 0
|
210
|
-
begin
|
211
|
-
@num_children.times do
|
212
|
-
c = ChildProcess.new(@parser, @respawns, log)
|
213
|
-
c.start(@command)
|
214
|
-
@children << c
|
215
|
-
end
|
216
|
-
rescue
|
217
|
-
shutdown
|
218
|
-
raise
|
219
|
-
end
|
220
|
-
end
|
221
183
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
c.shutdown
|
235
|
-
true
|
184
|
+
exit_callback = ->(status){
|
185
|
+
c = @children.select{|child| child.pid == status.pid }.first
|
186
|
+
if c
|
187
|
+
unless self.stopped?
|
188
|
+
log.warn "child process exits with error code", code: status.to_i, status: status.exitstatus, signal: status.termsig
|
189
|
+
end
|
190
|
+
c.mutex.synchronize do
|
191
|
+
(c.writeio && c.writeio.close) rescue nil
|
192
|
+
(c.readio && c.readio.close) rescue nil
|
193
|
+
c.pid = c.readio = c.writeio = nil
|
194
|
+
end
|
195
|
+
end
|
236
196
|
}
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
197
|
+
child_process_callback = ->(index, readio, writeio){
|
198
|
+
pid = child_process_id
|
199
|
+
c = @children[index]
|
200
|
+
writeio.sync = true
|
201
|
+
c.mutex.synchronize do
|
202
|
+
c.pid = pid
|
203
|
+
c.respawns = @respawns
|
204
|
+
c.readio = readio
|
205
|
+
c.writeio = writeio
|
245
206
|
end
|
246
|
-
end
|
247
207
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end
|
254
|
-
if @in_tag_key
|
255
|
-
record[@in_tag_key] = tag
|
208
|
+
run(readio)
|
209
|
+
}
|
210
|
+
execute_child_process = ->(index){
|
211
|
+
child_process_execute("out_exec_filter_child#{index}".to_sym, @command, on_exit_callback: exit_callback) do |readio, writeio|
|
212
|
+
child_process_callback.call(index, readio, writeio)
|
256
213
|
end
|
257
|
-
@formatter.call(record, out)
|
258
214
|
}
|
259
215
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
r = @rr = (@rr + 1) % @children.length
|
265
|
-
@children[r].write chunk
|
266
|
-
end
|
267
|
-
|
268
|
-
class ChildProcess
|
269
|
-
attr_accessor :finished
|
270
|
-
|
271
|
-
def initialize(parser, respawns=0, log = $log)
|
272
|
-
@pid = nil
|
273
|
-
@thread = nil
|
274
|
-
@parser = parser
|
275
|
-
@respawns = respawns
|
276
|
-
@mutex = Mutex.new
|
277
|
-
@finished = nil
|
278
|
-
@log = log
|
279
|
-
end
|
280
|
-
|
281
|
-
def start(command)
|
282
|
-
@command = command
|
283
|
-
@mutex.synchronize do
|
284
|
-
@io = IO.popen(command, "r+")
|
285
|
-
@pid = @io.pid
|
286
|
-
@io.sync = true
|
287
|
-
@thread = Thread.new(&method(:run))
|
216
|
+
@children_mutex.synchronize do
|
217
|
+
@num_children.times do |i|
|
218
|
+
@children << ExecutedProcess.new(Mutex.new, nil, 0, nil, nil)
|
219
|
+
execute_child_process.call(i)
|
288
220
|
end
|
289
|
-
@finished = false
|
290
221
|
end
|
291
222
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
# ignore if successfully killed by :TERM
|
223
|
+
if @respawns != 0
|
224
|
+
thread_create(:out_exec_filter_respawn_monitor) do
|
225
|
+
while thread_current_running?
|
226
|
+
@children.each_with_index do |c, i|
|
227
|
+
if c.mutex && c.mutex.synchronize{ c.pid.nil? && c.respawns != 0 }
|
228
|
+
respawns = c.mutex.synchronize do
|
229
|
+
c.respawns -= 1 if c.respawns > 0
|
230
|
+
c.respawns
|
231
|
+
end
|
232
|
+
log.info "respawning child process", num: i, respawn_counter: respawns
|
233
|
+
execute_child_process.call(i)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
sleep 0.2
|
237
|
+
end
|
308
238
|
end
|
309
|
-
@thread.join
|
310
239
|
end
|
240
|
+
end
|
311
241
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
end
|
317
|
-
end
|
242
|
+
def terminate
|
243
|
+
@children = []
|
244
|
+
super
|
245
|
+
end
|
318
246
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
# Broken pipe (child process unexpectedly exited)
|
324
|
-
@log.warn "exec_filter Broken pipe, child process maybe exited.", command: @command
|
325
|
-
if try_respawn
|
326
|
-
retry # retry chunk#write_to with child respawned
|
327
|
-
else
|
328
|
-
raise e # to retry #write with other ChildProcess instance (when num_children > 1)
|
329
|
-
end
|
247
|
+
def tag_remove_prefix(tag)
|
248
|
+
if @remove_prefix
|
249
|
+
if (tag[0, @removed_length] == @removed_prefix_string and tag.length > @removed_length) or tag == @removed_prefix_string
|
250
|
+
tag = tag[@removed_length..-1] || ''
|
330
251
|
end
|
331
252
|
end
|
253
|
+
tag
|
254
|
+
end
|
332
255
|
|
333
|
-
|
334
|
-
return false if @respawns == 0
|
335
|
-
@mutex.synchronize do
|
336
|
-
return false if @respawns == 0
|
337
|
-
|
338
|
-
kill_child(5) # TODO wait time
|
339
|
-
|
340
|
-
@io = IO.popen(@command, "r+")
|
341
|
-
@pid = @io.pid
|
342
|
-
@io.sync = true
|
343
|
-
@thread = Thread.new(&method(:run))
|
256
|
+
NEWLINE = "\n"
|
344
257
|
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
258
|
+
def format(tag, time, record)
|
259
|
+
tag = tag_remove_prefix(tag)
|
260
|
+
record = inject_values_to_record(tag, time, record)
|
261
|
+
if @formatter.formatter_type == :text_per_line
|
262
|
+
@formatter.format(tag, time, record).chomp + NEWLINE
|
263
|
+
else
|
264
|
+
@formatter.format(tag, time, record)
|
349
265
|
end
|
266
|
+
end
|
350
267
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
@
|
356
|
-
|
357
|
-
|
358
|
-
unless @finished
|
359
|
-
@log.error "exec_filter process unexpectedly exited.", command: @command, ecode: stat.to_i
|
360
|
-
unless @respawns == 0
|
361
|
-
@log.warn "exec_filter child process will respawn for next input data (respawns #{@respawns})."
|
362
|
-
end
|
268
|
+
def write(chunk)
|
269
|
+
try_times = 0
|
270
|
+
while true
|
271
|
+
r = @rr = (@rr + 1) % @children.length
|
272
|
+
if @children[r].pid && writeio = @children[r].writeio
|
273
|
+
chunk.write_to(writeio)
|
274
|
+
break
|
363
275
|
end
|
276
|
+
try_times += 1
|
277
|
+
raise "no healthy child processes exist" if try_times >= @children.length
|
364
278
|
end
|
365
279
|
end
|
366
280
|
|
367
|
-
def
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
end
|
281
|
+
def run(io)
|
282
|
+
case
|
283
|
+
when @parser.implement?(:parse_io)
|
284
|
+
@parser.parse_io(io, &method(:on_record))
|
285
|
+
when @parser.implement?(:parse_partial_data)
|
286
|
+
until io.eof?
|
287
|
+
@parser.parse_partial_data(io.readpartial(@read_block_size), &method(:on_record))
|
288
|
+
end
|
289
|
+
when @parser.parser_type == :text_per_line
|
290
|
+
io.each_line do |line|
|
291
|
+
@parser.parse(line.chomp, &method(:on_record))
|
292
|
+
end
|
380
293
|
else
|
381
|
-
|
294
|
+
@parser.parse(io.read, &method(:on_record))
|
382
295
|
end
|
296
|
+
end
|
383
297
|
|
298
|
+
def on_record(time, record)
|
299
|
+
tag = extract_tag_from_record(record)
|
300
|
+
tag = @added_prefix_string + tag if tag && @add_prefix
|
301
|
+
tag ||= @tag
|
302
|
+
time ||= extract_time_from_record(record) || Fluent::EventTime.now
|
384
303
|
router.emit(tag, time, record)
|
385
|
-
rescue
|
304
|
+
rescue => e
|
386
305
|
if @suppress_error_log_interval == 0 || Time.now.to_i > @next_log_time
|
387
|
-
log.error "exec_filter failed to emit",
|
388
|
-
log.
|
306
|
+
log.error "exec_filter failed to emit", record: Yajl.dump(record), error: e
|
307
|
+
log.error_backtrace e.backtrace
|
389
308
|
@next_log_time = Time.now.to_i + @suppress_error_log_interval
|
390
309
|
end
|
310
|
+
router.emit_error_event(tag, time, record, e) if tag && time && record
|
391
311
|
end
|
392
312
|
end
|
393
313
|
end
|