fluentd 0.14.7-x64-mingw32 → 0.14.10-x64-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.

Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +2 -0
  4. data/CONTRIBUTING.md +6 -1
  5. data/ChangeLog +95 -0
  6. data/Rakefile +21 -0
  7. data/appveyor.yml +1 -0
  8. data/code-of-conduct.md +3 -0
  9. data/example/out_exec_filter.conf +42 -0
  10. data/fluentd.gemspec +1 -1
  11. data/lib/fluent/agent.rb +2 -2
  12. data/lib/fluent/command/binlog_reader.rb +1 -1
  13. data/lib/fluent/command/cat.rb +15 -4
  14. data/lib/fluent/compat/output.rb +14 -9
  15. data/lib/fluent/compat/parser.rb +141 -11
  16. data/lib/fluent/config/configure_proxy.rb +2 -11
  17. data/lib/fluent/config/section.rb +8 -1
  18. data/lib/fluent/configurable.rb +1 -3
  19. data/lib/fluent/env.rb +1 -1
  20. data/lib/fluent/log.rb +1 -1
  21. data/lib/fluent/plugin/base.rb +17 -0
  22. data/lib/fluent/plugin/filter_parser.rb +108 -0
  23. data/lib/fluent/plugin/filter_record_transformer.rb +14 -35
  24. data/lib/fluent/plugin/filter_stdout.rb +1 -1
  25. data/lib/fluent/plugin/formatter.rb +5 -0
  26. data/lib/fluent/plugin/formatter_msgpack.rb +4 -0
  27. data/lib/fluent/plugin/formatter_stdout.rb +3 -2
  28. data/lib/fluent/plugin/formatter_tsv.rb +34 -0
  29. data/lib/fluent/plugin/in_exec.rb +48 -93
  30. data/lib/fluent/plugin/in_forward.rb +66 -265
  31. data/lib/fluent/plugin/in_http.rb +68 -65
  32. data/lib/fluent/plugin/in_monitor_agent.rb +8 -4
  33. data/lib/fluent/plugin/in_syslog.rb +42 -58
  34. data/lib/fluent/plugin/in_tail.rb +29 -14
  35. data/lib/fluent/plugin/in_tcp.rb +54 -14
  36. data/lib/fluent/plugin/in_udp.rb +49 -13
  37. data/lib/fluent/plugin/multi_output.rb +1 -3
  38. data/lib/fluent/plugin/out_exec.rb +58 -71
  39. data/lib/fluent/plugin/out_exec_filter.rb +199 -279
  40. data/lib/fluent/plugin/out_file.rb +172 -81
  41. data/lib/fluent/plugin/out_forward.rb +229 -206
  42. data/lib/fluent/plugin/out_stdout.rb +6 -21
  43. data/lib/fluent/plugin/output.rb +90 -59
  44. data/lib/fluent/plugin/parser.rb +121 -61
  45. data/lib/fluent/plugin/parser_csv.rb +9 -3
  46. data/lib/fluent/plugin/parser_json.rb +37 -35
  47. data/lib/fluent/plugin/parser_ltsv.rb +11 -19
  48. data/lib/fluent/plugin/parser_msgpack.rb +50 -0
  49. data/lib/fluent/plugin/parser_regexp.rb +15 -42
  50. data/lib/fluent/plugin/parser_tsv.rb +8 -3
  51. data/lib/fluent/plugin_helper.rb +10 -1
  52. data/lib/fluent/plugin_helper/child_process.rb +139 -73
  53. data/lib/fluent/plugin_helper/compat_parameters.rb +93 -4
  54. data/lib/fluent/plugin_helper/event_emitter.rb +14 -1
  55. data/lib/fluent/plugin_helper/event_loop.rb +24 -6
  56. data/lib/fluent/plugin_helper/extract.rb +16 -4
  57. data/lib/fluent/plugin_helper/formatter.rb +9 -11
  58. data/lib/fluent/plugin_helper/inject.rb +16 -1
  59. data/lib/fluent/plugin_helper/parser.rb +3 -3
  60. data/lib/fluent/plugin_helper/server.rb +494 -0
  61. data/lib/fluent/plugin_helper/socket.rb +101 -0
  62. data/lib/fluent/plugin_helper/socket_option.rb +84 -0
  63. data/lib/fluent/plugin_helper/timer.rb +1 -0
  64. data/lib/fluent/root_agent.rb +1 -1
  65. data/lib/fluent/test/driver/base.rb +95 -49
  66. data/lib/fluent/test/driver/base_owner.rb +18 -8
  67. data/lib/fluent/test/driver/multi_output.rb +2 -1
  68. data/lib/fluent/test/driver/output.rb +29 -6
  69. data/lib/fluent/test/helpers.rb +3 -1
  70. data/lib/fluent/test/log.rb +4 -0
  71. data/lib/fluent/test/startup_shutdown.rb +13 -0
  72. data/lib/fluent/time.rb +14 -8
  73. data/lib/fluent/version.rb +1 -1
  74. data/lib/fluent/winsvc.rb +1 -1
  75. data/test/command/test_binlog_reader.rb +5 -1
  76. data/test/compat/test_parser.rb +10 -0
  77. data/test/config/test_configurable.rb +193 -0
  78. data/test/config/test_configure_proxy.rb +0 -43
  79. data/test/helper.rb +36 -1
  80. data/test/plugin/test_base.rb +16 -0
  81. data/test/plugin/test_filter_parser.rb +665 -0
  82. data/test/plugin/test_filter_record_transformer.rb +36 -100
  83. data/test/plugin/test_filter_stdout.rb +18 -27
  84. data/test/plugin/test_in_dummy.rb +1 -1
  85. data/test/plugin/test_in_exec.rb +206 -94
  86. data/test/plugin/test_in_forward.rb +268 -347
  87. data/test/plugin/test_in_http.rb +310 -186
  88. data/test/plugin/test_in_monitor_agent.rb +65 -35
  89. data/test/plugin/test_in_syslog.rb +39 -3
  90. data/test/plugin/test_in_tcp.rb +78 -62
  91. data/test/plugin/test_in_udp.rb +101 -80
  92. data/test/plugin/test_out_exec.rb +223 -68
  93. data/test/plugin/test_out_exec_filter.rb +520 -169
  94. data/test/plugin/test_out_file.rb +637 -177
  95. data/test/plugin/test_out_forward.rb +242 -234
  96. data/test/plugin/test_out_null.rb +1 -1
  97. data/test/plugin/test_out_secondary_file.rb +4 -2
  98. data/test/plugin/test_out_stdout.rb +14 -35
  99. data/test/plugin/test_output_as_buffered.rb +60 -2
  100. data/test/plugin/test_parser.rb +359 -0
  101. data/test/plugin/test_parser_csv.rb +1 -2
  102. data/test/plugin/test_parser_json.rb +3 -4
  103. data/test/plugin/test_parser_labeled_tsv.rb +1 -2
  104. data/test/plugin/test_parser_none.rb +1 -2
  105. data/test/plugin/test_parser_regexp.rb +8 -4
  106. data/test/plugin/test_parser_tsv.rb +4 -3
  107. data/test/plugin_helper/test_child_process.rb +184 -0
  108. data/test/plugin_helper/test_compat_parameters.rb +88 -1
  109. data/test/plugin_helper/test_extract.rb +0 -1
  110. data/test/plugin_helper/test_formatter.rb +5 -2
  111. data/test/plugin_helper/test_inject.rb +21 -0
  112. data/test/plugin_helper/test_parser.rb +6 -5
  113. data/test/plugin_helper/test_server.rb +905 -0
  114. data/test/test_event_time.rb +3 -1
  115. data/test/test_output.rb +53 -2
  116. data/test/test_plugin_classes.rb +20 -0
  117. data/test/test_root_agent.rb +139 -0
  118. data/test/test_test_drivers.rb +135 -0
  119. metadata +28 -8
  120. data/test/plugin/test_parser_base.rb +0 -32
@@ -14,28 +14,68 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- require 'cool.io'
17
+ require 'fluent/plugin/input'
18
18
 
19
- require 'fluent/plugin/socket_util'
19
+ module Fluent::Plugin
20
+ class TcpInput < Input
21
+ Fluent::Plugin.register_input('tcp', self)
20
22
 
21
- module Fluent
22
- class TcpInput < SocketUtil::BaseInput
23
- Plugin.register_input('tcp', self)
23
+ helpers :server, :parser, :extract, :compat_parameters
24
+
25
+ desc 'Tag of output events.'
26
+ config_param :tag, :string
27
+ desc 'The port to listen to.'
28
+ config_param :port, :integer, default: 5170
29
+ desc 'The bind address to listen to.'
30
+ config_param :bind, :string, default: '0.0.0.0'
31
+
32
+ desc "The field name of the client's hostname."
33
+ config_param :source_host_key, :string, default: nil, deprecated: "use source_hostname_key instead."
34
+ desc "The field name of the client's hostname."
35
+ config_param :source_hostname_key, :string, default: nil
36
+
37
+ config_param :blocking_timeout, :time, default: 0.5
24
38
 
25
- config_set_default :port, 5170
26
39
  desc 'The payload is read up to this character.'
27
40
  config_param :delimiter, :string, default: "\n" # syslog family add "\n" to each message and this seems only way to split messages in tcp stream
28
41
 
29
- def listen(callback)
30
- log.info "listening tcp socket on #{@bind}:#{@port}"
42
+ def configure(conf)
43
+ compat_parameters_convert(conf, :parser)
44
+ super
45
+ @_event_loop_blocking_timeout = @blocking_timeout
46
+ @source_hostname_key ||= @source_host_key if @source_host_key
47
+
48
+ @parser = parser_create
49
+ end
50
+
51
+ def start
52
+ super
53
+
54
+ @buffer = ''
55
+ server_create(:in_tcp_server, @port, proto: :tcp, bind: @bind) do |data, conn|
56
+ @buffer << data
57
+ begin
58
+ pos = 0
59
+ while i = @buffer.index(@delimiter, pos)
60
+ msg = @buffer[pos...i]
61
+ pos = i + @delimiter.length
62
+
63
+ @parser.parse(msg) do |time, record|
64
+ unless time && record
65
+ log.warn "pattern not match", message: msg
66
+ next
67
+ end
31
68
 
32
- socket_manager_path = ENV['SERVERENGINE_SOCKETMANAGER_PATH']
33
- if Fluent.windows?
34
- socket_manager_path = socket_manager_path.to_i
69
+ tag = extract_tag_from_record(record)
70
+ tag ||= @tag
71
+ time ||= extract_time_from_record(record) || Fluent::EventTime.now
72
+ record[@source_hostname_key] = conn.remote_host if @source_hostname_key
73
+ router.emit(tag, time, record)
74
+ end
75
+ end
76
+ @buffer.slice!(0, pos) if pos > 0
77
+ end
35
78
  end
36
- client = ServerEngine::SocketManager::Client.new(socket_manager_path)
37
- lsock = client.listen_tcp(@bind, @port)
38
- Coolio::TCPServer.new(lsock, nil, SocketUtil::TcpHandler, log, @delimiter, callback)
39
79
  end
40
80
  end
41
81
  end
@@ -14,24 +14,60 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- require 'fluent/plugin/socket_util'
17
+ require 'fluent/plugin/input'
18
18
 
19
- module Fluent
20
- class UdpInput < SocketUtil::BaseInput
21
- Plugin.register_input('udp', self)
19
+ module Fluent::Plugin
20
+ class UdpInput < Input
21
+ Fluent::Plugin.register_input('udp', self)
22
+
23
+ helpers :server, :parser, :extract, :compat_parameters
24
+
25
+ desc 'Tag of output events.'
26
+ config_param :tag, :string
27
+ desc 'The port to listen to.'
28
+ config_param :port, :integer, default: 5160
29
+ desc 'The bind address to listen to.'
30
+ config_param :bind, :string, default: '0.0.0.0'
31
+
32
+ desc "The field name of the client's hostname."
33
+ config_param :source_host_key, :string, default: nil, deprecated: "use source_hostname_key instead."
34
+ desc "The field name of the client's hostname."
35
+ config_param :source_hostname_key, :string, default: nil
22
36
 
23
- config_set_default :port, 5160
24
37
  config_param :body_size_limit, :size, default: 4096
25
38
 
26
- def listen(callback)
27
- log.info "listening udp socket on #{@bind}:#{@port}"
28
- socket_manager_path = ENV['SERVERENGINE_SOCKETMANAGER_PATH']
29
- if Fluent.windows?
30
- socket_manager_path = socket_manager_path.to_i
39
+ config_param :blocking_timeout, :time, default: 0.5
40
+
41
+ def configure(conf)
42
+ compat_parameters_convert(conf, :parser)
43
+ super
44
+ @_event_loop_blocking_timeout = @blocking_timeout
45
+ @source_hostname_key ||= @source_host_key if @source_host_key
46
+
47
+ @parser = parser_create
48
+ end
49
+
50
+ def start
51
+ super
52
+
53
+ log.info "listening udp socket", bind: @bind, port: @port
54
+ server_create(:in_udp_server, @port, proto: :udp, bind: @bind, max_bytes: @body_size_limit) do |data, sock|
55
+ data.chomp!
56
+ begin
57
+ @parser.parse(data) do |time, record|
58
+ unless time && record
59
+ log.warn "pattern not match", data: data
60
+ next
61
+ end
62
+
63
+ tag = extract_tag_from_record(record)
64
+ tag ||= @tag
65
+ time ||= extract_time_from_record(record) || Fluent::EventTime.now
66
+ record[@source_hostname_key] = sock.remote_host if @source_hostname_key
67
+ router.emit(tag, time, record)
68
+ end
69
+ end
31
70
  end
32
- client = ServerEngine::SocketManager::Client.new(socket_manager_path)
33
- @usock = client.listen_udp(@bind, @port)
34
- SocketUtil::UdpHandler.new(@usock, log, @body_size_limit, callback)
35
71
  end
36
72
  end
37
73
  end
@@ -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
- if output.has_router?
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 argumen'
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
- desc 'Specify the comma-separated keys when using the tsv format.'
33
- config_param :keys, default: [] do |val|
34
- val.split(',')
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
- desc 'The name of the key to use as the event tag. This replaces the value in the event record.'
37
- config_param :tag_key, :string, default: nil
38
- desc 'The name of the key to use as the event time. This replaces the the value in the event record.'
39
- config_param :time_key, :string, default: nil
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
- def compat_parameters_default_chunk_key
52
- 'time'
42
+ config_section :buffer do
43
+ config_set_default :delayed_commit_timeout, 300 # 5 min
53
44
  end
54
45
 
55
- def configure(conf)
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
- def format(tag, time, record)
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 write(chunk)
95
- if chunk.respond_to?(:path)
96
- prog = "#{@command} #{chunk.path}"
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
- tmpfile = Tempfile.new("fluent-plugin-exec-")
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
- system(prog)
106
- ecode = $?.to_i
107
- tmpfile.delete if tmpfile
108
-
109
- if ecode != 0
110
- raise "command returns #{ecode}: #{prog}"
111
- end
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
- module Fluent
27
- class ExecFilterOutput < BufferedOutput
28
- Plugin.register_output('exec_filter', self)
20
+ require 'yajl'
29
21
 
30
- def initialize
31
- super
32
- require 'fluent/timezone'
33
- end
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
- desc "The format used to map the incoming event to the program input.(#{Fluent::ExecUtil::SUPPORTED_FORMAT.keys.join(',')})"
42
- config_param :in_format, default: :tsv do |val|
43
- f = Fluent::ExecUtil::SUPPORTED_FORMAT[val]
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
- desc 'Specify comma-separated values for tsv format.'
48
- config_param :in_keys, default: [] do |val|
49
- val.split(',')
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
- desc 'The name of the key to use as the event tag.'
52
- config_param :in_tag_key, default: nil
53
- desc 'The name of the key to use as the event time.'
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
- desc 'Specify comma-separated values for tsv format.'
65
- config_param :out_keys, default: [] do |val| # for tsv format
66
- val.split(',')
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 :time_key, :string, default: nil
78
- config_param :time_format, :string, default: nil
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
- config_set_default :flush_interval, 1
95
-
96
- def configure(conf)
97
- if tag_key = conf['tag_key']
98
- # TODO obsoleted?
99
- @in_tag_key = tag_key
100
- @out_tag_key = tag_key
101
- end
102
-
103
- if time_key = conf['time_key']
104
- # TODO obsoleted?
105
- @in_time_key = time_key
106
- @out_time_key = time_key
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
- if time_format = conf['time_format']
110
- # TODO obsoleted?
111
- @in_time_format = time_format
112
- @out_time_format = time_format
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
- super
129
+ def configure(conf)
130
+ exec_filter_compat_parameters_convert!(conf)
131
+ compat_parameters_convert(conf, :buffer)
116
132
 
117
- if conf['localtime']
118
- @localtime = true
119
- elsif conf['utc']
120
- @localtime = false
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
- if conf['timezone']
124
- @timezone = conf['timezone']
125
- Fluent::Timezone.validate!(@timezone)
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
- if !@tag && !@out_tag_key
129
- raise ConfigError, "'tag' or 'out_tag_key' option is required on exec_filter output"
130
- end
144
+ super
131
145
 
132
- if @in_time_key
133
- if f = @in_time_format
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
- if @out_time_key
144
- if f = @out_time_format
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
- def before_shutdown
223
- log.debug "out_exec_filter#before_shutdown called"
224
- @children.each {|c|
225
- c.finished = true
226
- }
227
- sleep 0.5 # TODO wait time before killing child process
228
-
229
- super
230
- end
231
-
232
- def shutdown
233
- @children.reject! {|c|
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
- super
239
- end
240
-
241
- def format_stream(tag, es)
242
- if @remove_prefix
243
- if (tag[0, @removed_length] == @removed_prefix_string and tag.length > @removed_length) or tag == @removed_prefix
244
- tag = tag[@removed_length..-1] || ''
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
- out = ''
249
-
250
- es.each {|time,record|
251
- if @in_time_key
252
- record[@in_time_key] = @time_format_proc.call(time)
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
- out
261
- end
262
-
263
- def write(chunk)
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
- def kill_child(join_wait)
293
- begin
294
- signal = Fluent.windows? ? :KILL : :TERM
295
- Process.kill(signal, @pid)
296
- rescue #Errno::ECHILD, Errno::ESRCH, Errno::EPERM
297
- # Errno::ESRCH 'No such process', ignore
298
- # child process killed by signal chained from fluentd process
299
- end
300
- if @thread.join(join_wait)
301
- # @thread successfully shutdown
302
- return
303
- end
304
- begin
305
- Process.kill(:KILL, @pid)
306
- rescue #Errno::ECHILD, Errno::ESRCH, Errno::EPERM
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
- def shutdown
313
- @finished = true
314
- @mutex.synchronize do
315
- kill_child(60) # TODO wait time
316
- end
317
- end
242
+ def terminate
243
+ @children = []
244
+ super
245
+ end
318
246
 
319
- def write(chunk)
320
- begin
321
- chunk.write_to(@io)
322
- rescue Errno::EPIPE => e
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
- def try_respawn
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
- @respawns -= 1 if @respawns > 0
346
- end
347
- @log.warn "exec_filter child process successfully respawned.", command: @command, respawns: @respawns
348
- true
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
- def run
352
- @parser.call(@io)
353
- rescue
354
- @log.error "exec_filter thread unexpectedly failed with an error.", command: @command, error: $!.to_s
355
- @log.warn_backtrace $!.backtrace
356
- ensure
357
- _pid, stat = Process.waitpid2(@pid)
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 on_message(record)
368
- if val = record.delete(@out_time_key)
369
- time = @time_parse_proc.call(val)
370
- else
371
- time = Engine.now
372
- end
373
-
374
- if val = record.delete(@out_tag_key)
375
- tag = if @add_prefix
376
- @added_prefix_string + val
377
- else
378
- val
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
- tag = @tag
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", error: $!, record: Yajl.dump(record)
388
- log.warn_backtrace $!.backtrace
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