fluentd 0.12.40 → 0.14.0

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 (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
@@ -15,218 +15,137 @@
15
15
  #
16
16
 
17
17
  require 'fileutils'
18
- require 'uri'
19
18
 
20
- require 'fluent/env'
21
- require 'fluent/plugin'
22
- require 'fluent/buffer'
19
+ require 'fluent/plugin/buffer'
20
+ require 'fluent/plugin/buffer/file_chunk'
21
+ require 'fluent/system_config'
23
22
 
24
23
  module Fluent
25
- class FileBufferChunk < BufferChunk
26
- def initialize(key, path, unique_id, mode="a+", symlink_path = nil)
27
- super(key)
28
- @path = path
29
- @unique_id = unique_id
30
- @file = File.open(@path, mode, DEFAULT_FILE_PERMISSION)
31
- @file.sync = true
32
- @size = @file.stat.size
33
- FileUtils.ln_sf(@path, symlink_path) if symlink_path
34
- end
35
-
36
- attr_reader :unique_id, :path
37
-
38
- def <<(data)
39
- @file.write(data)
40
- @size += data.bytesize
41
- end
42
-
43
- def size
44
- @size
45
- end
24
+ module Plugin
25
+ class FileBuffer < Fluent::Plugin::Buffer
26
+ Plugin.register_buffer('file', self)
46
27
 
47
- def empty?
48
- @size == 0
49
- end
50
-
51
- def close
52
- stat = @file.stat
53
- @file.close
54
- if stat.size == 0
55
- File.unlink(@path)
56
- end
57
- end
28
+ include SystemConfig::Mixin
58
29
 
59
- def purge
60
- @file.close
61
- File.unlink(@path) rescue nil # TODO rescue?
62
- end
30
+ DEFAULT_CHUNK_LIMIT_SIZE = 256 * 1024 * 1024 # 256MB
31
+ DEFAULT_TOTAL_LIMIT_SIZE = 64 * 1024 * 1024 * 1024 # 64GB, same with v0.12 (TimeSlicedOutput + buf_file)
63
32
 
64
- def read
65
- @file.pos = 0
66
- @file.read
67
- end
33
+ DIR_PERMISSION = 0755
68
34
 
69
- def open(&block)
70
- @file.pos = 0
71
- yield @file
72
- end
35
+ # TODO: buffer_path based on system config
36
+ desc 'The path where buffer chunks are stored.'
37
+ config_param :path, :string
73
38
 
74
- def mv(path)
75
- File.rename(@path, path)
76
- @path = path
77
- end
78
- end
39
+ config_set_default :chunk_limit_size, DEFAULT_CHUNK_LIMIT_SIZE
40
+ config_set_default :total_limit_size, DEFAULT_TOTAL_LIMIT_SIZE
79
41
 
80
- class FileBuffer < BasicBuffer
81
- Plugin.register_buffer('file', self)
42
+ config_param :file_permission, :string, default: nil # '0644'
43
+ config_param :dir_permission, :string, default: nil # '0755'
82
44
 
83
- @@buffer_paths = {}
45
+ ##TODO: Buffer plugin cannot handle symlinks because new API @stage has many writing buffer chunks
46
+ ## re-implement this feature on out_file, w/ enqueue_chunk(or generate_chunk) hook + chunk.path
47
+ # attr_accessor :symlink_path
84
48
 
85
- def initialize
86
- require 'uri'
87
- super
49
+ @@buffer_paths = {}
88
50
 
89
- @uri_parser = URI::Parser.new
90
- end
51
+ def initialize
52
+ super
53
+ @symlink_path = nil
54
+ end
91
55
 
92
- desc 'The path where buffer chunks are stored.'
93
- config_param :buffer_path, :string
94
- desc 'If true, queued chunks are flushed at shutdown process.'
95
- config_param :flush_at_shutdown, :bool, default: false
56
+ def configure(conf)
57
+ super
96
58
 
97
- # 'symlink_path' is currently only for out_file.
98
- # That is the reason why this is not config_param, but attr_accessor.
99
- # See: https://github.com/fluent/fluentd/pull/181
100
- attr_accessor :symlink_path
59
+ type_of_owner = Plugin.lookup_type_from_class(@_owner.class)
60
+ if @@buffer_paths.has_key?(@path) && !buffer_path_for_test?
61
+ type_using_this_path = @@buffer_paths[@path]
62
+ raise ConfigError, "Other '#{type_using_this_path}' plugin already use same buffer path: type = #{type_of_owner}, buffer path = #{@path}"
63
+ end
101
64
 
102
- def configure(conf)
103
- super
65
+ @@buffer_paths[@path] = type_of_owner
66
+
67
+ # TODO: create buffer path with plugin_id, under directory specified by system config
68
+ if File.exist?(@path)
69
+ if File.directory?(@path)
70
+ @path = File.join(@path, 'buffer.*.log')
71
+ elsif File.basename(@path).include?('.*.')
72
+ # valid path (buffer.*.log will be ignored)
73
+ elsif File.basename(@path).end_with?('.*')
74
+ @path = @path + '.log'
75
+ else
76
+ # existing file will be ignored
77
+ @path = @path + '.*.log'
78
+ end
79
+ else # path doesn't exist
80
+ if File.basename(@path).include?('.*.')
81
+ # valid path
82
+ elsif File.basename(@path).end_with?('.*')
83
+ @path = @path + '.log'
84
+ else
85
+ # path is handled as directory, and it will be created at #start
86
+ @path = File.join(@path, 'buffer.*.log')
87
+ end
88
+ end
104
89
 
105
- if @@buffer_paths.has_key?(@buffer_path)
106
- raise ConfigError, "Other '#{@@buffer_paths[@buffer_path]}' plugin already use same buffer_path: type = #{conf['@type'] || conf['type']}, buffer_path = #{@buffer_path}"
107
- else
108
- @@buffer_paths[@buffer_path] = conf['@type'] || conf['type']
90
+ unless @dir_permission
91
+ @dir_permission = system_config.dir_permission || DIR_PERMISSION
92
+ end
109
93
  end
110
94
 
111
- if pos = @buffer_path.index('*')
112
- @buffer_path_prefix = @buffer_path[0, pos]
113
- @buffer_path_suffix = @buffer_path[(pos + 1)..-1]
114
- else
115
- @buffer_path_prefix = @buffer_path + "."
116
- @buffer_path_suffix = ".log"
95
+ def buffer_path_for_test?
96
+ caller_locations.each do |location|
97
+ # Thread::Backtrace::Location#path returns base filename or absolute path.
98
+ # #absolute_path returns absolute_path always.
99
+ # https://bugs.ruby-lang.org/issues/12159
100
+ if location.absolute_path =~ /\/test_[^\/]+\.rb$/ # location.path =~ /test_.+\.rb$/
101
+ return true
102
+ end
103
+ end
104
+ false
117
105
  end
118
106
 
119
- end
120
-
121
- def start
122
- FileUtils.mkdir_p File.dirname(@buffer_path_prefix + "path"), mode: DEFAULT_DIR_PERMISSION
123
- super
124
- end
107
+ def start
108
+ FileUtils.mkdir_p File.dirname(@path), mode: @dir_permission
125
109
 
126
- # Dots are separator for many cases:
127
- # we should have to escape dots in keys...
128
- PATH_MATCH = /^([-_.%0-9a-zA-Z]*)\.(b|q)([0-9a-fA-F]{1,32})$/
110
+ super
111
+ end
129
112
 
130
- def new_chunk(key)
131
- encoded_key = encode_key(key)
132
- path, tsuffix = make_path(encoded_key, "b")
133
- unique_id = tsuffix_to_unique_id(tsuffix)
134
- FileBufferChunk.new(key, path, unique_id, "a+", @symlink_path)
135
- end
113
+ def persistent?
114
+ true
115
+ end
136
116
 
137
- def resume
138
- maps = []
139
- queues = []
140
-
141
- Dir.glob("#{@buffer_path_prefix}*#{@buffer_path_suffix}") {|path|
142
- identifier_part = chunk_identifier_in_path(path)
143
- if m = PATH_MATCH.match(identifier_part)
144
- key = decode_key(m[1])
145
- bq = m[2]
146
- tsuffix = m[3]
147
- timestamp = m[3].to_i(16)
148
- unique_id = tsuffix_to_unique_id(tsuffix)
149
-
150
- if bq == 'b'
151
- chunk = FileBufferChunk.new(key, path, unique_id, "a+")
152
- maps << [timestamp, chunk]
153
- elsif bq == 'q'
154
- chunk = FileBufferChunk.new(key, path, unique_id, "r")
155
- queues << [timestamp, chunk]
117
+ def resume
118
+ stage = {}
119
+ queue = []
120
+
121
+ Dir.glob(@path) do |path|
122
+ m = new_metadata() # this metadata will be overwritten by resuming .meta file content
123
+ # so it should not added into @metadata_list for now
124
+ mode = Fluent::Plugin::Buffer::FileChunk.assume_chunk_state(path)
125
+ chunk = Fluent::Plugin::Buffer::FileChunk.new(m, path, mode) # file chunk resumes contents of metadata
126
+ case chunk.state
127
+ when :staged
128
+ stage[chunk.metadata] = chunk
129
+ when :queued
130
+ queue << chunk
131
+ else
132
+ raise "BUG: unexpected chunk state '#{chunk.state}' for path '#{path}'"
156
133
  end
157
134
  end
158
- }
159
-
160
- map = {}
161
- maps.sort_by {|(timestamp,chunk)|
162
- timestamp
163
- }.each {|(timestamp,chunk)|
164
- map[chunk.key] = chunk
165
- }
166
-
167
- queue = queues.sort_by {|(timestamp,chunk)|
168
- timestamp
169
- }.map {|(timestamp,chunk)|
170
- chunk
171
- }
172
-
173
- return queue, map
174
- end
175
-
176
- def chunk_identifier_in_path(path)
177
- pos_after_prefix = @buffer_path_prefix.length
178
- pos_before_suffix = @buffer_path_suffix.length + 1 # from tail of path
179
-
180
- path.slice(pos_after_prefix..-pos_before_suffix)
181
- end
182
-
183
- def enqueue(chunk)
184
- path = chunk.path
185
- identifier_part = chunk_identifier_in_path(path)
186
135
 
187
- m = PATH_MATCH.match(identifier_part)
188
- encoded_key = m ? m[1] : ""
189
- tsuffix = m[3]
190
- npath = "#{@buffer_path_prefix}#{encoded_key}.q#{tsuffix}#{@buffer_path_suffix}"
136
+ queue.sort_by!{ |chunk| chunk.modified_at }
191
137
 
192
- chunk.mv(npath)
193
- end
138
+ return stage, queue
139
+ end
194
140
 
195
- def before_shutdown(out)
196
- if @flush_at_shutdown
197
- synchronize do
198
- @map.each_key {|key|
199
- push(key)
200
- }
201
- while pop(out)
202
- end
141
+ def generate_chunk(metadata)
142
+ # FileChunk generates real path with unique_id
143
+ if @file_permission
144
+ Fluent::Plugin::Buffer::FileChunk.new(metadata, @path, :create, perm: @file_permission)
145
+ else
146
+ Fluent::Plugin::Buffer::FileChunk.new(metadata, @path, :create)
203
147
  end
204
148
  end
205
149
  end
206
-
207
- private
208
-
209
- # Dots are separator for many cases:
210
- # we should have to escape dots in keys...
211
- def encode_key(key)
212
- @uri_parser.escape(key, /[^-_.a-zA-Z0-9]/n) # //n switch means explicit 'ASCII-8BIT' pattern
213
- end
214
-
215
- def decode_key(encoded_key)
216
- @uri_parser.unescape(encoded_key)
217
- end
218
-
219
- def make_path(encoded_key, bq)
220
- now = Time.now.utc
221
- timestamp = ((now.to_i * 1000 * 1000 + now.usec) << 12 | rand(0xfff))
222
- tsuffix = timestamp.to_s(16)
223
- path = "#{@buffer_path_prefix}#{encoded_key}.#{bq}#{tsuffix}#{@buffer_path_suffix}"
224
- return path, tsuffix
225
- end
226
-
227
- def tsuffix_to_unique_id(tsuffix)
228
- # why *2 ? frsyuki said that I forgot why completely.
229
- tsuffix.scan(/../).map {|x| x.to_i(16) }.pack('C*') * 2
230
- end
231
150
  end
232
151
  end
@@ -14,104 +14,21 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- require 'stringio'
18
-
19
- require 'fluent/engine'
20
- require 'fluent/plugin'
21
- require 'fluent/buffer'
17
+ require 'fluent/plugin/buffer'
18
+ require 'fluent/plugin/buffer/memory_chunk'
22
19
 
23
20
  module Fluent
24
- class MemoryBufferChunk < BufferChunk
25
- def initialize(key, data='')
26
- @data = data
27
- @data.force_encoding('ASCII-8BIT')
28
- now = Time.now.utc
29
- u1 = ((now.to_i*1000*1000+now.usec) << 12 | rand(0xfff))
30
- @unique_id = [u1 >> 32, u1 & 0xffffffff, rand(0xffffffff), rand(0xffffffff)].pack('NNNN')
31
- super(key)
32
- end
33
-
34
- attr_reader :unique_id
35
-
36
- def <<(data)
37
- data.force_encoding('ASCII-8BIT')
38
- @data << data
39
- end
40
-
41
- def size
42
- @data.bytesize
43
- end
44
-
45
- def close
46
- end
47
-
48
- def purge
49
- end
50
-
51
- def read
52
- @data
53
- end
54
-
55
- def open(&block)
56
- StringIO.open(@data, &block)
57
- end
58
-
59
- # optimize
60
- def write_to(io)
61
- io.write @data
62
- end
21
+ module Plugin
22
+ class MemoryBuffer < Fluent::Plugin::Buffer
23
+ Plugin.register_buffer('memory', self)
63
24
 
64
- # optimize
65
- def msgpack_each(&block)
66
- u = Fluent::Engine.msgpack_factory.unpacker
67
- u.feed_each(@data, &block)
68
- end
69
- end
70
-
71
-
72
- class MemoryBuffer < BasicBuffer
73
- Plugin.register_buffer('memory', self)
74
-
75
- def initialize
76
- super
77
- end
78
-
79
- desc 'If true, queued chunks are flushed at shutdown process. Otherwise queued chunks are discarded'
80
- config_param :flush_at_shutdown, :bool, default: true
81
- # Overwrite default BasicBuffer#buffer_queue_limit
82
- # to limit total memory usage upto 512MB.
83
- config_set_default :buffer_queue_limit, 64
84
-
85
- def configure(conf)
86
- super
87
-
88
- unless @flush_at_shutdown
89
- $log.warn "When flush_at_shutdown is false, buf_memory discards buffered chunks at shutdown."
90
- $log.warn "Please confirm 'flush_at_shutdown false' configuration is correct or not."
25
+ def resume
26
+ return {}, []
91
27
  end
92
- end
93
28
 
94
- def before_shutdown(out)
95
- if @flush_at_shutdown
96
- synchronize do
97
- @map.each_key {|key|
98
- push(key)
99
- }
100
- while pop(out)
101
- end
102
- end
29
+ def generate_chunk(metadata)
30
+ Fluent::Plugin::Buffer::MemoryChunk.new(metadata)
103
31
  end
104
32
  end
105
-
106
- def new_chunk(key)
107
- MemoryBufferChunk.new(key)
108
- end
109
-
110
- def resume
111
- return [], {}
112
- end
113
-
114
- def enqueue(chunk)
115
- end
116
33
  end
117
34
  end
@@ -0,0 +1,473 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'fluent/plugin/base'
18
+ require 'fluent/plugin/owned_by_mixin'
19
+ require 'fluent/unique_id'
20
+
21
+ require 'monitor'
22
+
23
+ module Fluent
24
+ module Plugin
25
+ class Buffer < Base
26
+ include OwnedByMixin
27
+ include UniqueId::Mixin
28
+ include MonitorMixin
29
+
30
+ class BufferError < StandardError; end
31
+ class BufferOverflowError < BufferError; end
32
+ class BufferChunkOverflowError < BufferError; end # A record size is larger than chunk size limit
33
+
34
+ MINIMUM_APPEND_ATTEMPT_RECORDS = 10
35
+
36
+ DEFAULT_CHUNK_LIMIT_SIZE = 8 * 1024 * 1024 # 8MB
37
+ DEFAULT_TOTAL_LIMIT_SIZE = 512 * 1024 * 1024 # 512MB, same with v0.12 (BufferedOutput + buf_memory: 64 x 8MB)
38
+
39
+ DEFAULT_CHUNK_FULL_THRESHOLD = 0.95
40
+
41
+ configured_in :buffer
42
+
43
+ # TODO: system total buffer limit size in bytes by SystemConfig
44
+
45
+ config_param :chunk_limit_size, :size, default: DEFAULT_CHUNK_LIMIT_SIZE
46
+ config_param :total_limit_size, :size, default: DEFAULT_TOTAL_LIMIT_SIZE
47
+
48
+ # If user specify this value and (chunk_size * queue_length) is smaller than total_size,
49
+ # then total_size is automatically configured to that value
50
+ config_param :queue_length_limit, :integer, default: nil
51
+
52
+ # optional new limitations
53
+ config_param :chunk_records_limit, :integer, default: nil
54
+
55
+ # if chunk size (or records) is 95% or more after #write, then that chunk will be enqueued
56
+ config_param :chunk_full_threshold, :float, default: DEFAULT_CHUNK_FULL_THRESHOLD
57
+
58
+ Metadata = Struct.new(:timekey, :tag, :variables)
59
+
60
+ # for tests
61
+ attr_accessor :stage_size, :queue_size
62
+ attr_reader :stage, :queue, :dequeued, :queued_num
63
+
64
+ def initialize
65
+ super
66
+
67
+ @chunk_limit_size = nil
68
+ @total_limit_size = nil
69
+ @queue_length_limit = nil
70
+ @chunk_records_limit = nil
71
+
72
+ @stage = {} #=> Hash (metadata -> chunk) : not flushed yet
73
+ @queue = [] #=> Array (chunks) : already flushed (not written)
74
+ @dequeued = {} #=> Hash (unique_id -> chunk): already written (not purged)
75
+ @queued_num = {} # metadata => int (number of queued chunks)
76
+
77
+ @stage_size = @queue_size = 0
78
+ @metadata_list = [] # keys of @stage
79
+ end
80
+
81
+ def persistent?
82
+ false
83
+ end
84
+
85
+ def configure(conf)
86
+ super
87
+
88
+ unless @queue_length_limit.nil?
89
+ @total_limit_size = @chunk_limit_size * @queue_length_limit
90
+ end
91
+ end
92
+
93
+ def start
94
+ super
95
+
96
+ @stage, @queue = resume
97
+ @stage.each_pair do |metadata, chunk|
98
+ @metadata_list << metadata unless @metadata_list.include?(metadata)
99
+ @stage_size += chunk.bytesize
100
+ end
101
+ @queue.each do |chunk|
102
+ @metadata_list << chunk.metadata unless @metadata_list.include?(chunk.metadata)
103
+ @queued_num[chunk.metadata] ||= 0
104
+ @queued_num[chunk.metadata] += 1
105
+ @queue_size += chunk.bytesize
106
+ end
107
+ end
108
+
109
+ def close
110
+ super
111
+ synchronize do
112
+ @dequeued.each_pair do |chunk_id, chunk|
113
+ chunk.close
114
+ end
115
+ until @queue.empty?
116
+ @queue.shift.close
117
+ end
118
+ @stage.each_pair do |metadata, chunk|
119
+ chunk.close
120
+ end
121
+ end
122
+ end
123
+
124
+ def terminate
125
+ super
126
+ @dequeued = @stage = @queue = @queued_num = @metadata_list = nil
127
+ @stage_size = @queue_size = 0
128
+ end
129
+
130
+ def storable?
131
+ @total_limit_size > @stage_size + @queue_size
132
+ end
133
+
134
+ ## TODO: for back pressure feature
135
+ # def used?(ratio)
136
+ # @total_size_limit * ratio > @stage_size + @queue_size
137
+ # end
138
+
139
+ def resume
140
+ # return {}, []
141
+ raise NotImplementedError, "Implement this method in child class"
142
+ end
143
+
144
+ def generate_chunk(metadata)
145
+ raise NotImplementedError, "Implement this method in child class"
146
+ end
147
+
148
+ def metadata_list
149
+ synchronize do
150
+ @metadata_list.dup
151
+ end
152
+ end
153
+
154
+ def new_metadata(timekey: nil, tag: nil, variables: nil)
155
+ Metadata.new(timekey, tag, variables)
156
+ end
157
+
158
+ def add_metadata(metadata)
159
+ synchronize do
160
+ if i = @metadata_list.index(metadata)
161
+ @metadata_list[i]
162
+ else
163
+ @metadata_list << metadata
164
+ metadata
165
+ end
166
+ end
167
+ end
168
+
169
+ def metadata(timekey: nil, tag: nil, variables: nil)
170
+ meta = new_metadata(timekey: timekey, tag: tag, variables: variables)
171
+ add_metadata(meta)
172
+ end
173
+
174
+ # metadata MUST have consistent object_id for each variation
175
+ # data MUST be Array of serialized events
176
+ # metadata_and_data MUST be a hash of { metadata => data }
177
+ def write(metadata_and_data, bulk: false, enqueue: false)
178
+ return if metadata_and_data.size < 1
179
+ raise BufferOverflowError, "buffer space has too many data" unless storable?
180
+
181
+ staged_bytesize = 0
182
+ operated_chunks = []
183
+
184
+ begin
185
+ metadata_and_data.each do |metadata, data|
186
+ write_once(metadata, data, bulk: bulk) do |chunk, adding_bytesize|
187
+ chunk.mon_enter # add lock to prevent to be committed/rollbacked from other threads
188
+ operated_chunks << chunk
189
+ staged_bytesize += adding_bytesize
190
+ end
191
+ end
192
+
193
+ return if operated_chunks.empty?
194
+
195
+ first_chunk = operated_chunks.shift
196
+ # Following commits for other chunks also can finish successfully if the first commit operation
197
+ # finishes without any exceptions.
198
+ # In most cases, #commit just requires very small disk spaces, so major failure reason are
199
+ # permission errors, disk failures and other permanent(fatal) errors.
200
+ begin
201
+ first_chunk.commit
202
+ enqueue_chunk(first_chunk.metadata) if enqueue || chunk_size_full?(first_chunk)
203
+ first_chunk.mon_exit
204
+ rescue
205
+ operated_chunks.unshift(first_chunk)
206
+ raise
207
+ end
208
+
209
+ errors = []
210
+ # Buffer plugin estimates there's no serious error cause: will commit for all chunks eigher way
211
+ operated_chunks.each do |chunk|
212
+ begin
213
+ chunk.commit
214
+ enqueue_chunk(chunk.metadata) if enqueue || chunk_size_full?(chunk)
215
+ chunk.mon_exit
216
+ rescue => e
217
+ chunk.rollback
218
+ chunk.mon_exit
219
+ errors << e
220
+ end
221
+ end
222
+ operated_chunks.clear if errors.empty?
223
+
224
+ @stage_size += staged_bytesize
225
+
226
+ if errors.size > 0
227
+ log.warn "error occurs in committing chunks: only first one raised", errors: errors.map(&:class)
228
+ raise errors.first
229
+ end
230
+ ensure
231
+ operated_chunks.each do |chunk|
232
+ chunk.rollback rescue nil # nothing possible to do for #rollback failure
233
+ chunk.mon_exit rescue nil # this may raise ThreadError for chunks already committed
234
+ end
235
+ end
236
+ end
237
+
238
+ def queued_records
239
+ synchronize { @queue.reduce(0){|r, chunk| r + chunk.size } }
240
+ end
241
+
242
+ def queued?(metadata=nil)
243
+ synchronize do
244
+ if metadata
245
+ n = @queued_num[metadata]
246
+ n && n.nonzero?
247
+ else
248
+ !@queue.empty?
249
+ end
250
+ end
251
+ end
252
+
253
+ def enqueue_chunk(metadata)
254
+ synchronize do
255
+ chunk = @stage.delete(metadata)
256
+ return nil unless chunk
257
+
258
+ chunk.synchronize do
259
+ if chunk.empty?
260
+ chunk.close
261
+ else
262
+ @queue << chunk
263
+ @queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
264
+ chunk.enqueued! if chunk.respond_to?(:enqueued!)
265
+ end
266
+ end
267
+ bytesize = chunk.bytesize
268
+ @stage_size -= bytesize
269
+ @queue_size += bytesize
270
+ end
271
+ nil
272
+ end
273
+
274
+ def enqueue_all
275
+ synchronize do
276
+ if block_given?
277
+ @stage.keys.each do |metadata|
278
+ chunk = @stage[metadata]
279
+ v = yield metadata, chunk
280
+ enqueue_chunk(metadata) if v
281
+ end
282
+ else
283
+ @stage.keys.each do |metadata|
284
+ enqueue_chunk(metadata)
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+ def dequeue_chunk
291
+ return nil if @queue.empty?
292
+ synchronize do
293
+ chunk = @queue.shift
294
+
295
+ # this buffer is dequeued by other thread just before "synchronize" in this thread
296
+ return nil unless chunk
297
+
298
+ @dequeued[chunk.unique_id] = chunk
299
+ @queued_num[chunk.metadata] -= 1 # BUG if nil, 0 or subzero
300
+ chunk
301
+ end
302
+ end
303
+
304
+ def takeback_chunk(chunk_id)
305
+ synchronize do
306
+ chunk = @dequeued.delete(chunk_id)
307
+ return false unless chunk # already purged by other thread
308
+ @queue.unshift(chunk)
309
+ @queued_num[chunk.metadata] += 1 # BUG if nil
310
+ end
311
+ true
312
+ end
313
+
314
+ def purge_chunk(chunk_id)
315
+ synchronize do
316
+ chunk = @dequeued.delete(chunk_id)
317
+ return nil unless chunk # purged by other threads
318
+
319
+ metadata = chunk.metadata
320
+ begin
321
+ bytesize = chunk.bytesize
322
+ chunk.purge
323
+ @queue_size -= bytesize
324
+ rescue => e
325
+ log.error "failed to purge buffer chunk", chunk_id: dump_unique_id_hex(chunk_id), error_class: e.class, error: e
326
+ end
327
+
328
+ if metadata && !@stage[metadata] && (!@queued_num[metadata] || @queued_num[metadata] < 1)
329
+ @metadata_list.delete(metadata)
330
+ end
331
+ end
332
+ nil
333
+ end
334
+
335
+ def clear_queue!
336
+ synchronize do
337
+ until @queue.empty?
338
+ begin
339
+ q = @queue.shift
340
+ log.debug("purging a chunk in queue"){ {id: dump_unique_id_hex(chunk.unique_id), bytesize: chunk.bytesize, size: chunk.size} }
341
+ q.purge
342
+ rescue => e
343
+ log.error "unexpected error while clearing buffer queue", error_class: e.class, error: e
344
+ end
345
+ end
346
+ @queue_size = 0
347
+ end
348
+ end
349
+
350
+ def chunk_size_over?(chunk)
351
+ chunk.bytesize > @chunk_limit_size || (@chunk_records_limit && chunk.size > @chunk_records_limit)
352
+ end
353
+
354
+ def chunk_size_full?(chunk)
355
+ chunk.bytesize >= @chunk_limit_size * @chunk_full_threshold || (@chunk_records_limit && chunk.size >= @chunk_records_limit * @chunk_full_threshold)
356
+ end
357
+
358
+ class ShouldRetry < StandardError; end
359
+
360
+ def write_once(metadata, data, bulk: false, &block)
361
+ return if !bulk && (data.nil? || data.empty?)
362
+ return if bulk && (data.empty? || data.first.nil? || data.first.empty?)
363
+
364
+ stored = false
365
+ adding_bytesize = nil
366
+
367
+ chunk = synchronize { @stage[metadata] ||= generate_chunk(metadata) }
368
+ enqueue_list = []
369
+
370
+ chunk.synchronize do
371
+ # retry this method if chunk is already queued (between getting chunk and entering critical section)
372
+ raise ShouldRetry unless chunk.staged?
373
+
374
+ empty_chunk = chunk.empty?
375
+
376
+ original_bytesize = chunk.bytesize
377
+ begin
378
+ if bulk
379
+ content, size = data
380
+ chunk.concat(content, size)
381
+ else
382
+ chunk.append(data)
383
+ end
384
+ adding_bytesize = chunk.bytesize - original_bytesize
385
+
386
+ if chunk_size_over?(chunk)
387
+ if empty_chunk && bulk
388
+ log.warn "chunk bytes limit exceeds for a bulk event stream: #{bulk.bytesize}bytes"
389
+ stored = true
390
+ else
391
+ chunk.rollback
392
+ end
393
+ else
394
+ stored = true
395
+ end
396
+ rescue
397
+ chunk.rollback
398
+ raise
399
+ end
400
+
401
+ if stored
402
+ block.call(chunk, adding_bytesize)
403
+ elsif bulk
404
+ # this metadata might be enqueued already by other threads
405
+ # but #enqueue_chunk does nothing in such case
406
+ enqueue_list << metadata
407
+ raise ShouldRetry
408
+ end
409
+ end
410
+
411
+ unless stored
412
+ # try step-by-step appending if data can't be stored into existing a chunk in non-bulk mode
413
+ write_step_by_step(metadata, data, data.size / 3, &block)
414
+ end
415
+ rescue ShouldRetry
416
+ enqueue_list.each do |metadata|
417
+ enqueue_chunk(metadata)
418
+ end
419
+ retry
420
+ end
421
+
422
+ def write_step_by_step(metadata, data, attempt_records, &block)
423
+ while data.size > 0
424
+ if attempt_records < MINIMUM_APPEND_ATTEMPT_RECORDS
425
+ attempt_records = MINIMUM_APPEND_ATTEMPT_RECORDS
426
+ end
427
+
428
+ chunk = synchronize{ @stage[metadata] ||= generate_chunk(metadata) }
429
+ chunk.synchronize do # critical section for chunk (chunk append/commit/rollback)
430
+ raise ShouldRetry unless chunk.staged?
431
+ begin
432
+ empty_chunk = chunk.empty?
433
+ original_bytesize = chunk.bytesize
434
+
435
+ attempt = data.slice(0, attempt_records)
436
+ chunk.append(attempt)
437
+ adding_bytesize = (chunk.bytesize - original_bytesize)
438
+
439
+ if chunk_size_over?(chunk)
440
+ chunk.rollback
441
+
442
+ if attempt_records <= MINIMUM_APPEND_ATTEMPT_RECORDS
443
+ if empty_chunk # record is too large even for empty chunk
444
+ raise BufferChunkOverflowError, "minimum append butch exceeds chunk bytes limit"
445
+ end
446
+ # no more records for this chunk -> enqueue -> to be flushed
447
+ enqueue_chunk(metadata) # `chunk` will be removed from stage
448
+ attempt_records = data.size # fresh chunk may have enough space
449
+ else
450
+ # whole data can be processed by twice operation
451
+ # ( by using apttempt /= 2, 3 operations required for odd numbers of data)
452
+ attempt_records = (attempt_records / 2) + 1
453
+ end
454
+
455
+ next
456
+ end
457
+
458
+ block.call(chunk, adding_bytesize)
459
+ data.slice!(0, attempt_records)
460
+ # same attempt size
461
+ nil # discard return value of data.slice!() immediately
462
+ rescue
463
+ chunk.rollback
464
+ raise
465
+ end
466
+ end
467
+ end
468
+ rescue ShouldRetry
469
+ retry
470
+ end # write_step_by_step
471
+ end
472
+ end
473
+ end