fluent-plugin-elasticsearch 1.18.2 → 2.0.0.rc.1
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 +5 -5
- data/.gitignore +0 -1
- data/.travis.yml +0 -1
- data/History.md +4 -96
- data/README.md +23 -162
- data/fluent-plugin-elasticsearch.gemspec +5 -5
- data/lib/fluent/plugin/elasticsearch_index_template.rb +3 -8
- data/lib/fluent/plugin/out_elasticsearch.rb +314 -420
- data/lib/fluent/plugin/out_elasticsearch_dynamic.rb +206 -220
- data/test/plugin/test_out_elasticsearch.rb +303 -806
- data/test/plugin/test_out_elasticsearch_dynamic.rb +180 -257
- metadata +13 -24
- data/Gemfile.v0.12 +0 -11
- data/lib/fluent/log-ext.rb +0 -38
- data/lib/fluent/plugin/elasticsearch_constants.rb +0 -11
- data/lib/fluent/plugin/elasticsearch_error_handler.rb +0 -89
- data/lib/fluent/plugin/elasticsearch_simple_sniffer.rb +0 -10
- data/lib/fluent/plugin/filter_elasticsearch_genid.rb +0 -25
- data/test/plugin/test_elasticsearch_error_handler.rb +0 -264
- data/test/plugin/test_filter_elasticsearch_genid.rb +0 -40
- data/test/test_log-ext.rb +0 -33
@@ -3,13 +3,13 @@ $:.push File.expand_path('../lib', __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = 'fluent-plugin-elasticsearch'
|
6
|
-
s.version = '
|
6
|
+
s.version = '2.0.0.rc.1'
|
7
7
|
s.authors = ['diogo', 'pitr']
|
8
8
|
s.email = ['pitr.vern@gmail.com', 'me@diogoterror.com']
|
9
|
-
s.description = %q{
|
9
|
+
s.description = %q{ElasticSearch output plugin for Fluent event collector}
|
10
10
|
s.summary = s.description
|
11
11
|
s.homepage = 'https://github.com/uken/fluent-plugin-elasticsearch'
|
12
|
-
s.license = '
|
12
|
+
s.license = 'MIT'
|
13
13
|
|
14
14
|
s.files = `git ls-files`.split($/)
|
15
15
|
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
|
19
19
|
s.required_ruby_version = Gem::Requirement.new(">= 2.0".freeze)
|
20
20
|
|
21
|
-
s.add_runtime_dependency 'fluentd', '>= 0.
|
21
|
+
s.add_runtime_dependency 'fluentd', '>= 0.14.8'
|
22
22
|
s.add_runtime_dependency 'excon', '>= 0'
|
23
23
|
s.add_runtime_dependency 'elasticsearch'
|
24
24
|
|
@@ -27,5 +27,5 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.add_development_dependency 'webmock', '~> 1'
|
28
28
|
s.add_development_dependency 'test-unit', '~> 3.1.0'
|
29
29
|
s.add_development_dependency 'minitest', '~> 5.8'
|
30
|
-
s.add_development_dependency 'flexmock', '~> 2.
|
30
|
+
s.add_development_dependency 'flexmock', '~> 2.0'
|
31
31
|
end
|
@@ -19,12 +19,7 @@ module Fluent::ElasticsearchIndexTemplate
|
|
19
19
|
client.indices.put_template(:name => name, :body => template)
|
20
20
|
end
|
21
21
|
|
22
|
-
def template_install(name, template_file
|
23
|
-
if overwrite
|
24
|
-
template_put(name, get_template(template_file))
|
25
|
-
log.info("Template '#{name}' overwritten with #{template_file}.")
|
26
|
-
return
|
27
|
-
end
|
22
|
+
def template_install(name, template_file)
|
28
23
|
if !template_exists?(name)
|
29
24
|
template_put(name, get_template(template_file))
|
30
25
|
log.info("Template configured, but no template installed. Installed '#{name}' from #{template_file}.")
|
@@ -33,9 +28,9 @@ module Fluent::ElasticsearchIndexTemplate
|
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
36
|
-
def templates_hash_install(templates
|
31
|
+
def templates_hash_install (templates)
|
37
32
|
templates.each do |key, value|
|
38
|
-
template_install(key, value
|
33
|
+
template_install(key, value)
|
39
34
|
end
|
40
35
|
end
|
41
36
|
|
@@ -9,488 +9,382 @@ begin
|
|
9
9
|
rescue LoadError
|
10
10
|
end
|
11
11
|
|
12
|
-
require 'fluent/output'
|
13
|
-
require 'fluent/event'
|
14
|
-
require 'fluent/log-ext'
|
15
|
-
require_relative 'elasticsearch_constants'
|
16
|
-
require_relative 'elasticsearch_error_handler'
|
12
|
+
require 'fluent/plugin/output'
|
17
13
|
require_relative 'elasticsearch_index_template'
|
18
14
|
|
19
|
-
|
20
|
-
class
|
15
|
+
module Fluent::Plugin
|
16
|
+
class ElasticsearchOutput < Output
|
17
|
+
class ConnectionFailure < StandardError; end
|
18
|
+
|
19
|
+
helpers :event_emitter, :compat_parameters
|
20
|
+
|
21
|
+
Fluent::Plugin.register_output('elasticsearch', self)
|
22
|
+
|
23
|
+
DEFAULT_BUFFER_TYPE = "memory"
|
24
|
+
|
25
|
+
config_param :host, :string, :default => 'localhost'
|
26
|
+
config_param :port, :integer, :default => 9200
|
27
|
+
config_param :user, :string, :default => nil
|
28
|
+
config_param :password, :string, :default => nil, :secret => true
|
29
|
+
config_param :path, :string, :default => nil
|
30
|
+
config_param :scheme, :string, :default => 'http'
|
31
|
+
config_param :hosts, :string, :default => nil
|
32
|
+
config_param :target_index_key, :string, :default => nil
|
33
|
+
config_param :target_type_key, :string, :default => nil
|
34
|
+
config_param :time_key_format, :string, :default => nil
|
35
|
+
config_param :time_precision, :integer, :default => 9
|
36
|
+
config_param :logstash_format, :bool, :default => false
|
37
|
+
config_param :logstash_prefix, :string, :default => "logstash"
|
38
|
+
config_param :logstash_dateformat, :string, :default => "%Y.%m.%d"
|
39
|
+
config_param :utc_index, :bool, :default => true
|
40
|
+
config_param :type_name, :string, :default => "fluentd"
|
41
|
+
config_param :index_name, :string, :default => "fluentd"
|
42
|
+
config_param :id_key, :string, :default => nil
|
43
|
+
config_param :write_operation, :string, :default => "index"
|
44
|
+
config_param :parent_key, :string, :default => nil
|
45
|
+
config_param :routing_key, :string, :default => nil
|
46
|
+
config_param :request_timeout, :time, :default => 5
|
47
|
+
config_param :reload_connections, :bool, :default => true
|
48
|
+
config_param :reload_on_failure, :bool, :default => false
|
49
|
+
config_param :resurrect_after, :time, :default => 60
|
50
|
+
config_param :time_key, :string, :default => nil
|
51
|
+
config_param :time_key_exclude_timestamp, :bool, :default => false
|
52
|
+
config_param :ssl_verify , :bool, :default => true
|
53
|
+
config_param :client_key, :string, :default => nil
|
54
|
+
config_param :client_cert, :string, :default => nil
|
55
|
+
config_param :client_key_pass, :string, :default => nil
|
56
|
+
config_param :ca_file, :string, :default => nil
|
57
|
+
config_param :remove_keys, :string, :default => nil
|
58
|
+
config_param :remove_keys_on_update, :string, :default => ""
|
59
|
+
config_param :remove_keys_on_update_key, :string, :default => nil
|
60
|
+
config_param :flatten_hashes, :bool, :default => false
|
61
|
+
config_param :flatten_hashes_separator, :string, :default => "_"
|
62
|
+
config_param :template_name, :string, :default => nil
|
63
|
+
config_param :template_file, :string, :default => nil
|
64
|
+
config_param :templates, :hash, :default => nil
|
65
|
+
config_param :include_tag_key, :bool, :default => false
|
66
|
+
config_param :tag_key, :string, :default => 'tag'
|
67
|
+
config_param :time_parse_error_tag, :string, :default => 'Fluent::ElasticsearchOutput::TimeParser.error'
|
68
|
+
config_param :reconnect_on_error, :bool, :default => false
|
69
|
+
|
70
|
+
config_section :buffer do
|
71
|
+
config_set_default :@type, DEFAULT_BUFFER_TYPE
|
72
|
+
config_set_default :chunk_keys, ['tag']
|
73
|
+
end
|
21
74
|
|
22
|
-
|
23
|
-
# include the field for the unique record identifier
|
24
|
-
class MissingIdFieldError < StandardError; end
|
75
|
+
include Fluent::ElasticsearchIndexTemplate
|
25
76
|
|
26
|
-
|
27
|
-
|
28
|
-
# failed (e.g some records succeed while others failed)
|
29
|
-
class RetryStreamError < StandardError
|
30
|
-
attr_reader :retry_stream
|
31
|
-
def initialize(retry_stream)
|
32
|
-
@retry_stream = retry_stream
|
77
|
+
def initialize
|
78
|
+
super
|
33
79
|
end
|
34
|
-
end
|
35
80
|
|
36
|
-
|
37
|
-
|
38
|
-
DEFAULT_RELOAD_AFTER = -1
|
39
|
-
|
40
|
-
config_param :host, :string, :default => 'localhost'
|
41
|
-
config_param :port, :integer, :default => 9200
|
42
|
-
config_param :user, :string, :default => nil
|
43
|
-
config_param :password, :string, :default => nil, :secret => true
|
44
|
-
config_param :path, :string, :default => nil
|
45
|
-
config_param :scheme, :enum, :list => [:https, :http], :default => :http
|
46
|
-
config_param :hosts, :string, :default => nil
|
47
|
-
config_param :target_index_key, :string, :default => nil
|
48
|
-
config_param :target_type_key, :string, :default => nil
|
49
|
-
config_param :time_key_format, :string, :default => nil
|
50
|
-
config_param :time_precision, :integer, :default => 0
|
51
|
-
config_param :include_timestamp, :bool, :default => false
|
52
|
-
config_param :logstash_format, :bool, :default => false
|
53
|
-
config_param :logstash_prefix, :string, :default => "logstash"
|
54
|
-
config_param :logstash_prefix_separator, :string, :default => '-'
|
55
|
-
config_param :logstash_dateformat, :string, :default => "%Y.%m.%d"
|
56
|
-
config_param :utc_index, :bool, :default => true
|
57
|
-
config_param :type_name, :string, :default => "fluentd"
|
58
|
-
config_param :index_name, :string, :default => "fluentd"
|
59
|
-
config_param :id_key, :string, :default => nil
|
60
|
-
config_param :write_operation, :string, :default => "index"
|
61
|
-
config_param :parent_key, :string, :default => nil
|
62
|
-
config_param :routing_key, :string, :default => nil
|
63
|
-
config_param :request_timeout, :time, :default => 5
|
64
|
-
config_param :reload_connections, :bool, :default => true
|
65
|
-
config_param :reload_on_failure, :bool, :default => false
|
66
|
-
config_param :retry_tag, :string, :default=>nil
|
67
|
-
config_param :resurrect_after, :time, :default => 60
|
68
|
-
config_param :time_key, :string, :default => nil
|
69
|
-
config_param :time_key_exclude_timestamp, :bool, :default => false
|
70
|
-
config_param :ssl_verify , :bool, :default => true
|
71
|
-
config_param :client_key, :string, :default => nil
|
72
|
-
config_param :client_cert, :string, :default => nil
|
73
|
-
config_param :client_key_pass, :string, :default => nil
|
74
|
-
config_param :ca_file, :string, :default => nil
|
75
|
-
config_param :ssl_version, :enum, list: [:SSLv23, :TLSv1, :TLSv1_1, :TLSv1_2], :default => :TLSv1
|
76
|
-
config_param :remove_keys, :string, :default => nil
|
77
|
-
config_param :remove_keys_on_update, :string, :default => ""
|
78
|
-
config_param :remove_keys_on_update_key, :string, :default => nil
|
79
|
-
config_param :flatten_hashes, :bool, :default => false
|
80
|
-
config_param :flatten_hashes_separator, :string, :default => "_"
|
81
|
-
config_param :template_name, :string, :default => nil
|
82
|
-
config_param :template_file, :string, :default => nil
|
83
|
-
config_param :template_overwrite, :bool, :default => false
|
84
|
-
config_param :templates, :hash, :default => nil
|
85
|
-
config_param :include_tag_key, :bool, :default => false
|
86
|
-
config_param :tag_key, :string, :default => 'tag'
|
87
|
-
config_param :time_parse_error_tag, :string, :default => 'Fluent::ElasticsearchOutput::TimeParser.error'
|
88
|
-
config_param :reconnect_on_error, :bool, :default => false
|
89
|
-
config_param :pipeline, :string, :default => nil
|
90
|
-
config_param :with_transporter_log, :bool, :default => false
|
91
|
-
config_param :emit_error_for_missing_id, :bool, :default => false
|
92
|
-
config_param :sniffer_class_name, :string, :default => nil
|
93
|
-
config_param :reload_after, :integer, :default => DEFAULT_RELOAD_AFTER
|
94
|
-
config_param :suppress_doc_wrap, :bool, :default => false
|
95
|
-
|
96
|
-
include Fluent::ElasticsearchIndexTemplate
|
97
|
-
include Fluent::ElasticsearchConstants
|
98
|
-
|
99
|
-
def initialize
|
100
|
-
super
|
101
|
-
end
|
81
|
+
def configure(conf)
|
82
|
+
compat_parameters_convert(conf, :buffer)
|
102
83
|
|
103
|
-
|
104
|
-
|
105
|
-
@time_parser = create_time_parser
|
84
|
+
super
|
85
|
+
raise Fluent::ConfigError, "'tag' in chunk_keys is required." if not @chunk_key_tag
|
106
86
|
|
107
|
-
|
108
|
-
@remove_keys = @remove_keys.split(/\s*,\s*/)
|
109
|
-
end
|
87
|
+
@time_parser = create_time_parser
|
110
88
|
|
111
|
-
|
112
|
-
|
113
|
-
|
89
|
+
if @remove_keys
|
90
|
+
@remove_keys = @remove_keys.split(/\s*,\s*/)
|
91
|
+
end
|
114
92
|
|
115
|
-
|
116
|
-
|
117
|
-
|
93
|
+
if @target_index_key && @target_index_key.is_a?(String)
|
94
|
+
@target_index_key = @target_index_key.split '.'
|
95
|
+
end
|
118
96
|
|
119
|
-
|
120
|
-
|
121
|
-
|
97
|
+
if @target_type_key && @target_type_key.is_a?(String)
|
98
|
+
@target_type_key = @target_type_key.split '.'
|
99
|
+
end
|
122
100
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
templates_hash_install(@templates, @template_overwrite)
|
127
|
-
end
|
101
|
+
if @remove_keys_on_update && @remove_keys_on_update.is_a?(String)
|
102
|
+
@remove_keys_on_update = @remove_keys_on_update.split ','
|
103
|
+
end
|
128
104
|
|
129
|
-
|
105
|
+
if @template_name && @template_file
|
106
|
+
template_install(@template_name, @template_file)
|
107
|
+
elsif @templates
|
108
|
+
templates_hash_install (@templates)
|
109
|
+
end
|
130
110
|
|
131
|
-
|
132
|
-
require 'oj'
|
133
|
-
@dump_proc = Oj.method(:dump)
|
134
|
-
rescue LoadError
|
135
|
-
@dump_proc = Yajl.method(:dump)
|
136
|
-
end
|
111
|
+
@meta_config_map = create_meta_config_map
|
137
112
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
113
|
+
begin
|
114
|
+
require 'oj'
|
115
|
+
@dump_proc = Oj.method(:dump)
|
116
|
+
rescue LoadError
|
117
|
+
@dump_proc = Yajl.method(:dump)
|
118
|
+
end
|
143
119
|
end
|
144
120
|
|
145
|
-
|
146
|
-
|
121
|
+
def create_meta_config_map
|
122
|
+
result = []
|
123
|
+
result << [@id_key, '_id'] if @id_key
|
124
|
+
result << [@parent_key, '_parent'] if @parent_key
|
125
|
+
result << [@routing_key, '_routing'] if @routing_key
|
126
|
+
result
|
147
127
|
end
|
148
128
|
|
149
|
-
|
150
|
-
if
|
151
|
-
|
152
|
-
|
153
|
-
|
129
|
+
# once fluent v0.14 is released we might be able to use
|
130
|
+
# Fluent::Parser::TimeParser, but it doesn't quite do what we want - if gives
|
131
|
+
# [sec,nsec] where as we want something we can call `strftime` on...
|
132
|
+
def create_time_parser
|
133
|
+
if @time_key_format
|
134
|
+
begin
|
135
|
+
# Strptime doesn't support all formats, but for those it does it's
|
136
|
+
# blazingly fast.
|
137
|
+
strptime = Strptime.new(@time_key_format)
|
138
|
+
Proc.new { |value| strptime.exec(value).to_datetime }
|
139
|
+
rescue
|
140
|
+
# Can happen if Strptime doesn't recognize the format; or
|
141
|
+
# if strptime couldn't be required (because it's not installed -- it's
|
142
|
+
# ruby 2 only)
|
143
|
+
Proc.new { |value| DateTime.strptime(value, @time_key_format) }
|
144
|
+
end
|
145
|
+
else
|
146
|
+
Proc.new { |value| DateTime.parse(value) }
|
147
|
+
end
|
154
148
|
end
|
155
149
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
150
|
+
def parse_time(value, event_time, tag)
|
151
|
+
@time_parser.call(value)
|
152
|
+
rescue => e
|
153
|
+
router.emit_error_event(@time_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @time_key_format, 'value' => value}, e)
|
154
|
+
return Time.at(event_time).to_datetime
|
161
155
|
end
|
162
156
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
157
|
+
def client
|
158
|
+
@_es ||= begin
|
159
|
+
excon_options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
|
160
|
+
adapter_conf = lambda {|f| f.adapter :excon, excon_options }
|
161
|
+
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(get_connection_options.merge(
|
162
|
+
options: {
|
163
|
+
reload_connections: @reload_connections,
|
164
|
+
reload_on_failure: @reload_on_failure,
|
165
|
+
resurrect_after: @resurrect_after,
|
166
|
+
retry_on_failure: 5,
|
167
|
+
transport_options: {
|
168
|
+
headers: { 'Content-Type' => 'application/json' },
|
169
|
+
request: { timeout: @request_timeout },
|
170
|
+
ssl: { verify: @ssl_verify, ca_file: @ca_file }
|
171
|
+
}
|
172
|
+
}), &adapter_conf)
|
173
|
+
es = Elasticsearch::Client.new transport: transport
|
174
|
+
|
175
|
+
begin
|
176
|
+
raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description})!" unless es.ping
|
177
|
+
rescue *es.transport.host_unreachable_exceptions => e
|
178
|
+
raise ConnectionFailure, "Can not reach Elasticsearch cluster (#{connection_options_description})! #{e.message}"
|
179
|
+
end
|
172
180
|
|
173
|
-
|
174
|
-
|
175
|
-
# [sec,nsec] where as we want something we can call `strftime` on...
|
176
|
-
def create_time_parser
|
177
|
-
if @time_key_format
|
178
|
-
begin
|
179
|
-
# Strptime doesn't support all formats, but for those it does it's
|
180
|
-
# blazingly fast.
|
181
|
-
strptime = Strptime.new(@time_key_format)
|
182
|
-
Proc.new { |value| strptime.exec(value).to_datetime }
|
183
|
-
rescue
|
184
|
-
# Can happen if Strptime doesn't recognize the format; or
|
185
|
-
# if strptime couldn't be required (because it's not installed -- it's
|
186
|
-
# ruby 2 only)
|
187
|
-
Proc.new { |value| DateTime.strptime(value, @time_key_format) }
|
181
|
+
log.info "Connection opened to Elasticsearch cluster => #{connection_options_description}"
|
182
|
+
es
|
188
183
|
end
|
189
|
-
else
|
190
|
-
Proc.new { |value| DateTime.parse(value) }
|
191
184
|
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def parse_time(value, event_time, tag)
|
195
|
-
@time_parser.call(value)
|
196
|
-
rescue => e
|
197
|
-
router.emit_error_event(@time_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @time_key_format, 'value' => value}, e)
|
198
|
-
return Time.at(event_time).to_datetime
|
199
|
-
end
|
200
|
-
|
201
|
-
def client
|
202
|
-
@_es ||= begin
|
203
|
-
excon_options = { client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
|
204
|
-
adapter_conf = lambda {|f| f.adapter :excon, excon_options }
|
205
|
-
local_reload_connections = @reload_connections
|
206
|
-
if local_reload_connections && @reload_after > DEFAULT_RELOAD_AFTER
|
207
|
-
local_reload_connections = @reload_after
|
208
|
-
end
|
209
|
-
transport = Elasticsearch::Transport::Transport::HTTP::Faraday.new(get_connection_options.merge(
|
210
|
-
options: {
|
211
|
-
reload_connections: local_reload_connections,
|
212
|
-
reload_on_failure: @reload_on_failure,
|
213
|
-
resurrect_after: @resurrect_after,
|
214
|
-
retry_on_failure: 5,
|
215
|
-
logger: @transport_logger,
|
216
|
-
transport_options: {
|
217
|
-
headers: { 'Content-Type' => 'application/json' },
|
218
|
-
request: { timeout: @request_timeout },
|
219
|
-
ssl: { verify: @ssl_verify, ca_file: @ca_file, version: @ssl_version }
|
220
|
-
},
|
221
|
-
http: {
|
222
|
-
user: @user,
|
223
|
-
password: @password
|
224
|
-
},
|
225
|
-
sniffer_class: @sniffer_class,
|
226
|
-
}), &adapter_conf)
|
227
|
-
es = Elasticsearch::Client.new transport: transport
|
228
185
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
186
|
+
def get_connection_options
|
187
|
+
raise "`password` must be present if `user` is present" if @user && !@password
|
188
|
+
|
189
|
+
hosts = if @hosts
|
190
|
+
@hosts.split(',').map do |host_str|
|
191
|
+
# Support legacy hosts format host:port,host:port,host:port...
|
192
|
+
if host_str.match(%r{^[^:]+(\:\d+)?$})
|
193
|
+
{
|
194
|
+
host: host_str.split(':')[0],
|
195
|
+
port: (host_str.split(':')[1] || @port).to_i,
|
196
|
+
scheme: @scheme
|
197
|
+
}
|
198
|
+
else
|
199
|
+
# New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
|
200
|
+
uri = URI(host_str)
|
201
|
+
%w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
|
202
|
+
hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
|
203
|
+
hash
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end.compact
|
207
|
+
else
|
208
|
+
[{host: @host, port: @port, scheme: @scheme}]
|
209
|
+
end.each do |host|
|
210
|
+
host.merge!(user: @user, password: @password) if !host[:user] && @user
|
211
|
+
host.merge!(path: @path) if !host[:path] && @path
|
233
212
|
end
|
234
213
|
|
235
|
-
|
236
|
-
|
214
|
+
{
|
215
|
+
hosts: hosts
|
216
|
+
}
|
237
217
|
end
|
238
|
-
end
|
239
218
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
m["path"]
|
247
|
-
else
|
248
|
-
host_str
|
219
|
+
def connection_options_description
|
220
|
+
get_connection_options[:hosts].map do |host_info|
|
221
|
+
attributes = host_info.dup
|
222
|
+
attributes[:password] = 'obfuscated' if attributes.has_key?(:password)
|
223
|
+
attributes.inspect
|
224
|
+
end.join(', ')
|
249
225
|
end
|
250
|
-
end
|
251
226
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
%w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
|
268
|
-
hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
|
269
|
-
hash
|
270
|
-
end
|
227
|
+
BODY_DELIMITER = "\n".freeze
|
228
|
+
UPDATE_OP = "update".freeze
|
229
|
+
UPSERT_OP = "upsert".freeze
|
230
|
+
CREATE_OP = "create".freeze
|
231
|
+
INDEX_OP = "index".freeze
|
232
|
+
ID_FIELD = "_id".freeze
|
233
|
+
TIMESTAMP_FIELD = "@timestamp".freeze
|
234
|
+
|
235
|
+
def append_record_to_messages(op, meta, header, record, msgs)
|
236
|
+
case op
|
237
|
+
when UPDATE_OP, UPSERT_OP
|
238
|
+
if meta.has_key?(ID_FIELD)
|
239
|
+
header[UPDATE_OP] = meta
|
240
|
+
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
241
|
+
msgs << @dump_proc.call(update_body(record, op)) << BODY_DELIMITER
|
271
242
|
end
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
{
|
281
|
-
hosts: hosts
|
282
|
-
}
|
283
|
-
end
|
284
|
-
|
285
|
-
def connection_options_description
|
286
|
-
get_connection_options[:hosts].map do |host_info|
|
287
|
-
attributes = host_info.dup
|
288
|
-
attributes[:password] = 'obfuscated' if attributes.has_key?(:password)
|
289
|
-
attributes.inspect
|
290
|
-
end.join(', ')
|
291
|
-
end
|
292
|
-
|
293
|
-
# append_record_to_messages adds a record to the bulk message
|
294
|
-
# payload to be submitted to Elasticsearch. Records that do
|
295
|
-
# not include '_id' field are skipped when 'write_operation'
|
296
|
-
# is configured for 'create' or 'update'
|
297
|
-
#
|
298
|
-
# returns 'true' if record was appended to the bulk message
|
299
|
-
# and 'false' otherwise
|
300
|
-
def append_record_to_messages(op, meta, header, record, msgs)
|
301
|
-
case op
|
302
|
-
when UPDATE_OP, UPSERT_OP
|
303
|
-
if meta.has_key?(ID_FIELD)
|
304
|
-
header[UPDATE_OP] = meta
|
305
|
-
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
306
|
-
msgs << @dump_proc.call(update_body(record, op)) << BODY_DELIMITER
|
307
|
-
return true
|
308
|
-
end
|
309
|
-
when CREATE_OP
|
310
|
-
if meta.has_key?(ID_FIELD)
|
311
|
-
header[CREATE_OP] = meta
|
243
|
+
when CREATE_OP
|
244
|
+
if meta.has_key?(ID_FIELD)
|
245
|
+
header[CREATE_OP] = meta
|
246
|
+
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
247
|
+
msgs << @dump_proc.call(record) << BODY_DELIMITER
|
248
|
+
end
|
249
|
+
when INDEX_OP
|
250
|
+
header[INDEX_OP] = meta
|
312
251
|
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
313
252
|
msgs << @dump_proc.call(record) << BODY_DELIMITER
|
314
|
-
return true
|
315
253
|
end
|
316
|
-
when INDEX_OP
|
317
|
-
header[INDEX_OP] = meta
|
318
|
-
msgs << @dump_proc.call(header) << BODY_DELIMITER
|
319
|
-
msgs << @dump_proc.call(record) << BODY_DELIMITER
|
320
|
-
return true
|
321
254
|
end
|
322
|
-
return false
|
323
|
-
end
|
324
|
-
|
325
|
-
def update_body(record, op)
|
326
|
-
update = remove_keys(record)
|
327
|
-
if @suppress_doc_wrap
|
328
|
-
return update
|
329
|
-
end
|
330
|
-
body = {"doc".freeze => update}
|
331
|
-
if op == UPSERT_OP
|
332
|
-
if update == record
|
333
|
-
body["doc_as_upsert".freeze] = true
|
334
|
-
else
|
335
|
-
body[UPSERT_OP] = record
|
336
|
-
end
|
337
|
-
end
|
338
|
-
body
|
339
|
-
end
|
340
|
-
|
341
|
-
def remove_keys(record)
|
342
|
-
keys = record[@remove_keys_on_update_key] || @remove_keys_on_update || []
|
343
|
-
record.delete(@remove_keys_on_update_key)
|
344
|
-
return record unless keys.any?
|
345
|
-
record = record.dup
|
346
|
-
keys.each { |key| record.delete(key) }
|
347
|
-
record
|
348
|
-
end
|
349
|
-
|
350
|
-
def flatten_record(record, prefix=[])
|
351
|
-
ret = {}
|
352
|
-
if record.is_a? Hash
|
353
|
-
record.each { |key, value|
|
354
|
-
ret.merge! flatten_record(value, prefix + [key.to_s])
|
355
|
-
}
|
356
|
-
elsif record.is_a? Array
|
357
|
-
# Don't mess with arrays, leave them unprocessed
|
358
|
-
ret.merge!({prefix.join(@flatten_hashes_separator) => record})
|
359
|
-
else
|
360
|
-
return {prefix.join(@flatten_hashes_separator) => record}
|
361
|
-
end
|
362
|
-
ret
|
363
|
-
end
|
364
255
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
next unless record.is_a? Hash
|
372
|
-
begin
|
373
|
-
if process_message(tag, meta, header, time, record, bulk_message)
|
374
|
-
bulk_message_count += 1
|
256
|
+
def update_body(record, op)
|
257
|
+
update = remove_keys(record)
|
258
|
+
body = {"doc".freeze => update}
|
259
|
+
if op == UPSERT_OP
|
260
|
+
if update == record
|
261
|
+
body["doc_as_upsert".freeze] = true
|
375
262
|
else
|
376
|
-
|
377
|
-
raise MissingIdFieldError, "Missing '_id' field. Write operation is #{@write_operation}"
|
378
|
-
else
|
379
|
-
log.on_debug { log.debug("Dropping record because its missing an '_id' field and write_operation is #{@write_operation}: #{record}") }
|
380
|
-
end
|
263
|
+
body[UPSERT_OP] = record
|
381
264
|
end
|
382
|
-
rescue=>e
|
383
|
-
router.emit_error_event(tag, time, record, e)
|
384
265
|
end
|
266
|
+
body
|
385
267
|
end
|
386
268
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
record
|
394
|
-
end
|
395
|
-
|
396
|
-
if @hash_config
|
397
|
-
record = generate_hash_id_key(record)
|
269
|
+
def remove_keys(record)
|
270
|
+
keys = record[@remove_keys_on_update_key] || @remove_keys_on_update || []
|
271
|
+
record.delete(@remove_keys_on_update_key)
|
272
|
+
return record unless keys.any?
|
273
|
+
record = record.dup
|
274
|
+
keys.each { |key| record.delete(key) }
|
275
|
+
record
|
398
276
|
end
|
399
277
|
|
400
|
-
|
401
|
-
|
402
|
-
if record.
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
278
|
+
def flatten_record(record, prefix=[])
|
279
|
+
ret = {}
|
280
|
+
if record.is_a? Hash
|
281
|
+
record.each { |key, value|
|
282
|
+
ret.merge! flatten_record(value, prefix + [key.to_s])
|
283
|
+
}
|
284
|
+
elsif record.is_a? Array
|
285
|
+
# Don't mess with arrays, leave them unprocessed
|
286
|
+
ret.merge!({prefix.join(@flatten_hashes_separator) => record})
|
409
287
|
else
|
410
|
-
|
411
|
-
record[TIMESTAMP_FIELD] = dt.iso8601(@time_precision)
|
288
|
+
return {prefix.join(@flatten_hashes_separator) => record}
|
412
289
|
end
|
290
|
+
ret
|
413
291
|
end
|
414
292
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
dt = dt.new_offset(0) if @utc_index
|
420
|
-
target_index = "#{@logstash_prefix}#{@logstash_prefix_separator}#{dt.strftime(@logstash_dateformat)}"
|
421
|
-
else
|
422
|
-
target_index = @index_name
|
423
|
-
end
|
424
|
-
|
425
|
-
# Change target_index to lower-case since Elasticsearch doesn't
|
426
|
-
# allow upper-case characters in index names.
|
427
|
-
target_index = target_index.downcase
|
428
|
-
if @include_tag_key
|
429
|
-
record[@tag_key] = tag
|
430
|
-
end
|
293
|
+
def write(chunk)
|
294
|
+
bulk_message = ''
|
295
|
+
header = {}
|
296
|
+
meta = {}
|
431
297
|
|
432
|
-
|
433
|
-
if target_type_parent && target_type_parent[target_type_child_key]
|
434
|
-
target_type = target_type_parent.delete(target_type_child_key)
|
435
|
-
else
|
436
|
-
target_type = @type_name
|
437
|
-
end
|
298
|
+
tag = chunk.metadata.tag
|
438
299
|
|
439
|
-
|
440
|
-
|
441
|
-
meta["_type".freeze] = target_type
|
300
|
+
chunk.msgpack_each do |time, record|
|
301
|
+
next unless record.is_a? Hash
|
442
302
|
|
443
|
-
|
444
|
-
|
445
|
-
|
303
|
+
if @flatten_hashes
|
304
|
+
record = flatten_record(record)
|
305
|
+
end
|
446
306
|
|
447
|
-
|
448
|
-
|
449
|
-
|
307
|
+
target_index_parent, target_index_child_key = @target_index_key ? get_parent_of(record, @target_index_key) : nil
|
308
|
+
if target_index_parent && target_index_parent[target_index_child_key]
|
309
|
+
target_index = target_index_parent.delete(target_index_child_key)
|
310
|
+
elsif @logstash_format
|
311
|
+
if record.has_key?(TIMESTAMP_FIELD)
|
312
|
+
rts = record[TIMESTAMP_FIELD]
|
313
|
+
dt = parse_time(rts, time, tag)
|
314
|
+
elsif record.has_key?(@time_key)
|
315
|
+
rts = record[@time_key]
|
316
|
+
dt = parse_time(rts, time, tag)
|
317
|
+
record[TIMESTAMP_FIELD] = rts unless @time_key_exclude_timestamp
|
318
|
+
else
|
319
|
+
dt = Time.at(time).to_datetime
|
320
|
+
record[TIMESTAMP_FIELD] = dt.iso8601(@time_precision)
|
321
|
+
end
|
322
|
+
dt = dt.new_offset(0) if @utc_index
|
323
|
+
target_index = "#{@logstash_prefix}-#{dt.strftime(@logstash_dateformat)}"
|
324
|
+
else
|
325
|
+
target_index = @index_name
|
326
|
+
end
|
450
327
|
|
451
|
-
|
452
|
-
|
453
|
-
|
328
|
+
# Change target_index to lower-case since Elasticsearch doesn't
|
329
|
+
# allow upper-case characters in index names.
|
330
|
+
target_index = target_index.downcase
|
331
|
+
if @include_tag_key
|
332
|
+
record[@tag_key] = tag
|
333
|
+
end
|
454
334
|
|
455
|
-
|
456
|
-
|
335
|
+
target_type_parent, target_type_child_key = @target_type_key ? get_parent_of(record, @target_type_key) : nil
|
336
|
+
if target_type_parent && target_type_parent[target_type_child_key]
|
337
|
+
target_type = target_type_parent.delete(target_type_child_key)
|
338
|
+
else
|
339
|
+
target_type = @type_name
|
340
|
+
end
|
457
341
|
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
parent_object = path[0..-2].reduce(record) { |a, e| a.is_a?(Hash) ? a[e] : nil }
|
462
|
-
[parent_object, path[-1]]
|
463
|
-
end
|
342
|
+
meta.clear
|
343
|
+
meta["_index".freeze] = target_index
|
344
|
+
meta["_type".freeze] = target_type
|
464
345
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
retries = 0
|
469
|
-
begin
|
346
|
+
@meta_config_map.each do |record_key, meta_key|
|
347
|
+
meta[meta_key] = record[record_key] if record[record_key]
|
348
|
+
end
|
470
349
|
|
471
|
-
|
472
|
-
|
473
|
-
|
350
|
+
if @remove_keys
|
351
|
+
@remove_keys.each { |key| record.delete(key) }
|
352
|
+
end
|
474
353
|
|
475
|
-
|
476
|
-
error = Fluent::ElasticsearchErrorHandler.new(self)
|
477
|
-
error.handle_error(response, tag, chunk, bulk_message_count)
|
354
|
+
append_record_to_messages(@write_operation, meta, header, record, bulk_message)
|
478
355
|
end
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
356
|
+
|
357
|
+
send_bulk(bulk_message) unless bulk_message.empty?
|
358
|
+
bulk_message.clear
|
359
|
+
end
|
360
|
+
|
361
|
+
# returns [parent, child_key] of child described by path array in record's tree
|
362
|
+
# returns [nil, child_key] if path doesnt exist in record
|
363
|
+
def get_parent_of(record, path)
|
364
|
+
parent_object = path[0..-2].reduce(record) { |a, e| a.is_a?(Hash) ? a[e] : nil }
|
365
|
+
[parent_object, path[-1]]
|
366
|
+
end
|
367
|
+
|
368
|
+
def send_bulk(data)
|
369
|
+
retries = 0
|
370
|
+
begin
|
371
|
+
response = client.bulk body: data
|
372
|
+
if response['errors']
|
373
|
+
log.error "Could not push log to Elasticsearch: #{response}"
|
374
|
+
end
|
375
|
+
rescue *client.transport.host_unreachable_exceptions => e
|
376
|
+
if retries < 2
|
377
|
+
retries += 1
|
378
|
+
@_es = nil
|
379
|
+
log.warn "Could not push logs to Elasticsearch, resetting connection and trying again. #{e.message}"
|
380
|
+
sleep 2**retries
|
381
|
+
retry
|
382
|
+
end
|
383
|
+
raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
|
384
|
+
rescue Exception
|
385
|
+
@_es = nil if @reconnect_on_error
|
386
|
+
raise
|
489
387
|
end
|
490
|
-
raise ConnectionFailure, "Could not push logs to Elasticsearch after #{retries} retries. #{e.message}"
|
491
|
-
rescue Exception
|
492
|
-
@_es = nil if @reconnect_on_error
|
493
|
-
raise
|
494
388
|
end
|
495
389
|
end
|
496
390
|
end
|