fluent-plugin-input-opensearch 1.1.9
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 +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
|