fluent-plugin-webhdfs 0.5.3 → 0.6.0rc1
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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -10
- data/fluent-plugin-webhdfs.gemspec +3 -4
- data/lib/fluent/plugin/out_webhdfs.rb +239 -95
- data/lib/fluent/plugin/webhdfs_compressor_bzip2.rb +2 -2
- data/lib/fluent/plugin/webhdfs_compressor_gzip.rb +2 -2
- data/lib/fluent/plugin/webhdfs_compressor_lzo_command.rb +3 -3
- data/lib/fluent/plugin/webhdfs_compressor_snappy.rb +2 -2
- data/lib/fluent/plugin/webhdfs_compressor_text.rb +2 -2
- data/test/helper.rb +5 -0
- data/test/plugin/test_compressor.rb +3 -3
- data/test/plugin/test_out_webhdfs.rb +181 -92
- metadata +15 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e92e9863c1ee9908d2ee0076b3651b7198062511
|
|
4
|
+
data.tar.gz: b4cf49b2c59fd07f9cb5a3f7faecf165d60723b7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9c1ea8130b845859ea7c9dd2504f03c176e6162d24636ca4ee27c12d36ac2e32dbcf962aaca77e394e3384d007a5328d974742ae258555130ff8ecf9384b2a6
|
|
7
|
+
data.tar.gz: 7afef45b797ba03cea394895dc3c6dd327b30339f825d3e2291dec5097b126dd803cd476e1b8420183141ff3f268da602623b0945a71c2f5261fb9b8a18062eb
|
data/.travis.yml
CHANGED
|
@@ -2,10 +2,9 @@ sudo: false
|
|
|
2
2
|
language: ruby
|
|
3
3
|
|
|
4
4
|
rvm:
|
|
5
|
-
- 2.0.0
|
|
6
5
|
- 2.1
|
|
7
6
|
- 2.2
|
|
8
|
-
- 2.3.
|
|
7
|
+
- 2.3.1
|
|
9
8
|
|
|
10
9
|
branches:
|
|
11
10
|
only:
|
|
@@ -23,12 +22,4 @@ script: bundle exec rake test
|
|
|
23
22
|
|
|
24
23
|
gemfile:
|
|
25
24
|
- Gemfile
|
|
26
|
-
- gemfiles/fluentd_v0.12.gemfile
|
|
27
25
|
- gemfiles/fluentd_v0.14.gemfile
|
|
28
|
-
|
|
29
|
-
matrix:
|
|
30
|
-
exclude:
|
|
31
|
-
- rvm: 2.0.0
|
|
32
|
-
gemfile: Gemfile
|
|
33
|
-
- rvm: 2.0.0
|
|
34
|
-
gemfile: gemfiles/fluentd_v0.14.gemfile
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |gem|
|
|
4
4
|
gem.name = "fluent-plugin-webhdfs"
|
|
5
|
-
gem.version = "0.
|
|
5
|
+
gem.version = "0.6.0rc1"
|
|
6
6
|
gem.authors = ["TAGOMORI Satoshi"]
|
|
7
7
|
gem.email = ["tagomoris@gmail.com"]
|
|
8
8
|
gem.summary = %q{Fluentd plugin to write data on HDFS over WebHDFS, with flexible formatting}
|
|
@@ -17,11 +17,10 @@ Gem::Specification.new do |gem|
|
|
|
17
17
|
|
|
18
18
|
gem.add_development_dependency "rake"
|
|
19
19
|
gem.add_development_dependency "test-unit"
|
|
20
|
+
gem.add_development_dependency "test-unit-rr"
|
|
20
21
|
gem.add_development_dependency "appraisal"
|
|
21
22
|
gem.add_development_dependency "snappy", '>= 0.0.13'
|
|
22
|
-
gem.add_runtime_dependency "fluentd",
|
|
23
|
-
gem.add_runtime_dependency "fluent-mixin-plaintextformatter", '>= 0.2.1'
|
|
24
|
-
gem.add_runtime_dependency "fluent-mixin-config-placeholders", ">= 0.3.0"
|
|
23
|
+
gem.add_runtime_dependency "fluentd", '>= 0.14.4'
|
|
25
24
|
gem.add_runtime_dependency "webhdfs", '>= 0.6.0'
|
|
26
25
|
gem.add_runtime_dependency "bzip2-ffi"
|
|
27
26
|
end
|
|
@@ -1,139 +1,150 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'fluent/plugin/output'
|
|
4
|
+
require 'fluent/config/element'
|
|
4
5
|
|
|
5
|
-
require '
|
|
6
|
-
require '
|
|
6
|
+
require 'webhdfs'
|
|
7
|
+
require 'tempfile'
|
|
8
|
+
require 'securerandom'
|
|
7
9
|
|
|
8
|
-
class Fluent::WebHDFSOutput < Fluent::
|
|
10
|
+
class Fluent::Plugin::WebHDFSOutput < Fluent::Plugin::Output
|
|
9
11
|
Fluent::Plugin.register_output('webhdfs', self)
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
config_set_default :time_slice_format, '%Y%m%d'
|
|
13
|
-
|
|
14
|
-
# For fluentd v0.12.16 or earlier
|
|
15
|
-
class << self
|
|
16
|
-
unless method_defined?(:desc)
|
|
17
|
-
def desc(description)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
13
|
+
helpers :inject, :formatter, :compat_parameters
|
|
21
14
|
|
|
22
15
|
desc 'WebHDFS/HttpFs host'
|
|
23
|
-
config_param :host, :string, :
|
|
16
|
+
config_param :host, :string, default: nil
|
|
24
17
|
desc 'WebHDFS/HttpFs port'
|
|
25
|
-
config_param :port, :integer, :
|
|
18
|
+
config_param :port, :integer, default: 50070
|
|
26
19
|
desc 'Namenode (host:port)'
|
|
27
|
-
config_param :namenode, :string, :
|
|
20
|
+
config_param :namenode, :string, default: nil # host:port
|
|
28
21
|
desc 'Standby namenode for Namenode HA (host:port)'
|
|
29
|
-
config_param :standby_namenode, :string, :
|
|
22
|
+
config_param :standby_namenode, :string, default: nil # host:port
|
|
30
23
|
|
|
31
24
|
desc 'Ignore errors on start up'
|
|
32
|
-
config_param :ignore_start_check_error, :bool, :
|
|
33
|
-
|
|
34
|
-
include Fluent::Mixin::ConfigPlaceholders
|
|
25
|
+
config_param :ignore_start_check_error, :bool, default: false
|
|
35
26
|
|
|
36
27
|
desc 'Output file path on HDFS'
|
|
37
28
|
config_param :path, :string
|
|
38
29
|
desc 'User name for pseudo authentication'
|
|
39
|
-
config_param :username, :string, :
|
|
30
|
+
config_param :username, :string, default: nil
|
|
40
31
|
|
|
41
32
|
desc 'Store data over HttpFs instead of WebHDFS'
|
|
42
|
-
config_param :httpfs, :bool, :
|
|
33
|
+
config_param :httpfs, :bool, default: false
|
|
43
34
|
|
|
44
35
|
desc 'Number of seconds to wait for the connection to open'
|
|
45
|
-
config_param :open_timeout, :integer, :
|
|
36
|
+
config_param :open_timeout, :integer, default: 30 # from ruby net/http default
|
|
46
37
|
desc 'Number of seconds to wait for one block to be read'
|
|
47
|
-
config_param :read_timeout, :integer, :
|
|
38
|
+
config_param :read_timeout, :integer, default: 60 # from ruby net/http default
|
|
48
39
|
|
|
49
40
|
desc 'Retry automatically when known errors of HDFS are occurred'
|
|
50
|
-
config_param :retry_known_errors, :bool, :
|
|
41
|
+
config_param :retry_known_errors, :bool, default: false
|
|
51
42
|
desc 'Retry interval'
|
|
52
|
-
config_param :retry_interval, :integer, :
|
|
43
|
+
config_param :retry_interval, :integer, default: nil
|
|
53
44
|
desc 'The number of retries'
|
|
54
|
-
config_param :retry_times, :integer, :
|
|
45
|
+
config_param :retry_times, :integer, default: nil
|
|
55
46
|
|
|
56
47
|
# how many times of write failure before switch to standby namenode
|
|
57
48
|
# by default it's 11 times that costs 1023 seconds inside fluentd,
|
|
58
49
|
# which is considered enough to exclude the scenes that caused by temporary network fail or single datanode fail
|
|
59
50
|
desc 'How many times of write failure before switch to standby namenode'
|
|
60
|
-
config_param :failures_before_use_standby, :integer, :
|
|
61
|
-
|
|
62
|
-
include Fluent::Mixin::PlainTextFormatter
|
|
51
|
+
config_param :failures_before_use_standby, :integer, default: 11
|
|
63
52
|
|
|
64
|
-
config_param :
|
|
53
|
+
config_param :end_with_newline, :bool, default: true
|
|
65
54
|
|
|
66
55
|
desc 'Append data or not'
|
|
67
|
-
config_param :append, :bool, :
|
|
56
|
+
config_param :append, :bool, default: true
|
|
68
57
|
|
|
69
58
|
desc 'Use SSL or not'
|
|
70
|
-
config_param :ssl, :bool, :
|
|
59
|
+
config_param :ssl, :bool, default: false
|
|
71
60
|
desc 'OpenSSL certificate authority file'
|
|
72
|
-
config_param :ssl_ca_file, :string, :
|
|
61
|
+
config_param :ssl_ca_file, :string, default: nil
|
|
73
62
|
desc 'OpenSSL verify mode (none,peer)'
|
|
74
|
-
config_param :ssl_verify_mode, :
|
|
75
|
-
case val
|
|
76
|
-
when 'none'
|
|
77
|
-
:none
|
|
78
|
-
when 'peer'
|
|
79
|
-
:peer
|
|
80
|
-
else
|
|
81
|
-
raise Fluent::ConfigError, "unexpected parameter on ssl_verify_mode: #{val}"
|
|
82
|
-
end
|
|
83
|
-
end
|
|
63
|
+
config_param :ssl_verify_mode, :enum, list: [:none, :peer], default: :none
|
|
84
64
|
|
|
85
65
|
desc 'Use kerberos authentication or not'
|
|
86
|
-
config_param :kerberos, :bool, :
|
|
66
|
+
config_param :kerberos, :bool, default: false
|
|
87
67
|
|
|
88
|
-
SUPPORTED_COMPRESS = [
|
|
68
|
+
SUPPORTED_COMPRESS = [:gzip, :bzip2, :snappy, :lzo_command, :text]
|
|
89
69
|
desc "Compress method (#{SUPPORTED_COMPRESS.join(',')})"
|
|
90
|
-
config_param :compress, :
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
70
|
+
config_param :compress, :enum, list: SUPPORTED_COMPRESS, default: :text
|
|
71
|
+
|
|
72
|
+
config_param :remove_prefix, :string, default: nil, deprecated: "use @label for routing"
|
|
73
|
+
config_param :default_tag, :string, default: nil, deprecated: "use @label for routing"
|
|
74
|
+
config_param :null_value, :string, default: nil, deprecated: "use filter plugins to convert null values into any specified string"
|
|
75
|
+
config_param :suppress_log_broken_string, :bool, default: false, deprecated: "use @log_level for plugin to suppress such info logs"
|
|
96
76
|
|
|
97
77
|
CHUNK_ID_PLACE_HOLDER = '${chunk_id}'
|
|
98
78
|
|
|
99
|
-
|
|
79
|
+
config_section :buffer do
|
|
80
|
+
config_set_default :chunk_keys, ["time"]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
config_section :format do
|
|
84
|
+
config_set_default :@type, 'out_file'
|
|
85
|
+
config_set_default :localtime, false # default timezone is UTC
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
attr_reader :formatter, :compressor
|
|
100
89
|
|
|
101
90
|
def initialize
|
|
102
91
|
super
|
|
103
|
-
require 'net/http'
|
|
104
|
-
require 'time'
|
|
105
|
-
require 'webhdfs'
|
|
106
|
-
|
|
107
92
|
@compressor = nil
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
unless method_defined?(:log)
|
|
112
|
-
define_method("log") { $log }
|
|
93
|
+
@standby_namenode_host = nil
|
|
94
|
+
@output_include_tag = @output_include_time = nil # TODO: deprecated
|
|
95
|
+
@header_separator = @field_separator = nil # TODO: deprecated
|
|
113
96
|
end
|
|
114
97
|
|
|
115
98
|
def configure(conf)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
99
|
+
compat_parameters_convert(conf, :buffer, default_chunk_key: "time")
|
|
100
|
+
|
|
101
|
+
timekey = case conf["path"]
|
|
102
|
+
when /%S/ then 1
|
|
103
|
+
when /%M/ then 60
|
|
104
|
+
when /%H/ then 3600
|
|
105
|
+
else 86400
|
|
106
|
+
end
|
|
107
|
+
if conf.elements(name: "buffer").empty?
|
|
108
|
+
e = Fluent::Config::Element.new("buffer", "time", {}, [])
|
|
109
|
+
conf.elements << e
|
|
124
110
|
end
|
|
111
|
+
buffer_config = conf.elements(name: "buffer").first
|
|
112
|
+
buffer_config["timekey"] = timekey unless buffer_config["timekey"]
|
|
113
|
+
|
|
114
|
+
compat_parameters_convert_plaintextformatter(conf)
|
|
125
115
|
|
|
126
116
|
super
|
|
127
117
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@
|
|
118
|
+
@formatter = formatter_create
|
|
119
|
+
|
|
120
|
+
if @using_formatter_config
|
|
121
|
+
@null_value = nil
|
|
122
|
+
else
|
|
123
|
+
@formatter.delimiter = "\x01" if @formatter.respond_to?(:delimiter) && @formatter.delimiter == 'SOH'
|
|
124
|
+
@null_value ||= 'NULL'
|
|
135
125
|
end
|
|
136
126
|
|
|
127
|
+
if @default_tag.nil? && !@using_formatter_config && @output_include_tag
|
|
128
|
+
@default_tag = "tag_missing"
|
|
129
|
+
end
|
|
130
|
+
if @remove_prefix
|
|
131
|
+
@remove_prefix_actual = @remove_prefix + "."
|
|
132
|
+
@remove_prefix_actual_length = @remove_prefix_actual.length
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
verify_config_placeholders_in_path!(conf)
|
|
136
|
+
@replace_random_uuid = @path.include?('%{uuid}') || @path.include?('%{uuid_flush}')
|
|
137
|
+
if @replace_random_uuid
|
|
138
|
+
# to check SecureRandom.uuid is available or not (NotImplementedError raised in such environment)
|
|
139
|
+
begin
|
|
140
|
+
SecureRandom.uuid
|
|
141
|
+
rescue
|
|
142
|
+
raise Fluent::ConfigError, "uuid feature (SecureRandom) is unavailable in this environment"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
@compressor = COMPRESSOR_REGISTRY.lookup(@compress.to_s).new
|
|
147
|
+
|
|
137
148
|
if @host
|
|
138
149
|
@namenode_host = @host
|
|
139
150
|
@namenode_port = @port
|
|
@@ -167,7 +178,7 @@ class Fluent::WebHDFSOutput < Fluent::TimeSlicedOutput
|
|
|
167
178
|
@client_standby = nil
|
|
168
179
|
end
|
|
169
180
|
|
|
170
|
-
|
|
181
|
+
unless @append
|
|
171
182
|
if @path.index(CHUNK_ID_PLACE_HOLDER).nil?
|
|
172
183
|
raise Fluent::ConfigError, "path must contain ${chunk_id}, which is the placeholder for chunk_id, when append is set to false."
|
|
173
184
|
end
|
|
@@ -230,14 +241,6 @@ class Fluent::WebHDFSOutput < Fluent::TimeSlicedOutput
|
|
|
230
241
|
end
|
|
231
242
|
end
|
|
232
243
|
|
|
233
|
-
def shutdown
|
|
234
|
-
super
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def path_format(chunk_key)
|
|
238
|
-
Time.strptime(chunk_key, @time_slice_format).strftime(@path)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
244
|
def is_standby_exception(e)
|
|
242
245
|
e.is_a?(WebHDFS::IOError) && e.message.match(/org\.apache\.hadoop\.ipc\.StandbyException/)
|
|
243
246
|
end
|
|
@@ -249,12 +252,6 @@ class Fluent::WebHDFSOutput < Fluent::TimeSlicedOutput
|
|
|
249
252
|
end
|
|
250
253
|
end
|
|
251
254
|
|
|
252
|
-
def chunk_unique_id_to_str(unique_id)
|
|
253
|
-
unique_id.unpack('C*').map{|x| x.to_s(16).rjust(2,'0')}.join('')
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# TODO check conflictions
|
|
257
|
-
|
|
258
255
|
def send_data(path, data)
|
|
259
256
|
if @append
|
|
260
257
|
begin
|
|
@@ -267,13 +264,52 @@ class Fluent::WebHDFSOutput < Fluent::TimeSlicedOutput
|
|
|
267
264
|
end
|
|
268
265
|
end
|
|
269
266
|
|
|
267
|
+
HOSTNAME_PLACEHOLDERS_DEPRECATED = ['${hostname}', '%{hostname}', '__HOSTNAME__']
|
|
268
|
+
UUID_RANDOM_PLACEHOLDERS_DEPRECATED = ['${uuid}', '${uuid:random}', '__UUID__', '__UUID_RANDOM__']
|
|
269
|
+
UUID_OTHER_PLACEHOLDERS_OBSOLETED = ['${uuid:hostname}', '%{uuid:hostname}', '__UUID_HOSTNAME__', '${uuid:timestamp}', '%{uuid:timestamp}', '__UUID_TIMESTAMP__']
|
|
270
|
+
|
|
271
|
+
def verify_config_placeholders_in_path!(conf)
|
|
272
|
+
return unless conf.has_key?('path')
|
|
273
|
+
|
|
274
|
+
path = conf['path']
|
|
275
|
+
|
|
276
|
+
# check @path for ${hostname}, %{hostname} and __HOSTNAME__ to warn to use #{Socket.gethostbyname}
|
|
277
|
+
if HOSTNAME_PLACEHOLDERS_DEPRECATED.any?{|ph| path.include?(ph) }
|
|
278
|
+
log.warn "hostname placeholder is now deprecated. use '\#\{Socket.gethostname\}' instead."
|
|
279
|
+
hostname = conf['hostname'] || Socket.gethostname
|
|
280
|
+
HOSTNAME_PLACEHOLDERS_DEPRECATED.each do |ph|
|
|
281
|
+
path.gsub!(ph, hostname)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
if UUID_RANDOM_PLACEHOLDERS_DEPRECATED.any?{|ph| path.include?(ph) }
|
|
286
|
+
log.warn "random uuid placeholders are now deprecated. use %{uuid} (or %{uuid_flush}) instead."
|
|
287
|
+
UUID_RANDOM_PLACEHOLDERS_DEPRECATED.each do |ph|
|
|
288
|
+
path.gsub!(ph, '%{uuid}')
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
if UUID_OTHER_PLACEHOLDERS_OBSOLETED.any?{|ph| path.include?(ph) }
|
|
293
|
+
UUID_OTHER_PLACEHOLDERS_OBSOLETED.each do |ph|
|
|
294
|
+
if path.include?(ph)
|
|
295
|
+
log.error "configuration placeholder #{ph} is now unsupported by webhdfs output plugin."
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
raise ConfigError, "there are unsupported placeholders in path."
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
270
302
|
def generate_path(chunk)
|
|
271
303
|
hdfs_path = if @append
|
|
272
|
-
|
|
304
|
+
extract_placeholders(@path, chunk.metadata)
|
|
273
305
|
else
|
|
274
|
-
|
|
306
|
+
extract_placeholders(@path, chunk.metadata).gsub(CHUNK_ID_PLACE_HOLDER, dump_unique_id(chunk.unique_id))
|
|
275
307
|
end
|
|
276
308
|
hdfs_path = "#{hdfs_path}#{@compressor.ext}"
|
|
309
|
+
if @replace_random_uuid
|
|
310
|
+
uuid_random = SecureRandom.uuid
|
|
311
|
+
hdfs_path.gsub!('%{uuid}', uuid_random).gsub!('%{uuid_flush}', uuid_random)
|
|
312
|
+
end
|
|
277
313
|
hdfs_path
|
|
278
314
|
end
|
|
279
315
|
|
|
@@ -288,6 +324,48 @@ class Fluent::WebHDFSOutput < Fluent::TimeSlicedOutput
|
|
|
288
324
|
end
|
|
289
325
|
end
|
|
290
326
|
|
|
327
|
+
def format(tag, time, record)
|
|
328
|
+
if @remove_prefix # TODO: remove when it's obsoleted
|
|
329
|
+
if tag.start_with?(@remove_prefix_actual)
|
|
330
|
+
if tag.length > @remove_prefix_actual_length
|
|
331
|
+
tag = tag[@remove_prefix_actual_length..-1]
|
|
332
|
+
else
|
|
333
|
+
tag = @default_tag
|
|
334
|
+
end
|
|
335
|
+
elsif tag.start_with?(@remove_prefix)
|
|
336
|
+
if tag == @remove_prefix
|
|
337
|
+
tag = @default_tag
|
|
338
|
+
else
|
|
339
|
+
tag = tag.sub(@remove_prefix, '')
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
if @null_value # TODO: remove when it's obsoleted
|
|
345
|
+
check_keys = (record.keys + @null_convert_keys).uniq
|
|
346
|
+
check_keys.each do |key|
|
|
347
|
+
record[key] = @null_value if record[key].nil?
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
if @using_formatter_config
|
|
352
|
+
record = inject_values_to_record(tag, time, record)
|
|
353
|
+
line = @formatter.format(tag, time, record)
|
|
354
|
+
else # TODO: remove when it's obsoleted
|
|
355
|
+
time_str = @output_include_time ? @time_formatter.call(time) + @header_separator : ''
|
|
356
|
+
tag_str = @output_include_tag ? tag + @header_separator : ''
|
|
357
|
+
record_str = @formatter.format(tag, time, record)
|
|
358
|
+
line = time_str + tag_str + record_str
|
|
359
|
+
end
|
|
360
|
+
line << "\n" if @end_with_newline && !line.end_with?("\n")
|
|
361
|
+
line
|
|
362
|
+
rescue => e # remove this clause when @suppress_log_broken_string is obsoleted
|
|
363
|
+
unless @suppress_log_broken_string
|
|
364
|
+
log.info "unexpected error while formatting events, ignored", tag: tag, record: record, error: e
|
|
365
|
+
end
|
|
366
|
+
''
|
|
367
|
+
end
|
|
368
|
+
|
|
291
369
|
def write(chunk)
|
|
292
370
|
hdfs_path = generate_path(chunk)
|
|
293
371
|
|
|
@@ -318,6 +396,72 @@ class Fluent::WebHDFSOutput < Fluent::TimeSlicedOutput
|
|
|
318
396
|
hdfs_path
|
|
319
397
|
end
|
|
320
398
|
|
|
399
|
+
def compat_parameters_convert_plaintextformatter(conf)
|
|
400
|
+
if !conf.elements('format').empty? || !conf['output_data_type']
|
|
401
|
+
@using_formatter_config = true
|
|
402
|
+
@null_convert_keys = []
|
|
403
|
+
return
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
log.warn "webhdfs output plugin is working with old configuration parameters. use <inject>/<format> sections instead for further releases."
|
|
407
|
+
@using_formatter_config = false
|
|
408
|
+
@null_convert_keys = []
|
|
409
|
+
|
|
410
|
+
@header_separator = case conf['field_separator']
|
|
411
|
+
when nil then "\t"
|
|
412
|
+
when 'SPACE' then ' '
|
|
413
|
+
when 'TAB' then "\t"
|
|
414
|
+
when 'COMMA' then ','
|
|
415
|
+
when 'SOH' then "\x01"
|
|
416
|
+
else conf['field_separator']
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
format_section = Fluent::Config::Element.new('format', '', {}, [])
|
|
420
|
+
case conf['output_data_type']
|
|
421
|
+
when '', 'json' # blank value is for compatibility reason (especially in testing)
|
|
422
|
+
format_section['@type'] = 'json'
|
|
423
|
+
when 'ltsv'
|
|
424
|
+
format_section['@type'] = 'ltsv'
|
|
425
|
+
else
|
|
426
|
+
unless conf['output_data_type'].start_with?('attr:')
|
|
427
|
+
raise Fluent::ConfigError, "output_data_type is invalid: #{conf['output_data_type']}"
|
|
428
|
+
end
|
|
429
|
+
format_section['@format'] = 'tsv'
|
|
430
|
+
keys_part = conf['output_data_type'].sub(/^attr:/, '')
|
|
431
|
+
@null_convert_keys = keys_part.split(',')
|
|
432
|
+
format_section['keys'] = keys_part
|
|
433
|
+
format_section['delimiter'] = case conf['field_separator']
|
|
434
|
+
when nil then '\t'
|
|
435
|
+
when 'SPACE' then ' '
|
|
436
|
+
when 'TAB' then '\t'
|
|
437
|
+
when 'COMMA' then ','
|
|
438
|
+
when 'SOH' then 'SOH' # fixed later
|
|
439
|
+
else conf['field_separator']
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
conf.elements << format_section
|
|
444
|
+
|
|
445
|
+
@output_include_time = conf.has_key?('output_include_time') ? Fluent::Config.bool_value(conf['output_include_time']) : true
|
|
446
|
+
@output_include_tag = conf.has_key?('output_include_tag') ? Fluent::Config.bool_value(conf['output_include_tag']) : true
|
|
447
|
+
|
|
448
|
+
if @output_include_time
|
|
449
|
+
# default timezone is UTC
|
|
450
|
+
using_localtime = if !conf.has_key?('utc') && !conf.has_key?('localtime')
|
|
451
|
+
false
|
|
452
|
+
elsif conf.has_key?('localtime') && conf.has_key?('utc')
|
|
453
|
+
raise Fluent::ConfigError, "specify either 'localtime' or 'utc'"
|
|
454
|
+
elsif conf.has_key?('localtime')
|
|
455
|
+
Fluent::Config.bool_value('localtime')
|
|
456
|
+
else
|
|
457
|
+
Fluent::Config.bool_value('utc')
|
|
458
|
+
end
|
|
459
|
+
@time_formatter = Fluent::TimeFormatter.new(conf['time_format'], using_localtime)
|
|
460
|
+
else
|
|
461
|
+
@time_formatter = nil
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
321
465
|
class Compressor
|
|
322
466
|
include Fluent::Configurable
|
|
323
467
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module Fluent
|
|
2
|
-
class WebHDFSOutput
|
|
1
|
+
module Fluent::Plugin
|
|
2
|
+
class WebHDFSOutput < Output
|
|
3
3
|
class LZOCommandCompressor < Compressor
|
|
4
4
|
WebHDFSOutput.register_compressor('lzo_command', self)
|
|
5
5
|
|
|
6
|
-
config_param :command_parameter, :string, :
|
|
6
|
+
config_param :command_parameter, :string, default: '-qf1'
|
|
7
7
|
|
|
8
8
|
def configure(conf)
|
|
9
9
|
super
|
data/test/helper.rb
CHANGED
|
@@ -8,10 +8,13 @@ rescue Bundler::BundlerError => e
|
|
|
8
8
|
exit e.status_code
|
|
9
9
|
end
|
|
10
10
|
require 'test/unit'
|
|
11
|
+
require 'test/unit/rr'
|
|
11
12
|
|
|
12
13
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
13
14
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
14
15
|
require 'fluent/test'
|
|
16
|
+
require 'fluent/test/helpers'
|
|
17
|
+
require 'fluent/test/driver/output'
|
|
15
18
|
unless ENV.has_key?('VERBOSE')
|
|
16
19
|
nulllogger = Object.new
|
|
17
20
|
nulllogger.instance_eval {|obj|
|
|
@@ -22,6 +25,8 @@ unless ENV.has_key?('VERBOSE')
|
|
|
22
25
|
$log = nulllogger
|
|
23
26
|
end
|
|
24
27
|
|
|
28
|
+
include Fluent::Test::Helpers
|
|
29
|
+
|
|
25
30
|
require 'fluent/plugin/out_webhdfs'
|
|
26
31
|
|
|
27
32
|
class Test::Unit::TestCase
|
|
@@ -16,11 +16,11 @@ class CompressorTest < Test::Unit::TestCase
|
|
|
16
16
|
def setup
|
|
17
17
|
omit unless Object.const_defined?(:Snappy)
|
|
18
18
|
Fluent::Test.setup
|
|
19
|
-
@compressor = Fluent::WebHDFSOutput::SnappyCompressor.new
|
|
19
|
+
@compressor = Fluent::Plugin::WebHDFSOutput::SnappyCompressor.new
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def create_driver(conf=CONFIG
|
|
23
|
-
Fluent::Test::
|
|
22
|
+
def create_driver(conf = CONFIG)
|
|
23
|
+
Fluent::Test::Driver::Output.new(Fluent::Plugin::WebHDFSOutput).configure(conf)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def test_ext
|
|
@@ -1,154 +1,243 @@
|
|
|
1
1
|
require 'helper'
|
|
2
2
|
|
|
3
3
|
class WebHDFSOutputTest < Test::Unit::TestCase
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
CONFIG_DEFAULT = config_element("match", "", {"host" => "namenode.local", "path" => "/hdfs/path/file.%Y%m%d.log"})
|
|
5
|
+
|
|
6
|
+
CONFIG_COMPAT = config_element(
|
|
7
|
+
"ROOT", "", {
|
|
8
|
+
"output_data_type" => "",
|
|
9
|
+
"host" => "namenode.local",
|
|
10
|
+
"path" => "/hdfs/path/file.%Y%m%d.log"
|
|
11
|
+
})
|
|
12
|
+
|
|
8
13
|
def setup
|
|
9
14
|
Fluent::Test.setup
|
|
10
15
|
end
|
|
11
16
|
|
|
12
|
-
def create_driver(conf
|
|
13
|
-
Fluent::Test::
|
|
17
|
+
def create_driver(conf)
|
|
18
|
+
Fluent::Test::Driver::Output.new(Fluent::Plugin::WebHDFSOutput).configure(conf)
|
|
14
19
|
end
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
d = create_driver
|
|
21
|
+
sub_test_case "default configuration" do
|
|
22
|
+
test 'configured with standard out_file format with specified hdfs info' do
|
|
23
|
+
d = create_driver(CONFIG_DEFAULT)
|
|
24
|
+
assert_true d.instance.instance_eval{ @using_formatter_config }
|
|
25
|
+
|
|
19
26
|
assert_equal 'namenode.local', d.instance.instance_eval{ @namenode_host }
|
|
20
27
|
assert_equal 50070, d.instance.instance_eval{ @namenode_port }
|
|
21
28
|
assert_equal '/hdfs/path/file.%Y%m%d.log', d.instance.path
|
|
22
|
-
assert_equal '%Y%m%d', d.instance.time_slice_format
|
|
23
29
|
assert_equal false, d.instance.httpfs
|
|
24
30
|
assert_nil d.instance.username
|
|
25
31
|
assert_equal false, d.instance.ignore_start_check_error
|
|
26
32
|
|
|
27
|
-
assert_equal
|
|
28
|
-
assert_equal true, d.instance.
|
|
29
|
-
|
|
33
|
+
assert_equal 'Fluent::Plugin::OutFileFormatter', d.instance.formatter.class.to_s
|
|
34
|
+
assert_equal true, d.instance.end_with_newline
|
|
35
|
+
|
|
36
|
+
# deprecated params
|
|
37
|
+
assert_nil d.instance.instance_eval{ @output_include_time }
|
|
38
|
+
assert_nil d.instance.instance_eval{ @output_include_tag }
|
|
30
39
|
assert_nil d.instance.remove_prefix
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
assert_nil d.instance.instance_eval{ @header_separator }
|
|
41
|
+
assert_nil d.instance.default_tag
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
sub_test_case "flat configuration" do
|
|
46
|
+
def test_default_for_traditional_config
|
|
47
|
+
d = create_driver(CONFIG_COMPAT)
|
|
48
|
+
assert_false d.instance.instance_eval{ @using_formatter_config }
|
|
49
|
+
|
|
50
|
+
assert_equal 'namenode.local', d.instance.instance_eval{ @namenode_host }
|
|
51
|
+
assert_equal 50070, d.instance.instance_eval{ @namenode_port }
|
|
52
|
+
assert_equal '/hdfs/path/file.%Y%m%d.log', d.instance.path
|
|
53
|
+
assert_equal false, d.instance.httpfs
|
|
54
|
+
assert_nil d.instance.username
|
|
55
|
+
assert_equal false, d.instance.ignore_start_check_error
|
|
56
|
+
|
|
57
|
+
assert_equal 'Fluent::Plugin::JSONFormatter', d.instance.formatter.class.to_s
|
|
58
|
+
assert_equal true, d.instance.end_with_newline
|
|
59
|
+
|
|
60
|
+
assert_equal true, d.instance.instance_eval{ @output_include_time }
|
|
61
|
+
assert_equal true, d.instance.instance_eval{ @output_include_tag }
|
|
62
|
+
assert_nil d.instance.instance_eval{ @remove_prefix }
|
|
63
|
+
assert_equal "\t", d.instance.instance_eval{ @header_separator }
|
|
64
|
+
assert_equal 'tag_missing', d.instance.instance_eval{ @default_tag }
|
|
34
65
|
end
|
|
35
66
|
|
|
36
67
|
def test_httpfs
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
conf = config_element(
|
|
69
|
+
"ROOT", "", {
|
|
70
|
+
"namenode" => "server.local:14000",
|
|
71
|
+
"path" => "/hdfs/path/file.%Y%m%d.%H%M.log",
|
|
72
|
+
"httpfs" => "yes",
|
|
73
|
+
"username" => "hdfs_user"
|
|
74
|
+
})
|
|
75
|
+
d = create_driver(conf)
|
|
76
|
+
|
|
43
77
|
assert_equal 'server.local', d.instance.instance_eval{ @namenode_host }
|
|
44
78
|
assert_equal 14000, d.instance.instance_eval{ @namenode_port }
|
|
45
79
|
assert_equal '/hdfs/path/file.%Y%m%d.%H%M.log', d.instance.path
|
|
46
|
-
assert_equal '%Y%m%d%H%M', d.instance.time_slice_format
|
|
47
80
|
assert_equal true, d.instance.httpfs
|
|
48
81
|
assert_equal 'hdfs_user', d.instance.username
|
|
49
82
|
end
|
|
50
83
|
|
|
51
84
|
def test_ssl
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
85
|
+
conf = config_element(
|
|
86
|
+
"ROOT", "", {
|
|
87
|
+
"namenode" => "server.local:14000",
|
|
88
|
+
"path" => "/hdfs/path/file.%Y%m%d.%H%M.log",
|
|
89
|
+
"ssl" => true,
|
|
90
|
+
"ssl_ca_file" => "/path/to/ca_file.pem",
|
|
91
|
+
"ssl_verify_mode" => "peer",
|
|
92
|
+
"kerberos" => true
|
|
93
|
+
})
|
|
94
|
+
d = create_driver(conf)
|
|
95
|
+
|
|
60
96
|
assert_equal 'server.local', d.instance.instance_eval{ @namenode_host }
|
|
61
97
|
assert_equal 14000, d.instance.instance_eval{ @namenode_port }
|
|
62
98
|
assert_equal '/hdfs/path/file.%Y%m%d.%H%M.log', d.instance.path
|
|
63
|
-
assert_equal '%Y%m%d%H%M', d.instance.time_slice_format
|
|
64
99
|
assert_equal true, d.instance.ssl
|
|
65
100
|
assert_equal '/path/to/ca_file.pem', d.instance.ssl_ca_file
|
|
66
101
|
assert_equal :peer, d.instance.ssl_verify_mode
|
|
67
102
|
assert_equal true, d.instance.kerberos
|
|
68
103
|
end
|
|
69
104
|
|
|
70
|
-
data(gzip: [
|
|
71
|
-
bzip2: [
|
|
72
|
-
snappy: [
|
|
73
|
-
lzo: [
|
|
105
|
+
data(gzip: [:gzip, Fluent::Plugin::WebHDFSOutput::GzipCompressor],
|
|
106
|
+
bzip2: [:bzip2, Fluent::Plugin::WebHDFSOutput::Bzip2Compressor],
|
|
107
|
+
snappy: [:snappy, Fluent::Plugin::WebHDFSOutput::SnappyCompressor],
|
|
108
|
+
lzo: [:lzo_command, Fluent::Plugin::WebHDFSOutput::LZOCommandCompressor])
|
|
74
109
|
def test_compress(data)
|
|
75
110
|
compress_type, compressor_class = data
|
|
76
111
|
begin
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
conf = config_element(
|
|
113
|
+
"ROOT", "", {
|
|
114
|
+
"namenode" => "server.local:14000",
|
|
115
|
+
"path" => "/hdfs/path/file.%Y%m%d.%H%M.log",
|
|
116
|
+
"compress" => compress_type
|
|
117
|
+
})
|
|
118
|
+
d = create_driver(conf)
|
|
82
119
|
rescue Fluent::ConfigError => ex
|
|
83
120
|
omit ex.message
|
|
84
121
|
end
|
|
85
122
|
assert_equal 'server.local', d.instance.instance_eval{ @namenode_host }
|
|
86
123
|
assert_equal 14000, d.instance.instance_eval{ @namenode_port }
|
|
87
124
|
assert_equal '/hdfs/path/file.%Y%m%d.%H%M.log', d.instance.path
|
|
88
|
-
assert_equal '%Y%m%d%H%M', d.instance.time_slice_format
|
|
89
125
|
assert_equal compress_type, d.instance.compress
|
|
90
126
|
assert_equal compressor_class, d.instance.compressor.class
|
|
91
127
|
end
|
|
92
128
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
129
|
+
def test_placeholders_old_style
|
|
130
|
+
conf = config_element(
|
|
131
|
+
"ROOT", "", {
|
|
132
|
+
"hostname" => "testing.node.local",
|
|
133
|
+
"namenode" => "server.local:50070",
|
|
134
|
+
"path" => "/hdfs/${hostname}/file.%Y%m%d%H.log"
|
|
135
|
+
})
|
|
136
|
+
d = create_driver(conf)
|
|
99
137
|
assert_equal '/hdfs/testing.node.local/file.%Y%m%d%H.log', d.instance.path
|
|
100
138
|
end
|
|
101
139
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
140
|
+
data("%Y%m%d" => ["/hdfs/path/file.%Y%m%d.log", "/hdfs/path/file.20120718.log"],
|
|
141
|
+
"%Y%m%d.%H%M" => ["/hdfs/path/file.%Y%m%d.%H%M.log", "/hdfs/path/file.20120718.1503.log"])
|
|
142
|
+
test "generate_path" do |(path, expected)|
|
|
143
|
+
conf = config_element(
|
|
144
|
+
"ROOT", "", {
|
|
145
|
+
"namenode" => "server.local:14000",
|
|
146
|
+
"path" => path
|
|
147
|
+
})
|
|
148
|
+
d = create_driver(conf)
|
|
149
|
+
formatter = Fluent::Timezone.formatter("+0900", path)
|
|
150
|
+
mock(Fluent::Timezone).formatter(Time.now.strftime("%z"), path) { formatter }
|
|
151
|
+
time = event_time("2012-07-18 15:03:00 +0900")
|
|
152
|
+
metadata = d.instance.metadata("test", time, {})
|
|
153
|
+
chunk = d.instance.buffer.generate_chunk(metadata)
|
|
154
|
+
assert_equal expected, d.instance.generate_path(chunk)
|
|
155
|
+
end
|
|
109
156
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
157
|
+
data(path: { "append" => false },
|
|
158
|
+
ssl: { "ssl" => true, "ssl_verify_mode" => "invalid" },
|
|
159
|
+
compress: { "compress" => "invalid" })
|
|
160
|
+
test "invalid" do |attr|
|
|
161
|
+
conf = config_element(
|
|
162
|
+
"ROOT", "", {
|
|
163
|
+
"namenode" => "server.local:14000",
|
|
164
|
+
"path" => "/hdfs/path/file.%Y%m%d.%H%M.log"
|
|
165
|
+
})
|
|
166
|
+
conf += config_element("", "", attr)
|
|
167
|
+
assert_raise Fluent::ConfigError do
|
|
168
|
+
create_driver(conf)
|
|
118
169
|
end
|
|
119
170
|
end
|
|
171
|
+
end
|
|
120
172
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
173
|
+
sub_test_case "sub section configuration" do
|
|
174
|
+
def test_time_key
|
|
175
|
+
conf = config_element(
|
|
176
|
+
"ROOT", "", {
|
|
177
|
+
"host" => "namenode.local",
|
|
178
|
+
"path" => "/hdfs/path/file.%Y%m%d.log"
|
|
179
|
+
}, [
|
|
180
|
+
config_element(
|
|
181
|
+
"buffer", "time", {
|
|
182
|
+
"timekey" => 1
|
|
183
|
+
})
|
|
184
|
+
]
|
|
185
|
+
)
|
|
186
|
+
d = create_driver(conf)
|
|
187
|
+
time = event_time("2012-07-18 15:03:00 +0900")
|
|
188
|
+
metadata = d.instance.metadata("test", time, {})
|
|
189
|
+
chunk = d.instance.buffer.generate_chunk(metadata)
|
|
190
|
+
assert_equal 1, d.instance.buffer_config.timekey
|
|
191
|
+
assert_equal "/hdfs/path/file.20120718.log", d.instance.generate_path(chunk)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
131
194
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
end
|
|
195
|
+
sub_test_case "using format subsection" do
|
|
196
|
+
test "blank format means default format 'out_file' with UTC timezone" do
|
|
197
|
+
format_section = config_element("format", "", {}, [])
|
|
198
|
+
conf = config_element("match", "", {"host" => "namenode.local", "path" => "/hdfs/path/file.%Y%m%d%H.log"}, [format_section])
|
|
199
|
+
d = create_driver(conf)
|
|
200
|
+
time = event_time("2017-01-24 13:10:30 -0700")
|
|
201
|
+
line = d.instance.format("test.now", time, {"message" => "yay", "name" => "tagomoris"})
|
|
202
|
+
assert_equal "2017-01-24T20:10:30Z\ttest.now\t{\"message\":\"yay\",\"name\":\"tagomoris\"}\n", line
|
|
203
|
+
end
|
|
142
204
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
205
|
+
test "specifying timezone works well in format section" do
|
|
206
|
+
format_section = config_element("format", "", {"timezone" => "+0100"}, [])
|
|
207
|
+
conf = config_element("match", "", {"host" => "namenode.local", "path" => "/hdfs/path/file.%Y%m%d%H.log"}, [format_section])
|
|
208
|
+
d = create_driver(conf)
|
|
209
|
+
time = event_time("2017-01-24 13:10:30 -0700")
|
|
210
|
+
line = d.instance.format("test.now", time, {"message" => "yay", "name" => "tagomoris"})
|
|
211
|
+
assert_equal "2017-01-24T21:10:30+01:00\ttest.now\t{\"message\":\"yay\",\"name\":\"tagomoris\"}\n", line
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
test "specifying formatter type LTSV for records, without tag and timezone" do
|
|
215
|
+
format_section = config_element("format", "", {"@type" => "ltsv"}, [])
|
|
216
|
+
conf = config_element("match", "", {"host" => "namenode.local", "path" => "/hdfs/path/file.%Y%m%d%H.log"}, [format_section])
|
|
217
|
+
d = create_driver(conf)
|
|
218
|
+
time = event_time("2017-01-24 13:10:30 -0700")
|
|
219
|
+
line = d.instance.format("test.now", time, {"message" => "yay", "name" => "tagomoris"})
|
|
220
|
+
assert_equal "message:yay\tname:tagomoris\n", line
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
test "specifying formatter type LTSV for records, with inject section to insert tag and time" do
|
|
224
|
+
inject_section = config_element("inject", "", {"tag_key" => "tag", "time_key" => "time", "time_type" => "string", "localtime" => "false"})
|
|
225
|
+
format_section = config_element("format", "", {"@type" => "ltsv"}, [])
|
|
226
|
+
conf = config_element("match", "", {"host" => "namenode.local", "path" => "/hdfs/path/file.%Y%m%d%H.log"}, [inject_section, format_section])
|
|
227
|
+
d = create_driver(conf)
|
|
228
|
+
time = event_time("2017-01-24 13:10:30 -0700")
|
|
229
|
+
line = d.instance.format("test.now", time, {"message" => "yay", "name" => "tagomoris"})
|
|
230
|
+
assert_equal "message:yay\tname:tagomoris\ttag:test.now\ttime:2017-01-24T20:10:30Z\n", line
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
sub_test_case "using older configuration" do
|
|
235
|
+
test "output_data_type json is same with out_file with UTC timezone" do
|
|
236
|
+
conf = config_element("match", "", {"host" => "namenode.local", "path" => "/hdfs/path/file.%Y%m%d%H.log", "output_data_type" => "json"}, [])
|
|
237
|
+
d = create_driver(conf)
|
|
238
|
+
time = event_time("2017-01-24 13:10:30 -0700")
|
|
239
|
+
line = d.instance.format("test.now", time, {"message" => "yay", "name" => "tagomoris"})
|
|
240
|
+
assert_equal "2017-01-24T20:10:30Z\ttest.now\t{\"message\":\"yay\",\"name\":\"tagomoris\"}\n", line
|
|
152
241
|
end
|
|
153
242
|
end
|
|
154
243
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fluent-plugin-webhdfs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TAGOMORI Satoshi
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-01-
|
|
11
|
+
date: 2017-01-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -39,7 +39,7 @@ dependencies:
|
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: test-unit-rr
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
@@ -53,67 +53,47 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
56
|
+
name: appraisal
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
59
|
- - ">="
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 0
|
|
61
|
+
version: '0'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
66
|
- - ">="
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: fluentd
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: 0.10.59
|
|
76
|
-
- - "<"
|
|
77
|
-
- !ruby/object:Gem::Version
|
|
78
|
-
version: 0.14.0
|
|
79
|
-
type: :runtime
|
|
80
|
-
prerelease: false
|
|
81
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
82
|
-
requirements:
|
|
83
|
-
- - ">="
|
|
84
|
-
- !ruby/object:Gem::Version
|
|
85
|
-
version: 0.10.59
|
|
86
|
-
- - "<"
|
|
87
|
-
- !ruby/object:Gem::Version
|
|
88
|
-
version: 0.14.0
|
|
68
|
+
version: '0'
|
|
89
69
|
- !ruby/object:Gem::Dependency
|
|
90
|
-
name:
|
|
70
|
+
name: snappy
|
|
91
71
|
requirement: !ruby/object:Gem::Requirement
|
|
92
72
|
requirements:
|
|
93
73
|
- - ">="
|
|
94
74
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: 0.
|
|
96
|
-
type: :
|
|
75
|
+
version: 0.0.13
|
|
76
|
+
type: :development
|
|
97
77
|
prerelease: false
|
|
98
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
99
79
|
requirements:
|
|
100
80
|
- - ">="
|
|
101
81
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: 0.
|
|
82
|
+
version: 0.0.13
|
|
103
83
|
- !ruby/object:Gem::Dependency
|
|
104
|
-
name:
|
|
84
|
+
name: fluentd
|
|
105
85
|
requirement: !ruby/object:Gem::Requirement
|
|
106
86
|
requirements:
|
|
107
87
|
- - ">="
|
|
108
88
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: 0.
|
|
89
|
+
version: 0.14.4
|
|
110
90
|
type: :runtime
|
|
111
91
|
prerelease: false
|
|
112
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
113
93
|
requirements:
|
|
114
94
|
- - ">="
|
|
115
95
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: 0.
|
|
96
|
+
version: 0.14.4
|
|
117
97
|
- !ruby/object:Gem::Dependency
|
|
118
98
|
name: webhdfs
|
|
119
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -183,9 +163,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
183
163
|
version: '0'
|
|
184
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
165
|
requirements:
|
|
186
|
-
- - "
|
|
166
|
+
- - ">"
|
|
187
167
|
- !ruby/object:Gem::Version
|
|
188
|
-
version:
|
|
168
|
+
version: 1.3.1
|
|
189
169
|
requirements: []
|
|
190
170
|
rubyforge_project:
|
|
191
171
|
rubygems_version: 2.6.8
|