fluentd 0.12.40 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +6 -0
  3. data/.gitignore +2 -0
  4. data/.travis.yml +33 -21
  5. data/CONTRIBUTING.md +1 -0
  6. data/ChangeLog +810 -237
  7. data/README.md +0 -25
  8. data/Rakefile +2 -1
  9. data/Vagrantfile +17 -0
  10. data/appveyor.yml +35 -0
  11. data/example/filter_stdout.conf +5 -5
  12. data/example/in_forward.conf +2 -2
  13. data/example/in_http.conf +2 -2
  14. data/example/in_out_forward.conf +17 -0
  15. data/example/in_syslog.conf +2 -2
  16. data/example/in_tail.conf +2 -2
  17. data/example/in_tcp.conf +2 -2
  18. data/example/in_udp.conf +2 -2
  19. data/example/out_copy.conf +4 -4
  20. data/example/out_file.conf +2 -2
  21. data/example/out_forward.conf +2 -2
  22. data/example/out_forward_buf_file.conf +23 -0
  23. data/example/v0_12_filter.conf +8 -8
  24. data/fluent.conf +29 -0
  25. data/fluentd.gemspec +18 -11
  26. data/lib/fluent/agent.rb +60 -58
  27. data/lib/fluent/command/cat.rb +1 -1
  28. data/lib/fluent/command/debug.rb +7 -5
  29. data/lib/fluent/command/fluentd.rb +97 -2
  30. data/lib/fluent/compat/call_super_mixin.rb +67 -0
  31. data/lib/fluent/compat/filter.rb +50 -0
  32. data/lib/fluent/compat/formatter.rb +109 -0
  33. data/lib/fluent/compat/input.rb +50 -0
  34. data/lib/fluent/compat/output.rb +617 -0
  35. data/lib/fluent/compat/output_chain.rb +60 -0
  36. data/lib/fluent/compat/parser.rb +163 -0
  37. data/lib/fluent/compat/propagate_default.rb +62 -0
  38. data/lib/fluent/config.rb +23 -20
  39. data/lib/fluent/config/configure_proxy.rb +119 -70
  40. data/lib/fluent/config/dsl.rb +5 -18
  41. data/lib/fluent/config/element.rb +72 -8
  42. data/lib/fluent/config/error.rb +0 -3
  43. data/lib/fluent/config/literal_parser.rb +0 -2
  44. data/lib/fluent/config/parser.rb +4 -4
  45. data/lib/fluent/config/section.rb +39 -28
  46. data/lib/fluent/config/types.rb +2 -13
  47. data/lib/fluent/config/v1_parser.rb +1 -3
  48. data/lib/fluent/configurable.rb +48 -16
  49. data/lib/fluent/daemon.rb +15 -0
  50. data/lib/fluent/engine.rb +26 -52
  51. data/lib/fluent/env.rb +6 -4
  52. data/lib/fluent/event.rb +58 -11
  53. data/lib/fluent/event_router.rb +5 -5
  54. data/lib/fluent/filter.rb +2 -50
  55. data/lib/fluent/formatter.rb +4 -293
  56. data/lib/fluent/input.rb +2 -32
  57. data/lib/fluent/label.rb +2 -2
  58. data/lib/fluent/load.rb +3 -2
  59. data/lib/fluent/log.rb +107 -38
  60. data/lib/fluent/match.rb +0 -36
  61. data/lib/fluent/mixin.rb +117 -7
  62. data/lib/fluent/msgpack_factory.rb +62 -0
  63. data/lib/fluent/output.rb +7 -612
  64. data/lib/fluent/output_chain.rb +23 -0
  65. data/lib/fluent/parser.rb +4 -800
  66. data/lib/fluent/plugin.rb +100 -121
  67. data/lib/fluent/plugin/bare_output.rb +63 -0
  68. data/lib/fluent/plugin/base.rb +121 -0
  69. data/lib/fluent/plugin/buf_file.rb +101 -182
  70. data/lib/fluent/plugin/buf_memory.rb +9 -92
  71. data/lib/fluent/plugin/buffer.rb +473 -0
  72. data/lib/fluent/plugin/buffer/chunk.rb +135 -0
  73. data/lib/fluent/plugin/buffer/file_chunk.rb +339 -0
  74. data/lib/fluent/plugin/buffer/memory_chunk.rb +100 -0
  75. data/lib/fluent/plugin/exec_util.rb +80 -75
  76. data/lib/fluent/plugin/file_util.rb +33 -28
  77. data/lib/fluent/plugin/file_wrapper.rb +120 -0
  78. data/lib/fluent/plugin/filter.rb +51 -0
  79. data/lib/fluent/plugin/filter_grep.rb +13 -40
  80. data/lib/fluent/plugin/filter_record_transformer.rb +22 -18
  81. data/lib/fluent/plugin/formatter.rb +93 -0
  82. data/lib/fluent/plugin/formatter_csv.rb +48 -0
  83. data/lib/fluent/plugin/formatter_hash.rb +32 -0
  84. data/lib/fluent/plugin/formatter_json.rb +47 -0
  85. data/lib/fluent/plugin/formatter_ltsv.rb +42 -0
  86. data/lib/fluent/plugin/formatter_msgpack.rb +32 -0
  87. data/lib/fluent/plugin/formatter_out_file.rb +45 -0
  88. data/lib/fluent/plugin/formatter_single_value.rb +34 -0
  89. data/lib/fluent/plugin/formatter_stdout.rb +39 -0
  90. data/lib/fluent/plugin/in_debug_agent.rb +4 -0
  91. data/lib/fluent/plugin/in_dummy.rb +22 -18
  92. data/lib/fluent/plugin/in_exec.rb +18 -8
  93. data/lib/fluent/plugin/in_forward.rb +36 -79
  94. data/lib/fluent/plugin/in_gc_stat.rb +4 -0
  95. data/lib/fluent/plugin/in_http.rb +21 -18
  96. data/lib/fluent/plugin/in_monitor_agent.rb +15 -48
  97. data/lib/fluent/plugin/in_object_space.rb +6 -1
  98. data/lib/fluent/plugin/in_stream.rb +7 -3
  99. data/lib/fluent/plugin/in_syslog.rb +46 -95
  100. data/lib/fluent/plugin/in_tail.rb +51 -595
  101. data/lib/fluent/plugin/in_tcp.rb +8 -1
  102. data/lib/fluent/plugin/in_udp.rb +8 -14
  103. data/lib/fluent/plugin/input.rb +33 -0
  104. data/lib/fluent/plugin/multi_output.rb +95 -0
  105. data/lib/fluent/plugin/out_buffered_null.rb +59 -0
  106. data/lib/fluent/plugin/out_copy.rb +11 -7
  107. data/lib/fluent/plugin/out_exec.rb +15 -11
  108. data/lib/fluent/plugin/out_exec_filter.rb +18 -10
  109. data/lib/fluent/plugin/out_file.rb +34 -5
  110. data/lib/fluent/plugin/out_forward.rb +19 -9
  111. data/lib/fluent/plugin/out_null.rb +0 -14
  112. data/lib/fluent/plugin/out_roundrobin.rb +11 -7
  113. data/lib/fluent/plugin/out_stdout.rb +5 -7
  114. data/lib/fluent/plugin/out_stream.rb +3 -1
  115. data/lib/fluent/plugin/output.rb +979 -0
  116. data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
  117. data/lib/fluent/plugin/parser.rb +244 -0
  118. data/lib/fluent/plugin/parser_apache.rb +24 -0
  119. data/lib/fluent/plugin/parser_apache2.rb +84 -0
  120. data/lib/fluent/plugin/parser_apache_error.rb +21 -0
  121. data/lib/fluent/plugin/parser_csv.rb +31 -0
  122. data/lib/fluent/plugin/parser_json.rb +79 -0
  123. data/lib/fluent/plugin/parser_ltsv.rb +50 -0
  124. data/lib/fluent/plugin/parser_multiline.rb +102 -0
  125. data/lib/fluent/plugin/parser_nginx.rb +24 -0
  126. data/lib/fluent/plugin/parser_none.rb +36 -0
  127. data/lib/fluent/plugin/parser_syslog.rb +82 -0
  128. data/lib/fluent/plugin/parser_tsv.rb +37 -0
  129. data/lib/fluent/plugin/socket_util.rb +120 -114
  130. data/lib/fluent/plugin/storage.rb +84 -0
  131. data/lib/fluent/plugin/storage_local.rb +116 -0
  132. data/lib/fluent/plugin/string_util.rb +16 -13
  133. data/lib/fluent/plugin_helper.rb +39 -0
  134. data/lib/fluent/plugin_helper/child_process.rb +298 -0
  135. data/lib/fluent/plugin_helper/compat_parameters.rb +99 -0
  136. data/lib/fluent/plugin_helper/event_emitter.rb +80 -0
  137. data/lib/fluent/plugin_helper/event_loop.rb +118 -0
  138. data/lib/fluent/plugin_helper/retry_state.rb +177 -0
  139. data/lib/fluent/plugin_helper/storage.rb +308 -0
  140. data/lib/fluent/plugin_helper/thread.rb +147 -0
  141. data/lib/fluent/plugin_helper/timer.rb +85 -0
  142. data/lib/fluent/plugin_id.rb +63 -0
  143. data/lib/fluent/process.rb +21 -30
  144. data/lib/fluent/registry.rb +21 -9
  145. data/lib/fluent/root_agent.rb +115 -40
  146. data/lib/fluent/supervisor.rb +330 -320
  147. data/lib/fluent/system_config.rb +42 -18
  148. data/lib/fluent/test.rb +6 -1
  149. data/lib/fluent/test/base.rb +23 -3
  150. data/lib/fluent/test/driver/base.rb +247 -0
  151. data/lib/fluent/test/driver/event_feeder.rb +98 -0
  152. data/lib/fluent/test/driver/filter.rb +35 -0
  153. data/lib/fluent/test/driver/input.rb +31 -0
  154. data/lib/fluent/test/driver/output.rb +78 -0
  155. data/lib/fluent/test/driver/test_event_router.rb +45 -0
  156. data/lib/fluent/test/filter_test.rb +0 -1
  157. data/lib/fluent/test/formatter_test.rb +2 -1
  158. data/lib/fluent/test/input_test.rb +23 -17
  159. data/lib/fluent/test/output_test.rb +28 -39
  160. data/lib/fluent/test/parser_test.rb +1 -1
  161. data/lib/fluent/time.rb +104 -1
  162. data/lib/fluent/{status.rb → unique_id.rb} +15 -24
  163. data/lib/fluent/version.rb +1 -1
  164. data/lib/fluent/winsvc.rb +72 -0
  165. data/test/compat/test_calls_super.rb +164 -0
  166. data/test/config/test_config_parser.rb +83 -0
  167. data/test/config/test_configurable.rb +547 -274
  168. data/test/config/test_configure_proxy.rb +146 -29
  169. data/test/config/test_dsl.rb +3 -181
  170. data/test/config/test_element.rb +274 -0
  171. data/test/config/test_literal_parser.rb +1 -1
  172. data/test/config/test_section.rb +79 -7
  173. data/test/config/test_system_config.rb +21 -0
  174. data/test/config/test_types.rb +3 -26
  175. data/test/helper.rb +78 -8
  176. data/test/plugin/test_bare_output.rb +118 -0
  177. data/test/plugin/test_base.rb +75 -0
  178. data/test/plugin/test_buf_file.rb +420 -521
  179. data/test/plugin/test_buf_memory.rb +32 -194
  180. data/test/plugin/test_buffer.rb +981 -0
  181. data/test/plugin/test_buffer_chunk.rb +110 -0
  182. data/test/plugin/test_buffer_file_chunk.rb +770 -0
  183. data/test/plugin/test_buffer_memory_chunk.rb +265 -0
  184. data/test/plugin/test_filter.rb +255 -0
  185. data/test/plugin/test_filter_grep.rb +2 -73
  186. data/test/plugin/test_filter_record_transformer.rb +24 -68
  187. data/test/plugin/test_filter_stdout.rb +6 -6
  188. data/test/plugin/test_in_debug_agent.rb +2 -0
  189. data/test/plugin/test_in_dummy.rb +11 -17
  190. data/test/plugin/test_in_exec.rb +6 -25
  191. data/test/plugin/test_in_forward.rb +112 -151
  192. data/test/plugin/test_in_gc_stat.rb +2 -0
  193. data/test/plugin/test_in_http.rb +106 -157
  194. data/test/plugin/test_in_object_space.rb +21 -5
  195. data/test/plugin/test_in_stream.rb +14 -13
  196. data/test/plugin/test_in_syslog.rb +30 -275
  197. data/test/plugin/test_in_tail.rb +95 -234
  198. data/test/plugin/test_in_tcp.rb +14 -0
  199. data/test/plugin/test_in_udp.rb +21 -13
  200. data/test/plugin/test_input.rb +122 -0
  201. data/test/plugin/test_multi_output.rb +180 -0
  202. data/test/plugin/test_out_buffered_null.rb +79 -0
  203. data/test/plugin/test_out_copy.rb +15 -2
  204. data/test/plugin/test_out_exec.rb +75 -25
  205. data/test/plugin/test_out_exec_filter.rb +74 -8
  206. data/test/plugin/test_out_file.rb +61 -7
  207. data/test/plugin/test_out_forward.rb +92 -15
  208. data/test/plugin/test_out_roundrobin.rb +1 -0
  209. data/test/plugin/test_out_stdout.rb +22 -13
  210. data/test/plugin/test_out_stream.rb +18 -0
  211. data/test/plugin/test_output.rb +515 -0
  212. data/test/plugin/test_output_as_buffered.rb +1540 -0
  213. data/test/plugin/test_output_as_buffered_overflow.rb +247 -0
  214. data/test/plugin/test_output_as_buffered_retries.rb +808 -0
  215. data/test/plugin/test_output_as_buffered_secondary.rb +776 -0
  216. data/test/plugin/test_output_as_standard.rb +362 -0
  217. data/test/plugin/test_owned_by.rb +35 -0
  218. data/test/plugin/test_storage.rb +167 -0
  219. data/test/plugin/test_storage_local.rb +8 -0
  220. data/test/plugin_helper/test_child_process.rb +599 -0
  221. data/test/plugin_helper/test_compat_parameters.rb +175 -0
  222. data/test/plugin_helper/test_event_emitter.rb +51 -0
  223. data/test/plugin_helper/test_event_loop.rb +52 -0
  224. data/test/plugin_helper/test_retry_state.rb +399 -0
  225. data/test/plugin_helper/test_storage.rb +411 -0
  226. data/test/plugin_helper/test_thread.rb +164 -0
  227. data/test/plugin_helper/test_timer.rb +100 -0
  228. data/test/scripts/exec_script.rb +0 -6
  229. data/test/scripts/fluent/plugin/out_test.rb +3 -0
  230. data/test/test_config.rb +13 -4
  231. data/test/test_event.rb +24 -13
  232. data/test/test_event_router.rb +8 -7
  233. data/test/test_event_time.rb +187 -0
  234. data/test/test_formatter.rb +13 -51
  235. data/test/test_input.rb +1 -1
  236. data/test/test_log.rb +239 -16
  237. data/test/test_mixin.rb +1 -1
  238. data/test/test_output.rb +53 -66
  239. data/test/test_parser.rb +105 -323
  240. data/test/test_plugin_helper.rb +81 -0
  241. data/test/test_root_agent.rb +4 -52
  242. data/test/test_supervisor.rb +272 -0
  243. data/test/test_unique_id.rb +47 -0
  244. metadata +180 -54
  245. data/lib/fluent/buffer.rb +0 -365
  246. data/lib/fluent/plugin/filter_parser.rb +0 -107
  247. data/lib/fluent/plugin/in_status.rb +0 -76
  248. data/lib/fluent/test/helpers.rb +0 -86
  249. data/test/plugin/data/log/foo/bar2 +0 -0
  250. data/test/plugin/test_filter_parser.rb +0 -744
  251. data/test/plugin/test_in_status.rb +0 -38
  252. data/test/test_buffer.rb +0 -624
@@ -19,16 +19,29 @@ require 'cool.io'
19
19
  require 'fluent/input'
20
20
  require 'fluent/config/error'
21
21
  require 'fluent/event'
22
+ require 'fluent/system_config'
23
+ require 'fluent/plugin/buffer'
24
+
25
+ if Fluent.windows?
26
+ require_relative 'file_wrapper'
27
+ else
28
+ Fluent::FileWrapper = File
29
+ end
22
30
 
23
31
  module Fluent
24
- class NewTailInput < Input
32
+ class TailInput < Input
33
+ include SystemConfig::Mixin
34
+
25
35
  Plugin.register_input('tail', self)
26
36
 
37
+ FILE_PERMISSION = 0644
38
+
27
39
  def initialize
28
40
  super
29
41
  @paths = []
30
42
  @tails = {}
31
- @ignore_list = []
43
+ @pf_file = nil
44
+ @pf = nil
32
45
  end
33
46
 
34
47
  desc 'The paths to read. Multiple paths can be specified, separated by comma.'
@@ -49,22 +62,18 @@ module Fluent
49
62
  config_param :read_lines_limit, :integer, default: 1000
50
63
  desc 'The interval of flushing the buffer for multiline format'
51
64
  config_param :multiline_flush_interval, :time, default: nil
52
- desc 'Enable the option to emit unmatched lines.'
53
- config_param :emit_unmatched_lines, :bool, default: false
54
65
  desc 'Enable the additional watch timer.'
55
66
  config_param :enable_watch_timer, :bool, default: true
56
- desc 'The encoding after conversion of the input.'
57
- config_param :encoding, :string, default: nil
58
67
  desc 'The encoding of the input.'
59
- config_param :from_encoding, :string, default: nil
68
+ config_param :encoding, default: nil do |encoding_name|
69
+ begin
70
+ Encoding.find(encoding_name)
71
+ rescue ArgumentError => e
72
+ raise ConfigError, e.message
73
+ end
74
+ end
60
75
  desc 'Add the log path being tailed to records. Specify the field name to be used.'
61
76
  config_param :path_key, :string, default: nil
62
- desc 'Limit the watching files that the modification time is within the specified time range (when use \'*\' in path).'
63
- config_param :limit_recently_modified, :time, default: nil
64
- desc 'Enable the option to skip the refresh of watching list on startup.'
65
- config_param :skip_refresh_on_startup, :bool, default: false
66
- desc 'Ignore repeated permission error logs'
67
- config_param :ignore_repeated_permission_error, :bool, default: false
68
77
 
69
78
  attr_reader :paths
70
79
 
@@ -83,7 +92,6 @@ module Fluent
83
92
 
84
93
  configure_parser(conf)
85
94
  configure_tag
86
- configure_encoding
87
95
 
88
96
  @multiline_mode = conf['format'] =~ /multiline/
89
97
  @receive_handler = if @multiline_mode
@@ -91,6 +99,7 @@ module Fluent
91
99
  else
92
100
  method(:parse_singleline)
93
101
  end
102
+ @file_perm = system_config.file_permission || FILE_PERMISSION
94
103
  end
95
104
 
96
105
  def configure_parser(conf)
@@ -101,7 +110,6 @@ module Fluent
101
110
  def configure_tag
102
111
  if @tag.index('*')
103
112
  @tag_prefix, @tag_suffix = @tag.split('*')
104
- @tag_prefix ||= ''
105
113
  @tag_suffix ||= ''
106
114
  else
107
115
  @tag_prefix = nil
@@ -109,34 +117,17 @@ module Fluent
109
117
  end
110
118
  end
111
119
 
112
- def configure_encoding
113
- unless @encoding
114
- if @from_encoding
115
- raise ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
116
- end
117
- end
118
-
119
- @encoding = parse_encoding_param(@encoding) if @encoding
120
- @from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
121
- end
122
-
123
- def parse_encoding_param(encoding_name)
124
- begin
125
- Encoding.find(encoding_name) if encoding_name
126
- rescue ArgumentError => e
127
- raise ConfigError, e.message
128
- end
129
- end
130
-
131
120
  def start
121
+ super
122
+
132
123
  if @pos_file
133
- @pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
124
+ @pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
134
125
  @pf_file.sync = true
135
126
  @pf = PositionFile.parse(@pf_file)
136
127
  end
137
128
 
138
129
  @loop = Coolio::Loop.new
139
- refresh_watchers unless @skip_refresh_on_startup
130
+ refresh_watchers
140
131
 
141
132
  @refresh_trigger = TailWatcher::TimerWatcher.new(@refresh_interval, true, log, &method(:refresh_watchers))
142
133
  @refresh_trigger.attach(@loop)
@@ -150,30 +141,23 @@ module Fluent
150
141
  @loop.stop rescue nil # when all watchers are detached, `stop` raises RuntimeError. We can ignore this exception.
151
142
  @thread.join
152
143
  @pf_file.close if @pf_file
144
+
145
+ super
153
146
  end
154
147
 
155
148
  def expand_paths
156
149
  date = Time.now
157
150
  paths = []
158
151
 
152
+ excluded = @exclude_path.map { |path| path = date.strftime(path); path.include?('*') ? Dir.glob(path) : path }.flatten.uniq
159
153
  @paths.each { |path|
160
154
  path = date.strftime(path)
161
155
  if path.include?('*')
162
156
  paths += Dir.glob(path).select { |p|
163
- is_file = !File.directory?(p)
164
- if File.readable?(p) && is_file
165
- if @limit_recently_modified && File.mtime(p) < (date - @limit_recently_modified)
166
- false
167
- else
168
- true
169
- end
157
+ if File.readable?(p)
158
+ true
170
159
  else
171
- if is_file
172
- unless @ignore_list.include?(path)
173
- log.warn "#{p} unreadable. It is excluded and would be examined next time."
174
- @ignore_list << path if @ignore_repeated_permission_error
175
- end
176
- end
160
+ log.warn "#{p} unreadable. It is excluded and would be examined next time."
177
161
  false
178
162
  end
179
163
  }
@@ -182,7 +166,6 @@ module Fluent
182
166
  paths << path
183
167
  end
184
168
  }
185
- excluded = @exclude_path.map { |path| path = date.strftime(path); path.include?('*') ? Dir.glob(path) : path }.flatten.uniq
186
169
  paths - excluded
187
170
  end
188
171
 
@@ -216,7 +199,7 @@ module Fluent
216
199
  pe = @pf[path]
217
200
  if @read_from_head && pe.read_inode.zero?
218
201
  begin
219
- pe.update(File::Stat.new(path).ino, 0)
202
+ pe.update(FileWrapper.stat(path).ino, 0)
220
203
  rescue Errno::ENOENT
221
204
  $log.warn "#{path} not found. Continuing without tailing it."
222
205
  end
@@ -268,13 +251,7 @@ module Fluent
268
251
  def flush_buffer(tw)
269
252
  if lb = tw.line_buffer
270
253
  lb.chomp!
271
- if @encoding
272
- if @from_encoding
273
- lb.encode!(@encoding, @from_encoding)
274
- else
275
- lb.force_encoding(@encoding)
276
- end
277
- end
254
+ lb.force_encoding(@encoding) if @encoding
278
255
  @parser.parse(lb) { |time, record|
279
256
  if time && record
280
257
  tag = if @tag_prefix || @tag_suffix
@@ -298,7 +275,7 @@ module Fluent
298
275
  log.error_backtrace
299
276
  end
300
277
 
301
- # @return true if no error or unrecoverable error happens in emit action. false if got BufferQueueLimitError
278
+ # @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
302
279
  def receive_lines(lines, tail_watcher)
303
280
  es = @receive_handler.call(lines, tail_watcher)
304
281
  unless es.empty?
@@ -309,7 +286,7 @@ module Fluent
309
286
  end
310
287
  begin
311
288
  router.emit_stream(tag, es)
312
- rescue BufferQueueLimitError
289
+ rescue Fluent::Plugin::Buffer::BufferOverflowError
313
290
  return false
314
291
  rescue
315
292
  # ignore non BufferQueueLimitError errors because in_tail can't recover. Engine shows logs and backtraces.
@@ -323,23 +300,12 @@ module Fluent
323
300
  def convert_line_to_event(line, es, tail_watcher)
324
301
  begin
325
302
  line.chomp! # remove \n
326
- if @encoding
327
- if @from_encoding
328
- line.encode!(@encoding, @from_encoding)
329
- else
330
- line.force_encoding(@encoding)
331
- end
332
- end
303
+ line.force_encoding(@encoding) if @encoding
333
304
  @parser.parse(line) { |time, record|
334
305
  if time && record
335
306
  record[@path_key] ||= tail_watcher.path unless @path_key.nil?
336
307
  es.add(time, record)
337
308
  else
338
- if @emit_unmatched_lines
339
- record = {'unmatched_line' => line}
340
- record[@path_key] ||= tail_watcher.path unless @path_key.nil?
341
- es.add(::Fluent::Engine.now, record)
342
- end
343
309
  log.warn "pattern not match: #{line.inspect}"
344
310
  end
345
311
  }
@@ -370,9 +336,6 @@ module Fluent
370
336
  lb = line
371
337
  else
372
338
  if lb.nil?
373
- if @emit_unmatched_lines
374
- convert_line_to_event(line, es, tail_watcher)
375
- end
376
339
  log.warn "got incomplete line before first line from #{tail_watcher.path}: #{line.inspect}"
377
340
  else
378
341
  lb << line
@@ -509,26 +472,24 @@ module Fluent
509
472
  io_handler = IOHandler.new(io, @pe, @log, @read_lines_limit, &method(:wrap_receive_lines))
510
473
  @io_handler = io_handler
511
474
  else # file is rotated and new file found
512
- detach
513
475
  @update_watcher.call(@path, swap_state(@pe))
514
476
  end
515
477
  else # file is rotated and new file not found
516
478
  # Clear RotateHandler to avoid duplicated file watch in same path.
517
479
  @rotate_handler = nil
518
- detach
519
480
  @update_watcher.call(@path, swap_state(@pe))
520
481
  end
521
482
  end
483
+ end
522
484
 
523
- def swap_state(pe)
524
- # Use MemoryPositionEntry for rotated file temporary
525
- mpe = MemoryPositionEntry.new
526
- mpe.update(pe.read_inode, pe.read_pos)
527
- @pe = mpe
528
- @io_handler.pe = mpe # Don't re-create IOHandler because IOHandler has an internal buffer.
485
+ def swap_state(pe)
486
+ # Use MemoryPositionEntry for rotated file temporary
487
+ mpe = MemoryPositionEntry.new
488
+ mpe.update(pe.read_inode, pe.read_pos)
489
+ @pe = mpe
490
+ @io_handler.pe = mpe # Don't re-create IOHandler because IOHandler has an internal buffer.
529
491
 
530
- pe # This pe will be updated in on_rotate after TailWatcher is initialized
531
- end
492
+ pe # This pe will be updated in on_rotate after TailWatcher is initialized
532
493
  end
533
494
 
534
495
  class TimerWatcher < Coolio::TimerWatcher
@@ -609,8 +570,8 @@ module Fluent
609
570
  else
610
571
  @buffer << @io.readpartial(2048, @iobuf)
611
572
  end
612
- while idx = @buffer.index("\n".freeze)
613
- @lines << @buffer.slice!(0, idx + 1)
573
+ while line = @buffer.slice!(/.*?\n/m)
574
+ @lines << line
614
575
  end
615
576
  if @lines.size >= @read_lines_limit
616
577
  # not to use too much memory in case the file is very large
@@ -668,7 +629,7 @@ module Fluent
668
629
 
669
630
  def on_notify
670
631
  begin
671
- stat = File.stat(@path)
632
+ stat = FileWrapper.stat(@path)
672
633
  inode = stat.ino
673
634
  fsize = stat.size
674
635
  rescue Errno::ENOENT
@@ -681,7 +642,7 @@ module Fluent
681
642
  if @inode != inode || fsize < @fsize
682
643
  # rotated or truncated
683
644
  begin
684
- io = File.open(@path)
645
+ io = FileWrapper.open(@path)
685
646
  rescue Errno::ENOENT
686
647
  end
687
648
  @on_rotate.call(io)
@@ -755,8 +716,6 @@ module Fluent
755
716
  m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
756
717
  next unless m
757
718
  path = m[1]
758
- pos = m[2].to_i(16)
759
- ino = m[3].to_i(16)
760
719
  seek = file.pos - line.bytesize + path.bytesize + 1
761
720
  map[path] = FilePositionEntry.new(file, seek)
762
721
  }
@@ -844,508 +803,5 @@ module Fluent
844
803
  end
845
804
  end
846
805
 
847
- # This TailInput is for existence plugins which extends old in_tail
848
- # This class will be removed after release v1.
849
- class TailInput < Input
850
- def initialize
851
- super
852
- @paths = []
853
- end
854
-
855
- config_param :path, :string
856
- config_param :tag, :string
857
- config_param :rotate_wait, :time, :default => 5
858
- config_param :pos_file, :string, :default => nil
859
-
860
- attr_reader :paths
861
-
862
- def configure(conf)
863
- super
864
-
865
- @paths = @path.split(',').map {|path| path.strip }
866
- if @paths.empty?
867
- raise ConfigError, "tail: 'path' parameter is required on tail input"
868
- end
869
-
870
- unless @pos_file
871
- $log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
872
- $log.warn "this parameter is highly recommended to save the position to resume tailing."
873
- end
874
-
875
- configure_parser(conf)
876
- end
877
-
878
- def configure_parser(conf)
879
- @parser = TextParser.new
880
- @parser.configure(conf)
881
- end
882
-
883
- def start
884
- if @pos_file
885
- @pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
886
- @pf_file.sync = true
887
- @pf = PositionFile.parse(@pf_file)
888
- end
889
-
890
- @loop = Coolio::Loop.new
891
- @tails = @paths.map {|path|
892
- pe = @pf ? @pf[path] : MemoryPositionEntry.new
893
- tw = TailWatcher.new(path, @rotate_wait, pe, &method(:receive_lines))
894
- tw.log = log
895
- tw
896
- }
897
- @tails.each {|tail|
898
- tail.attach(@loop)
899
- }
900
- @thread = Thread.new(&method(:run))
901
- end
902
-
903
- def shutdown
904
- @tails.each {|tail|
905
- tail.close
906
- }
907
- @loop.stop
908
- @thread.join
909
- @pf_file.close if @pf_file
910
- end
911
-
912
- def run
913
- @loop.run
914
- rescue
915
- log.error "unexpected error", :error=>$!.to_s
916
- log.error_backtrace
917
- end
918
-
919
- def receive_lines(lines)
920
- es = MultiEventStream.new
921
- lines.each {|line|
922
- begin
923
- line.chomp! # remove \n
924
- time, record = parse_line(line)
925
- if time && record
926
- es.add(time, record)
927
- else
928
- log.warn "pattern not match: #{line.inspect}"
929
- end
930
- rescue
931
- log.warn line.dump, :error=>$!.to_s
932
- log.debug_backtrace
933
- end
934
- }
935
-
936
- unless es.empty?
937
- begin
938
- router.emit_stream(@tag, es)
939
- rescue
940
- # ignore errors. Engine shows logs and backtraces.
941
- end
942
- end
943
- end
944
-
945
- def parse_line(line)
946
- return @parser.parse(line)
947
- end
948
-
949
- class TailWatcher
950
- def initialize(path, rotate_wait, pe, &receive_lines)
951
- @path = path
952
- @rotate_wait = rotate_wait
953
- @pe = pe || MemoryPositionEntry.new
954
- @receive_lines = receive_lines
955
-
956
- @rotate_queue = []
957
-
958
- @timer_trigger = TimerWatcher.new(1, true, &method(:on_notify))
959
- @stat_trigger = StatWatcher.new(path, &method(:on_notify))
960
-
961
- @rotate_handler = RotateHandler.new(path, &method(:on_rotate))
962
- @io_handler = nil
963
- @log = $log
964
- end
965
-
966
- # We use accessor approach to assign each logger, not passing log object at initialization,
967
- # because several plugins depend on these internal classes.
968
- # This approach avoids breaking plugins with new log_level option.
969
- attr_accessor :log
970
-
971
- def log=(logger)
972
- @log = logger
973
- @timer_trigger.log = logger
974
- @stat_trigger.log = logger
975
- @rotate_handler.log = logger
976
- end
977
-
978
- def attach(loop)
979
- @timer_trigger.attach(loop)
980
- @stat_trigger.attach(loop)
981
- on_notify
982
- end
983
-
984
- def detach
985
- @timer_trigger.detach if @timer_trigger.attached?
986
- @stat_trigger.detach if @stat_trigger.attached?
987
- end
988
-
989
- def close
990
- @rotate_queue.reject! {|req|
991
- req.io.close
992
- true
993
- }
994
- detach
995
- end
996
-
997
- def on_notify
998
- @rotate_handler.on_notify
999
- return unless @io_handler
1000
- @io_handler.on_notify
1001
-
1002
- # proceeds rotate queue
1003
- return if @rotate_queue.empty?
1004
- @rotate_queue.first.tick
1005
-
1006
- while @rotate_queue.first.ready?
1007
- if io = @rotate_queue.first.io
1008
- stat = io.stat
1009
- inode = stat.ino
1010
- if inode == @pe.read_inode
1011
- # rotated file has the same inode number with the last file.
1012
- # assuming following situation:
1013
- # a) file was once renamed and backed, or
1014
- # b) symlink or hardlink to the same file is recreated
1015
- # in either case, seek to the saved position
1016
- pos = @pe.read_pos
1017
- else
1018
- pos = io.pos
1019
- end
1020
- @pe.update(inode, pos)
1021
- io_handler = IOHandler.new(io, @pe, log, &@receive_lines)
1022
- else
1023
- io_handler = NullIOHandler.new
1024
- end
1025
- @io_handler.close
1026
- @io_handler = io_handler
1027
- @rotate_queue.shift
1028
- break if @rotate_queue.empty?
1029
- end
1030
- end
1031
-
1032
- def on_rotate(io)
1033
- if @io_handler == nil
1034
- if io
1035
- # first time
1036
- stat = io.stat
1037
- fsize = stat.size
1038
- inode = stat.ino
1039
-
1040
- last_inode = @pe.read_inode
1041
- if inode == last_inode
1042
- # seek to the saved position
1043
- pos = @pe.read_pos
1044
- elsif last_inode != 0
1045
- # this is FilePositionEntry and fluentd once started.
1046
- # read data from the head of the rotated file.
1047
- # logs never duplicate because this file is a rotated new file.
1048
- pos = 0
1049
- @pe.update(inode, pos)
1050
- else
1051
- # this is MemoryPositionEntry or this is the first time fluentd started.
1052
- # seek to the end of the any files.
1053
- # logs may duplicate without this seek because it's not sure the file is
1054
- # existent file or rotated new file.
1055
- pos = fsize
1056
- @pe.update(inode, pos)
1057
- end
1058
- io.seek(pos)
1059
-
1060
- @io_handler = IOHandler.new(io, @pe, log, &@receive_lines)
1061
- else
1062
- @io_handler = NullIOHandler.new
1063
- end
1064
-
1065
- else
1066
- if io && @rotate_queue.find {|req| req.io == io }
1067
- return
1068
- end
1069
- last_io = @rotate_queue.empty? ? @io_handler.io : @rotate_queue.last.io
1070
- if last_io == nil
1071
- log.info "detected rotation of #{@path}"
1072
- # rotate imeediately if previous file is nil
1073
- wait = 0
1074
- else
1075
- log.info "detected rotation of #{@path}; waiting #{@rotate_wait} seconds"
1076
- wait = @rotate_wait
1077
- wait -= @rotate_queue.first.wait unless @rotate_queue.empty?
1078
- end
1079
- @rotate_queue << RotationRequest.new(io, wait)
1080
- end
1081
- end
1082
-
1083
- class TimerWatcher < Coolio::TimerWatcher
1084
- def initialize(interval, repeat, &callback)
1085
- @callback = callback
1086
- @log = $log
1087
- super(interval, repeat)
1088
- end
1089
-
1090
- attr_accessor :log
1091
-
1092
- def on_timer
1093
- @callback.call
1094
- rescue
1095
- # TODO log?
1096
- @log.error $!.to_s
1097
- @log.error_backtrace
1098
- end
1099
- end
1100
-
1101
- class StatWatcher < Coolio::StatWatcher
1102
- def initialize(path, &callback)
1103
- @callback = callback
1104
- @log = $log
1105
- super(path)
1106
- end
1107
-
1108
- attr_accessor :log
1109
-
1110
- def on_change(prev, cur)
1111
- @callback.call
1112
- rescue
1113
- # TODO log?
1114
- @log.error $!.to_s
1115
- @log.error_backtrace
1116
- end
1117
- end
1118
-
1119
- class RotationRequest
1120
- def initialize(io, wait)
1121
- @io = io
1122
- @wait = wait
1123
- end
1124
-
1125
- attr_reader :io, :wait
1126
-
1127
- def tick
1128
- @wait -= 1
1129
- end
1130
-
1131
- def ready?
1132
- @wait <= 0
1133
- end
1134
- end
1135
-
1136
- MAX_LINES_AT_ONCE = 1000
1137
-
1138
- class IOHandler
1139
- def initialize(io, pe, log, &receive_lines)
1140
- @log = log
1141
- @log.info "following tail of #{io.path}"
1142
- @io = io
1143
- @pe = pe
1144
- @receive_lines = receive_lines
1145
- @buffer = ''.force_encoding('ASCII-8BIT')
1146
- @iobuf = ''.force_encoding('ASCII-8BIT')
1147
- end
1148
-
1149
- attr_reader :io
1150
-
1151
- def on_notify
1152
- begin
1153
- lines = []
1154
- read_more = false
1155
-
1156
- begin
1157
- while true
1158
- if @buffer.empty?
1159
- @io.read_nonblock(2048, @buffer)
1160
- else
1161
- @buffer << @io.read_nonblock(2048, @iobuf)
1162
- end
1163
- while line = @buffer.slice!(/.*?\n/m)
1164
- lines << line
1165
- end
1166
- if lines.size >= MAX_LINES_AT_ONCE
1167
- # not to use too much memory in case the file is very large
1168
- read_more = true
1169
- break
1170
- end
1171
- end
1172
- rescue EOFError
1173
- end
1174
-
1175
- unless lines.empty?
1176
- @receive_lines.call(lines)
1177
- @pe.update_pos(@io.pos - @buffer.bytesize)
1178
- end
1179
-
1180
- end while read_more
1181
-
1182
- rescue
1183
- @log.error $!.to_s
1184
- @log.error_backtrace
1185
- close
1186
- end
1187
-
1188
- def close
1189
- @io.close unless @io.closed?
1190
- end
1191
- end
1192
-
1193
- class NullIOHandler
1194
- def initialize
1195
- end
1196
-
1197
- def io
1198
- end
1199
-
1200
- def on_notify
1201
- end
1202
-
1203
- def close
1204
- end
1205
- end
1206
-
1207
- class RotateHandler
1208
- def initialize(path, &on_rotate)
1209
- @path = path
1210
- @inode = nil
1211
- @fsize = -1 # first
1212
- @on_rotate = on_rotate
1213
- @log = $log
1214
- end
1215
-
1216
- attr_accessor :log
1217
-
1218
- def on_notify
1219
- begin
1220
- io = File.open(@path)
1221
- stat = io.stat
1222
- inode = stat.ino
1223
- fsize = stat.size
1224
- rescue Errno::ENOENT
1225
- # moved or deleted
1226
- inode = nil
1227
- fsize = 0
1228
- end
1229
-
1230
- begin
1231
- if @inode != inode || fsize < @fsize
1232
- # rotated or truncated
1233
- @on_rotate.call(io)
1234
- io = nil
1235
- end
1236
-
1237
- @inode = inode
1238
- @fsize = fsize
1239
- ensure
1240
- io.close if io
1241
- end
1242
-
1243
- rescue
1244
- @log.error $!.to_s
1245
- @log.error_backtrace
1246
- end
1247
- end
1248
- end
1249
-
1250
-
1251
- class PositionFile
1252
- def initialize(file, map, last_pos)
1253
- @file = file
1254
- @map = map
1255
- @last_pos = last_pos
1256
- end
1257
-
1258
- def [](path)
1259
- if m = @map[path]
1260
- return m
1261
- end
1262
-
1263
- @file.pos = @last_pos
1264
- @file.write path
1265
- @file.write "\t"
1266
- seek = @file.pos
1267
- @file.write "0000000000000000\t00000000\n"
1268
- @last_pos = @file.pos
1269
-
1270
- @map[path] = FilePositionEntry.new(@file, seek)
1271
- end
1272
-
1273
- def self.parse(file)
1274
- map = {}
1275
- file.pos = 0
1276
- file.each_line {|line|
1277
- m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
1278
- next unless m
1279
- path = m[1]
1280
- pos = m[2].to_i(16)
1281
- ino = m[3].to_i(16)
1282
- seek = file.pos - line.bytesize + path.bytesize + 1
1283
- map[path] = FilePositionEntry.new(file, seek)
1284
- }
1285
- new(file, map, file.pos)
1286
- end
1287
- end
1288
-
1289
- # pos inode
1290
- # ffffffffffffffff\tffffffff\n
1291
- class FilePositionEntry
1292
- POS_SIZE = 16
1293
- INO_OFFSET = 17
1294
- INO_SIZE = 8
1295
- LN_OFFSET = 25
1296
- SIZE = 26
1297
-
1298
- def initialize(file, seek)
1299
- @file = file
1300
- @seek = seek
1301
- end
1302
-
1303
- def update(ino, pos)
1304
- @file.pos = @seek
1305
- @file.write "%016x\t%08x" % [pos, ino]
1306
- @inode = ino
1307
- end
1308
-
1309
- def update_pos(pos)
1310
- @file.pos = @seek
1311
- @file.write "%016x" % pos
1312
- end
1313
-
1314
- def read_inode
1315
- @file.pos = @seek + INO_OFFSET
1316
- raw = @file.read(8)
1317
- raw ? raw.to_i(16) : 0
1318
- end
1319
-
1320
- def read_pos
1321
- @file.pos = @seek
1322
- raw = @file.read(16)
1323
- raw ? raw.to_i(16) : 0
1324
- end
1325
- end
1326
-
1327
- class MemoryPositionEntry
1328
- def initialize
1329
- @pos = 0
1330
- @inode = 0
1331
- end
1332
-
1333
- def update(ino, pos)
1334
- @inode = ino
1335
- @pos = pos
1336
- end
1337
-
1338
- def update_pos(pos)
1339
- @pos = pos
1340
- end
1341
-
1342
- def read_pos
1343
- @pos
1344
- end
1345
-
1346
- def read_inode
1347
- @inode
1348
- end
1349
- end
1350
- end
806
+ NewTailInput = TailInput # for backward compatibility
1351
807
  end