fluent-plugin-input-opensearch 1.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.editorconfig +9 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- data/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +9 -0
- data/.github/workflows/coverage.yaml +22 -0
- data/.github/workflows/issue-auto-closer.yml +12 -0
- data/.github/workflows/linux.yml +26 -0
- data/.github/workflows/macos.yml +26 -0
- data/.github/workflows/windows.yml +26 -0
- data/.gitignore +18 -0
- data/CONTRIBUTING.md +24 -0
- data/Gemfile +10 -0
- data/History.md +67 -0
- data/LICENSE.txt +201 -0
- data/README.OpenSearchGenID.md +116 -0
- data/README.OpenSearchInput.md +314 -0
- data/README.Troubleshooting.md +482 -0
- data/README.md +1622 -0
- data/Rakefile +37 -0
- data/fluent-plugin-opensearch.gemspec +39 -0
- data/gemfiles/Gemfile.elasticsearch.v6 +12 -0
- data/lib/fluent/log-ext.rb +64 -0
- data/lib/fluent/plugin/filter_opensearch_genid.rb +103 -0
- data/lib/fluent/plugin/in_opensearch.rb +410 -0
- data/lib/fluent/plugin/oj_serializer.rb +48 -0
- data/lib/fluent/plugin/opensearch_constants.rb +39 -0
- data/lib/fluent/plugin/opensearch_error.rb +31 -0
- data/lib/fluent/plugin/opensearch_error_handler.rb +182 -0
- data/lib/fluent/plugin/opensearch_fallback_selector.rb +36 -0
- data/lib/fluent/plugin/opensearch_index_template.rb +155 -0
- data/lib/fluent/plugin/opensearch_simple_sniffer.rb +36 -0
- data/lib/fluent/plugin/opensearch_tls.rb +96 -0
- data/lib/fluent/plugin/out_opensearch.rb +1158 -0
- data/lib/fluent/plugin/out_opensearch_data_stream.rb +229 -0
- data/test/helper.rb +60 -0
- data/test/plugin/datastream_template.json +4 -0
- data/test/plugin/test_alias_template.json +9 -0
- data/test/plugin/test_filter_opensearch_genid.rb +241 -0
- data/test/plugin/test_in_opensearch.rb +500 -0
- data/test/plugin/test_index_alias_template.json +11 -0
- data/test/plugin/test_index_template.json +25 -0
- data/test/plugin/test_oj_serializer.rb +45 -0
- data/test/plugin/test_opensearch_error_handler.rb +770 -0
- data/test/plugin/test_opensearch_fallback_selector.rb +100 -0
- data/test/plugin/test_opensearch_tls.rb +171 -0
- data/test/plugin/test_out_opensearch.rb +3980 -0
- data/test/plugin/test_out_opensearch_data_stream.rb +746 -0
- data/test/plugin/test_template.json +23 -0
- data/test/test_log-ext.rb +61 -0
- metadata +291 -0
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
#
|
3
|
+
# The fluent-plugin-opensearch Contributors require contributions made to
|
4
|
+
# this file be licensed under the Apache-2.0 license or a
|
5
|
+
# compatible open source license.
|
6
|
+
#
|
7
|
+
# Modifications Copyright fluent-plugin-opensearch Contributors. See
|
8
|
+
# GitHub history for details.
|
9
|
+
#
|
10
|
+
# Licensed to Uken Inc. under one or more contributor
|
11
|
+
# license agreements. See the NOTICE file distributed with
|
12
|
+
# this work for additional information regarding copyright
|
13
|
+
# ownership. Uken Inc. licenses this file to you under
|
14
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
15
|
+
# not use this file except in compliance with the License.
|
16
|
+
# You may obtain a copy of the License at
|
17
|
+
#
|
18
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
19
|
+
#
|
20
|
+
# Unless required by applicable law or agreed to in writing,
|
21
|
+
# software distributed under the License is distributed on an
|
22
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
23
|
+
# KIND, either express or implied. See the License for the
|
24
|
+
# specific language governing permissions and limitations
|
25
|
+
# under the License.
|
26
|
+
|
27
|
+
require 'bundler/gem_tasks'
|
28
|
+
require 'rake/testtask'
|
29
|
+
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
test.warning = false
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => :test
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'fluent-plugin-input-opensearch'
|
6
|
+
s.version = '1.1.9'
|
7
|
+
s.authors = ['imcotop']
|
8
|
+
s.email = ['imcotop@icloud.com']
|
9
|
+
s.description = %q{Opensearch output plugin for Fluent event collector}
|
10
|
+
s.summary = s.description
|
11
|
+
s.homepage = 'https://github.com/fluent/fluent-plugin-opensearch'
|
12
|
+
s.license = 'Apache-2.0'
|
13
|
+
|
14
|
+
s.files = `git ls-files`.split($/)
|
15
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
if s.respond_to?(:metadata)
|
20
|
+
s.metadata["changelog_uri"] = "https://github.com/fluent/fluent-plugin-opensearch/blob/master/History.md"
|
21
|
+
end
|
22
|
+
|
23
|
+
s.required_ruby_version = Gem::Requirement.new(">= 2.3".freeze)
|
24
|
+
|
25
|
+
s.add_runtime_dependency 'fluentd', '>= 0.14.22'
|
26
|
+
s.add_runtime_dependency 'opensearch-ruby', '>= 3.0.1'
|
27
|
+
s.add_runtime_dependency "aws-sdk-core", "~> 3"
|
28
|
+
s.add_runtime_dependency 'excon', '>= 0'
|
29
|
+
s.add_runtime_dependency 'faraday', '>= 2.0.0'
|
30
|
+
s.add_runtime_dependency 'faraday-excon', '>= 2.0.0'
|
31
|
+
s.add_runtime_dependency "faraday_middleware-aws-sigv4", "~> 1.0.1"
|
32
|
+
|
33
|
+
s.add_development_dependency 'rake', '>= 0'
|
34
|
+
s.add_development_dependency 'webrick', '~> 1.7.0'
|
35
|
+
s.add_development_dependency 'webmock', '~> 3.18.1'
|
36
|
+
s.add_development_dependency 'test-unit', '~> 3.3.0'
|
37
|
+
s.add_development_dependency 'minitest', '~> 5.8'
|
38
|
+
s.add_development_dependency 'flexmock', '~> 2.0'
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in fluent-plugin-elasticsearch.gemspec
|
4
|
+
gemspec :path => "../"
|
5
|
+
|
6
|
+
gem 'simplecov', require: false
|
7
|
+
gem 'coveralls', ">= 0.8.0", require: false
|
8
|
+
gem 'strptime', require: false if RUBY_ENGINE == "ruby" && RUBY_VERSION =~ /^2/
|
9
|
+
gem "irb" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.6"
|
10
|
+
gem "elasticsearch", "~> 6.8.1"
|
11
|
+
gem "elasticsearch-xpack"
|
12
|
+
gem "oj"
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
#
|
3
|
+
# The fluent-plugin-opensearch Contributors require contributions made to
|
4
|
+
# this file be licensed under the Apache-2.0 license or a
|
5
|
+
# compatible open source license.
|
6
|
+
#
|
7
|
+
# Modifications Copyright OpenSearch Contributors. See
|
8
|
+
# GitHub history for details.
|
9
|
+
#
|
10
|
+
# Licensed toUken Inc. under one or more contributor
|
11
|
+
# license agreements. See the NOTICE file distributed with
|
12
|
+
# this work for additional information regarding copyright
|
13
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
14
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
15
|
+
# not use this file except in compliance with the License.
|
16
|
+
# You may obtain a copy of the License at
|
17
|
+
#
|
18
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
19
|
+
#
|
20
|
+
# Unless required by applicable law or agreed to in writing,
|
21
|
+
# software distributed under the License is distributed on an
|
22
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
23
|
+
# KIND, either express or implied. See the License for the
|
24
|
+
# specific language governing permissions and limitations
|
25
|
+
# under the License.
|
26
|
+
|
27
|
+
require 'fluent/log'
|
28
|
+
# For opensearch-ruby v1.0.0 or later which is based on elasticsearch-ruby v7.14 master tree
|
29
|
+
# logger for Elasticsearch::Loggable required the following methods:
|
30
|
+
#
|
31
|
+
# * debug?
|
32
|
+
# * info?
|
33
|
+
# * warn?
|
34
|
+
# * error?
|
35
|
+
# * fatal?
|
36
|
+
|
37
|
+
module Fluent
|
38
|
+
class Log
|
39
|
+
# OpenSearch::Loggable does not request trace? method.
|
40
|
+
# def trace?
|
41
|
+
# @level <= LEVEL_TRACE
|
42
|
+
# end
|
43
|
+
|
44
|
+
def debug?
|
45
|
+
@level <= LEVEL_DEBUG
|
46
|
+
end
|
47
|
+
|
48
|
+
def info?
|
49
|
+
@level <= LEVEL_INFO
|
50
|
+
end
|
51
|
+
|
52
|
+
def warn?
|
53
|
+
@level <= LEVEL_WARN
|
54
|
+
end
|
55
|
+
|
56
|
+
def error?
|
57
|
+
@level <= LEVEL_ERROR
|
58
|
+
end
|
59
|
+
|
60
|
+
def fatal?
|
61
|
+
@level <= LEVEL_FATAL
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
#
|
3
|
+
# The fluent-plugin-opensearch Contributors require contributions made to
|
4
|
+
# this file be licensed under the Apache-2.0 license or a
|
5
|
+
# compatible open source license.
|
6
|
+
#
|
7
|
+
# Modifications Copyright fluent-plugin-opensearch Contributors. See
|
8
|
+
# GitHub history for details.
|
9
|
+
#
|
10
|
+
# Licensed to Uken Inc. under one or more contributor
|
11
|
+
# license agreements. See the NOTICE file distributed with
|
12
|
+
# this work for additional information regarding copyright
|
13
|
+
# ownership. Uken Inc. licenses this file to you under
|
14
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
15
|
+
# not use this file except in compliance with the License.
|
16
|
+
# You may obtain a copy of the License at
|
17
|
+
#
|
18
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
19
|
+
#
|
20
|
+
# Unless required by applicable law or agreed to in writing,
|
21
|
+
# software distributed under the License is distributed on an
|
22
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
23
|
+
# KIND, either express or implied. See the License for the
|
24
|
+
# specific language governing permissions and limitations
|
25
|
+
# under the License.
|
26
|
+
|
27
|
+
require 'securerandom'
|
28
|
+
require 'base64'
|
29
|
+
require 'fluent/plugin/filter'
|
30
|
+
|
31
|
+
module Fluent::Plugin
|
32
|
+
class OpenSearchGenidFilter < Filter
|
33
|
+
Fluent::Plugin.register_filter('opensearch_genid', self)
|
34
|
+
|
35
|
+
config_param :hash_id_key, :string, :default => '_hash'
|
36
|
+
config_param :include_tag_in_seed, :bool, :default => false
|
37
|
+
config_param :include_time_in_seed, :bool, :default => false
|
38
|
+
config_param :use_record_as_seed, :bool, :default => false
|
39
|
+
config_param :use_entire_record, :bool, :default => false
|
40
|
+
config_param :record_keys, :array, :default => []
|
41
|
+
config_param :separator, :string, :default => '_'
|
42
|
+
config_param :hash_type, :enum, list: [:md5, :sha1, :sha256, :sha512], :default => :sha1
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def configure(conf)
|
49
|
+
super
|
50
|
+
|
51
|
+
if !@use_entire_record
|
52
|
+
if @record_keys.empty? && @use_record_as_seed
|
53
|
+
raise Fluent::ConfigError, "When using record as hash seed, users must specify `record_keys`."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if @use_record_as_seed
|
58
|
+
class << self
|
59
|
+
alias_method :filter, :filter_seed_as_record
|
60
|
+
end
|
61
|
+
else
|
62
|
+
class << self
|
63
|
+
alias_method :filter, :filter_simple
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def filter(tag, time, record)
|
69
|
+
# for safety.
|
70
|
+
end
|
71
|
+
|
72
|
+
def filter_simple(tag, time, record)
|
73
|
+
record[@hash_id_key] = Base64.strict_encode64(SecureRandom.uuid)
|
74
|
+
record
|
75
|
+
end
|
76
|
+
|
77
|
+
def filter_seed_as_record(tag, time, record)
|
78
|
+
seed = ""
|
79
|
+
seed += tag + separator if @include_tag_in_seed
|
80
|
+
seed += time.to_s + separator if @include_time_in_seed
|
81
|
+
if @use_entire_record
|
82
|
+
record.each {|k,v| seed += "|#{k}|#{v}"}
|
83
|
+
else
|
84
|
+
seed += record_keys.map {|k| record[k]}.join(separator)
|
85
|
+
end
|
86
|
+
record[@hash_id_key] = Base64.strict_encode64(encode_hash(@hash_type, seed))
|
87
|
+
record
|
88
|
+
end
|
89
|
+
|
90
|
+
def encode_hash(type, seed)
|
91
|
+
case type
|
92
|
+
when :md5
|
93
|
+
Digest::MD5.digest(seed)
|
94
|
+
when :sha1
|
95
|
+
Digest::SHA1.digest(seed)
|
96
|
+
when :sha256
|
97
|
+
Digest::SHA256.digest(seed)
|
98
|
+
when :sha512
|
99
|
+
Digest::SHA512.digest(seed)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,410 @@
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
2
|
+
#
|
3
|
+
# The fluent-plugin-opensearch Contributors require contributions made to
|
4
|
+
# this file be licensed under the Apache-2.0 license or a
|
5
|
+
# compatible open source license.
|
6
|
+
#
|
7
|
+
# Modifications Copyright fluent-plugin-opensearch Contributors. See
|
8
|
+
# GitHub history for details.
|
9
|
+
#
|
10
|
+
# Licensed to Uken Inc. under one or more contributor
|
11
|
+
# license agreements. See the NOTICE file distributed with
|
12
|
+
# this work for additional information regarding copyright
|
13
|
+
# ownership. Uken Inc. licenses this file to you under
|
14
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
15
|
+
# not use this file except in compliance with the License.
|
16
|
+
# You may obtain a copy of the License at
|
17
|
+
#
|
18
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
19
|
+
#
|
20
|
+
# Unless required by applicable law or agreed to in writing,
|
21
|
+
# software distributed under the License is distributed on an
|
22
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
23
|
+
# KIND, either express or implied. See the License for the
|
24
|
+
# specific language governing permissions and limitations
|
25
|
+
# under the License.
|
26
|
+
|
27
|
+
require 'opensearch'
|
28
|
+
|
29
|
+
require 'faraday/excon'
|
30
|
+
require 'fluent/log-ext'
|
31
|
+
require 'fluent/plugin/input'
|
32
|
+
require_relative 'opensearch_constants'
|
33
|
+
|
34
|
+
module Fluent::Plugin
|
35
|
+
class OpenSearchInput < Input
|
36
|
+
class UnrecoverableRequestFailure < Fluent::UnrecoverableError; end
|
37
|
+
|
38
|
+
DEFAULT_RELOAD_AFTER = -1
|
39
|
+
DEFAULT_STORAGE_TYPE = 'local'
|
40
|
+
METADATA = "@metadata".freeze
|
41
|
+
|
42
|
+
helpers :timer, :thread
|
43
|
+
|
44
|
+
Fluent::Plugin.register_input('opensearch', self)
|
45
|
+
|
46
|
+
config_param :tag, :string
|
47
|
+
config_param :host, :string, :default => 'localhost'
|
48
|
+
config_param :port, :integer, :default => 9200
|
49
|
+
config_param :user, :string, :default => nil
|
50
|
+
config_param :password, :string, :default => nil, :secret => true
|
51
|
+
config_param :path, :string, :default => nil
|
52
|
+
config_param :scheme, :enum, :list => [:https, :http], :default => :http
|
53
|
+
config_param :hosts, :string, :default => nil
|
54
|
+
config_param :index_name, :string, :default => "fluentd"
|
55
|
+
config_param :parse_timestamp, :bool, :default => false
|
56
|
+
config_param :timestamp_key_format, :string, :default => nil
|
57
|
+
config_param :timestamp_parse_error_tag, :string, :default => 'opensearch_plugin.input.time.error'
|
58
|
+
config_param :query, :hash, :default => {"sort" => [ "_doc" ]}
|
59
|
+
config_param :scroll, :string, :default => "1m"
|
60
|
+
config_param :size, :integer, :default => 1000
|
61
|
+
config_param :num_slices, :integer, :default => 1
|
62
|
+
config_param :interval, :size, :default => 5
|
63
|
+
config_param :repeat, :bool, :default => true
|
64
|
+
config_param :http_backend, :enum, list: [:excon, :typhoeus], :default => :excon
|
65
|
+
config_param :request_timeout, :time, :default => 5
|
66
|
+
config_param :reload_connections, :bool, :default => true
|
67
|
+
config_param :reload_on_failure, :bool, :default => false
|
68
|
+
config_param :resurrect_after, :time, :default => 60
|
69
|
+
config_param :reload_after, :integer, :default => DEFAULT_RELOAD_AFTER
|
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, :secret => true
|
74
|
+
config_param :ca_file, :string, :default => nil
|
75
|
+
config_param :ssl_version, :enum, list: [:SSLv23, :TLSv1, :TLSv1_1, :TLSv1_2], :default => :TLSv1_2
|
76
|
+
config_param :with_transporter_log, :bool, :default => false
|
77
|
+
config_param :emit_error_label_event, :bool, :default => true
|
78
|
+
config_param :sniffer_class_name, :string, :default => nil
|
79
|
+
config_param :custom_headers, :hash, :default => {}
|
80
|
+
config_param :docinfo_fields, :array, :default => ['_index', '_type', '_id']
|
81
|
+
config_param :docinfo_target, :string, :default => METADATA
|
82
|
+
config_param :docinfo, :bool, :default => false
|
83
|
+
config_param :infinite_check_connection, :bool, :default => true
|
84
|
+
|
85
|
+
include Fluent::Plugin::OpenSearchConstants
|
86
|
+
|
87
|
+
def initialize
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
def configure(conf)
|
92
|
+
super
|
93
|
+
|
94
|
+
@timestamp_parser = create_time_parser
|
95
|
+
@backend_options = backend_options
|
96
|
+
|
97
|
+
raise Fluent::ConfigError, "`password` must be present if `user` is present" if @user && @password.nil?
|
98
|
+
|
99
|
+
if @user && m = @user.match(/%{(?<user>.*)}/)
|
100
|
+
@user = URI.encode_www_form_component(m["user"])
|
101
|
+
end
|
102
|
+
if @password && m = @password.match(/%{(?<password>.*)}/)
|
103
|
+
@password = URI.encode_www_form_component(m["password"])
|
104
|
+
end
|
105
|
+
|
106
|
+
@transport_logger = nil
|
107
|
+
if @with_transporter_log
|
108
|
+
@transport_logger = log
|
109
|
+
log_level = conf['@log_level'] || conf['log_level']
|
110
|
+
log.warn "Consider to specify log_level with @log_level." unless log_level
|
111
|
+
end
|
112
|
+
@current_config = nil
|
113
|
+
# Specify @sniffer_class before calling #client.
|
114
|
+
@sniffer_class = nil
|
115
|
+
begin
|
116
|
+
@sniffer_class = Object.const_get(@sniffer_class_name) if @sniffer_class_name
|
117
|
+
rescue Exception => ex
|
118
|
+
raise Fluent::ConfigError, "Could not load sniffer class #{@sniffer_class_name}: #{ex}"
|
119
|
+
end
|
120
|
+
|
121
|
+
@options = {
|
122
|
+
:index => @index_name,
|
123
|
+
:scroll => @scroll,
|
124
|
+
:size => @size
|
125
|
+
}
|
126
|
+
@base_query = @query
|
127
|
+
end
|
128
|
+
|
129
|
+
def backend_options
|
130
|
+
case @http_backend
|
131
|
+
when :excon
|
132
|
+
{ client_key: @client_key, client_cert: @client_cert, client_key_pass: @client_key_pass }
|
133
|
+
when :typhoeus
|
134
|
+
require 'typhoeus'
|
135
|
+
{ sslkey: @client_key, sslcert: @client_cert, keypasswd: @client_key_pass }
|
136
|
+
end
|
137
|
+
rescue LoadError => ex
|
138
|
+
log.error_backtrace(ex.backtrace)
|
139
|
+
raise Fluent::ConfigError, "You must install #{@http_backend} gem. Exception: #{ex}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_escaped_userinfo(host_str)
|
143
|
+
if m = host_str.match(/(?<scheme>.*)%{(?<user>.*)}:%{(?<password>.*)}(?<path>@.*)/)
|
144
|
+
m["scheme"] +
|
145
|
+
URI.encode_www_form_component(m["user"]) +
|
146
|
+
':' +
|
147
|
+
URI.encode_www_form_component(m["password"]) +
|
148
|
+
m["path"]
|
149
|
+
else
|
150
|
+
host_str
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_connection_options(con_host=nil)
|
155
|
+
|
156
|
+
hosts = if con_host || @hosts
|
157
|
+
(con_host || @hosts).split(',').map do |host_str|
|
158
|
+
# Support legacy hosts format host:port,host:port,host:port...
|
159
|
+
if host_str.match(%r{^[^:]+(\:\d+)?$})
|
160
|
+
{
|
161
|
+
host: host_str.split(':')[0],
|
162
|
+
port: (host_str.split(':')[1] || @port).to_i,
|
163
|
+
scheme: @scheme.to_s
|
164
|
+
}
|
165
|
+
else
|
166
|
+
# New hosts format expects URLs such as http://logs.foo.com,https://john:pass@logs2.foo.com/elastic
|
167
|
+
uri = URI(get_escaped_userinfo(host_str))
|
168
|
+
%w(user password path).inject(host: uri.host, port: uri.port, scheme: uri.scheme) do |hash, key|
|
169
|
+
hash[key.to_sym] = uri.public_send(key) unless uri.public_send(key).nil? || uri.public_send(key) == ''
|
170
|
+
hash
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end.compact
|
174
|
+
else
|
175
|
+
[{host: @host, port: @port, scheme: @scheme.to_s}]
|
176
|
+
end.each do |host|
|
177
|
+
host.merge!(user: @user, password: @password) if !host[:user] && @user
|
178
|
+
host.merge!(path: @path) if !host[:path] && @path
|
179
|
+
end
|
180
|
+
|
181
|
+
{
|
182
|
+
hosts: get_reachable_hosts(hosts)
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
def emit_error_label_event(&block)
|
187
|
+
# If `emit_error_label_event` is specified as false, error event emittions are not occurred.
|
188
|
+
if emit_error_label_event
|
189
|
+
block.call
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def start
|
194
|
+
super
|
195
|
+
|
196
|
+
timer_execute(:in_opensearch_timer, @interval, repeat: @repeat, &method(:run))
|
197
|
+
end
|
198
|
+
|
199
|
+
# We might be able to use
|
200
|
+
# Fluent::Parser::TimeParser, but it doesn't quite do what we want - if gives
|
201
|
+
# [sec,nsec] where as we want something we can call `strftime` on...
|
202
|
+
def create_time_parser
|
203
|
+
if @timestamp_key_format
|
204
|
+
begin
|
205
|
+
# Strptime doesn't support all formats, but for those it does it's
|
206
|
+
# blazingly fast.
|
207
|
+
strptime = Strptime.new(@timestamp_key_format)
|
208
|
+
Proc.new { |value|
|
209
|
+
value = convert_numeric_time_into_string(value, @timestamp_key_format) if value.is_a?(Numeric)
|
210
|
+
strptime.exec(value).to_time
|
211
|
+
}
|
212
|
+
rescue
|
213
|
+
# Can happen if Strptime doesn't recognize the format; or
|
214
|
+
# if strptime couldn't be required (because it's not installed -- it's
|
215
|
+
# ruby 2 only)
|
216
|
+
Proc.new { |value|
|
217
|
+
value = convert_numeric_time_into_string(value, @timestamp_key_format) if value.is_a?(Numeric)
|
218
|
+
DateTime.strptime(value, @timestamp_key_format).to_time
|
219
|
+
}
|
220
|
+
end
|
221
|
+
else
|
222
|
+
Proc.new { |value|
|
223
|
+
value = convert_numeric_time_into_string(value) if value.is_a?(Numeric)
|
224
|
+
DateTime.parse(value).to_time
|
225
|
+
}
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def convert_numeric_time_into_string(numeric_time, timestamp_key_format = "%Y-%m-%dT%H:%M:%S.%N%z")
|
230
|
+
numeric_time_parser = Fluent::NumericTimeParser.new(:float)
|
231
|
+
Time.at(numeric_time_parser.parse(numeric_time).to_r).strftime(timestamp_key_format)
|
232
|
+
end
|
233
|
+
|
234
|
+
def parse_time(value, event_time, tag)
|
235
|
+
@timestamp_parser.call(value)
|
236
|
+
rescue => e
|
237
|
+
emit_error_label_event do
|
238
|
+
router.emit_error_event(@timestamp_parse_error_tag, Fluent::Engine.now, {'tag' => tag, 'time' => event_time, 'format' => @timestamp_key_format, 'value' => value}, e)
|
239
|
+
end
|
240
|
+
return Time.at(event_time).to_time
|
241
|
+
end
|
242
|
+
|
243
|
+
def get_reachable_hosts(hosts=nil)
|
244
|
+
reachable_hosts = []
|
245
|
+
attempt = 0
|
246
|
+
loop do
|
247
|
+
hosts.each do |host|
|
248
|
+
begin
|
249
|
+
if @infinite_check_connection == true
|
250
|
+
check_host = OpenSearch::Client.new(
|
251
|
+
host: ["#{host[:scheme]}://#{host[:host]}:#{host[:port]}"],
|
252
|
+
user: host[:user],
|
253
|
+
password: host[:password],
|
254
|
+
reload_connections: true,
|
255
|
+
resurrect_after: @resurrect_after,
|
256
|
+
reload_on_failure: @reload_on_failure,
|
257
|
+
transport_options: { ssl: { verify: @ssl_verify, ca_file: @ca_file, version: @ssl_version } }
|
258
|
+
)
|
259
|
+
response = check_host.ping #https://github.com/opensearch-project/opensearch-ruby/blob/136e1c975fc91b8cb80d7d1134e32c6dbefdb3eb/lib/opensearch/api/actions/ping.rb#L33
|
260
|
+
if response == true
|
261
|
+
reachable_hosts << host
|
262
|
+
else
|
263
|
+
log.warn "Connection to #{host[:scheme]}://#{host[:host]}:#{host[:port]} failed with status code #{response.status}"
|
264
|
+
end
|
265
|
+
else
|
266
|
+
reachable_hosts << host
|
267
|
+
end
|
268
|
+
rescue => e
|
269
|
+
log.warn "Failed to connect to #{host[:scheme]}://#{host[:host]}:#{host[:port]}"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
break unless reachable_hosts.empty?
|
273
|
+
log.info "Attempt ##{attempt += 1} to get reachable hosts"
|
274
|
+
log.info "No reachable hosts found. Retrying in #{@request_timeout} seconds..."
|
275
|
+
sleep(@request_timeout)
|
276
|
+
end
|
277
|
+
reachable_hosts
|
278
|
+
end
|
279
|
+
|
280
|
+
def client(host = nil)
|
281
|
+
# check here to see if we already have a client connection for the given host
|
282
|
+
connection_options = get_connection_options(host)
|
283
|
+
|
284
|
+
@_os = nil unless is_existing_connection(connection_options[:hosts])
|
285
|
+
|
286
|
+
@_os ||= begin
|
287
|
+
@current_config = connection_options[:hosts].clone
|
288
|
+
adapter_conf = lambda {|f| f.adapter @http_backend, @backend_options }
|
289
|
+
local_reload_connections = @reload_connections
|
290
|
+
if local_reload_connections && @reload_after > DEFAULT_RELOAD_AFTER
|
291
|
+
local_reload_connections = @reload_after
|
292
|
+
end
|
293
|
+
|
294
|
+
headers = { 'Content-Type' => "application/json" }.merge(@custom_headers)
|
295
|
+
|
296
|
+
transport = OpenSearch::Transport::Transport::HTTP::Faraday.new(
|
297
|
+
connection_options.merge(
|
298
|
+
options: {
|
299
|
+
reload_connections: local_reload_connections,
|
300
|
+
reload_on_failure: @reload_on_failure,
|
301
|
+
resurrect_after: @resurrect_after,
|
302
|
+
logger: @transport_logger,
|
303
|
+
transport_options: {
|
304
|
+
headers: headers,
|
305
|
+
request: { timeout: @request_timeout },
|
306
|
+
ssl: { verify: @ssl_verify, ca_file: @ca_file, version: @ssl_version }
|
307
|
+
},
|
308
|
+
http: {
|
309
|
+
user: @user,
|
310
|
+
password: @password
|
311
|
+
},
|
312
|
+
sniffer_class: @sniffer_class,
|
313
|
+
}), &adapter_conf)
|
314
|
+
OpenSearch::Client.new transport: transport
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def is_existing_connection(host)
|
319
|
+
# check if the host provided match the current connection
|
320
|
+
return false if @_os.nil?
|
321
|
+
return false if @current_config.nil?
|
322
|
+
return false if host.length != @current_config.length
|
323
|
+
|
324
|
+
for i in 0...host.length
|
325
|
+
if !host[i][:host].eql? @current_config[i][:host] || host[i][:port] != @current_config[i][:port]
|
326
|
+
return false
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
return true
|
331
|
+
end
|
332
|
+
|
333
|
+
def run
|
334
|
+
return run_slice if @num_slices <= 1
|
335
|
+
|
336
|
+
log.warn("Large slice number is specified:(#{@num_slices}). Consider reducing num_slices") if @num_slices > 8
|
337
|
+
|
338
|
+
@num_slices.times.map do |slice_id|
|
339
|
+
thread_create(:"in_opensearch_thread_#{slice_id}") do
|
340
|
+
run_slice(slice_id)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
rescue Faraday::ConnectionFailed => e
|
344
|
+
log.warn "Connection to OpenSearch failed during search in the 'run' method: #{e.message}. Retrying..."
|
345
|
+
retry
|
346
|
+
end
|
347
|
+
|
348
|
+
def run_slice(slice_id=nil)
|
349
|
+
slice_query = @base_query
|
350
|
+
slice_query = slice_query.merge('slice' => { 'id' => slice_id, 'max' => @num_slices}) unless slice_id.nil?
|
351
|
+
result = client.search(@options.merge(:body => Yajl.dump(slice_query) ))
|
352
|
+
es = Fluent::MultiEventStream.new
|
353
|
+
|
354
|
+
result["hits"]["hits"].each {|hit| process_events(hit, es)}
|
355
|
+
has_hits = result['hits']['hits'].any?
|
356
|
+
scroll_id = result['_scroll_id']
|
357
|
+
|
358
|
+
while has_hits && scroll_id
|
359
|
+
result = process_next_scroll_request(es, scroll_id)
|
360
|
+
has_hits = result['has_hits']
|
361
|
+
scroll_id = result['_scroll_id']
|
362
|
+
end
|
363
|
+
|
364
|
+
router.emit_stream(@tag, es)
|
365
|
+
clear_scroll(scroll_id)
|
366
|
+
end
|
367
|
+
|
368
|
+
def clear_scroll(scroll_id)
|
369
|
+
client.clear_scroll(scroll_id: scroll_id) if scroll_id
|
370
|
+
rescue => e
|
371
|
+
# ignore & log any clear_scroll errors
|
372
|
+
log.warn("Ignoring clear_scroll exception", message: e.message, exception: e.class)
|
373
|
+
end
|
374
|
+
|
375
|
+
def process_scroll_request(scroll_id)
|
376
|
+
client.scroll(:body => { :scroll_id => scroll_id }, :scroll => @scroll)
|
377
|
+
end
|
378
|
+
|
379
|
+
def process_next_scroll_request(es, scroll_id)
|
380
|
+
result = process_scroll_request(scroll_id)
|
381
|
+
result['hits']['hits'].each { |hit| process_events(hit, es) }
|
382
|
+
{'has_hits' => result['hits']['hits'].any?, '_scroll_id' => result['_scroll_id']}
|
383
|
+
end
|
384
|
+
|
385
|
+
def process_events(hit, es)
|
386
|
+
event = hit["_source"]
|
387
|
+
time = Fluent::Engine.now
|
388
|
+
if @parse_timestamp
|
389
|
+
if event.has_key?(TIMESTAMP_FIELD)
|
390
|
+
rts = event[TIMESTAMP_FIELD]
|
391
|
+
time = parse_time(rts, time, @tag)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
if @docinfo
|
395
|
+
docinfo_target = event[@docinfo_target] || {}
|
396
|
+
|
397
|
+
unless docinfo_target.is_a?(Hash)
|
398
|
+
raise UnrecoverableError, "incompatible type for the docinfo_target=#{@docinfo_target} field in the `_source` document, expected a hash got:", :type => docinfo_target.class, :event => event
|
399
|
+
end
|
400
|
+
|
401
|
+
@docinfo_fields.each do |field|
|
402
|
+
docinfo_target[field] = hit[field]
|
403
|
+
end
|
404
|
+
|
405
|
+
event[@docinfo_target] = docinfo_target
|
406
|
+
end
|
407
|
+
es.add(time, event)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|