elastic-transport 8.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/check_license_headers.rb +33 -0
- data/.github/license-header.txt +16 -0
- data/.github/workflows/license.yml +13 -0
- data/.github/workflows/tests.yml +45 -0
- data/.gitignore +19 -0
- data/CHANGELOG.md +224 -0
- data/Gemfile +38 -0
- data/LICENSE +202 -0
- data/README.md +552 -0
- data/Rakefile +87 -0
- data/elastic-transport.gemspec +74 -0
- data/lib/elastic/transport/client.rb +276 -0
- data/lib/elastic/transport/meta_header.rb +135 -0
- data/lib/elastic/transport/redacted.rb +73 -0
- data/lib/elastic/transport/transport/base.rb +450 -0
- data/lib/elastic/transport/transport/connections/collection.rb +126 -0
- data/lib/elastic/transport/transport/connections/connection.rb +160 -0
- data/lib/elastic/transport/transport/connections/selector.rb +91 -0
- data/lib/elastic/transport/transport/errors.rb +91 -0
- data/lib/elastic/transport/transport/http/curb.rb +120 -0
- data/lib/elastic/transport/transport/http/faraday.rb +95 -0
- data/lib/elastic/transport/transport/http/manticore.rb +179 -0
- data/lib/elastic/transport/transport/loggable.rb +83 -0
- data/lib/elastic/transport/transport/response.rb +36 -0
- data/lib/elastic/transport/transport/serializer/multi_json.rb +52 -0
- data/lib/elastic/transport/transport/sniffer.rb +101 -0
- data/lib/elastic/transport/version.rb +22 -0
- data/lib/elastic/transport.rb +37 -0
- data/lib/elastic-transport.rb +18 -0
- data/spec/elasticsearch/connections/collection_spec.rb +266 -0
- data/spec/elasticsearch/connections/selector_spec.rb +166 -0
- data/spec/elasticsearch/transport/base_spec.rb +264 -0
- data/spec/elasticsearch/transport/client_spec.rb +1651 -0
- data/spec/elasticsearch/transport/meta_header_spec.rb +274 -0
- data/spec/elasticsearch/transport/sniffer_spec.rb +275 -0
- data/spec/spec_helper.rb +90 -0
- data/test/integration/transport_test.rb +98 -0
- data/test/profile/client_benchmark_test.rb +132 -0
- data/test/test_helper.rb +83 -0
- data/test/unit/connection_test.rb +135 -0
- data/test/unit/response_test.rb +30 -0
- data/test/unit/serializer_test.rb +33 -0
- data/test/unit/transport_base_test.rb +664 -0
- data/test/unit/transport_curb_test.rb +135 -0
- data/test/unit/transport_faraday_test.rb +228 -0
- data/test/unit/transport_manticore_test.rb +251 -0
- metadata +412 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
module Transport
|
20
|
+
module Transport
|
21
|
+
module HTTP
|
22
|
+
# Alternative HTTP transport implementation, using the [_Curb_](https://rubygems.org/gems/curb) client.
|
23
|
+
#
|
24
|
+
# @see Transport::Base
|
25
|
+
#
|
26
|
+
class Curb
|
27
|
+
include Base
|
28
|
+
|
29
|
+
# Performs the request by invoking {Transport::Base#perform_request} with a block.
|
30
|
+
#
|
31
|
+
# @return [Response]
|
32
|
+
# @see Transport::Base#perform_request
|
33
|
+
#
|
34
|
+
def perform_request(method, path, params={}, body=nil, headers=nil, opts={})
|
35
|
+
super do |connection, url|
|
36
|
+
connection.connection.url = connection.full_url(path, params)
|
37
|
+
|
38
|
+
case method
|
39
|
+
when 'HEAD'
|
40
|
+
connection.connection.set :nobody, true
|
41
|
+
when 'GET', 'POST', 'PUT', 'DELETE'
|
42
|
+
connection.connection.set :nobody, false
|
43
|
+
|
44
|
+
connection.connection.put_data = __convert_to_json(body) if body
|
45
|
+
|
46
|
+
if headers
|
47
|
+
if connection.connection.headers
|
48
|
+
connection.connection.headers.merge!(headers)
|
49
|
+
else
|
50
|
+
connection.connection.headers = headers
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
else raise ArgumentError, "Unsupported HTTP method: #{method}"
|
55
|
+
end
|
56
|
+
|
57
|
+
connection.connection.http(method.to_sym)
|
58
|
+
|
59
|
+
response_headers = {}
|
60
|
+
response_headers['content-type'] = 'application/json' if connection.connection.header_str =~ /\/json/
|
61
|
+
|
62
|
+
Response.new connection.connection.response_code,
|
63
|
+
decompress_response(connection.connection.body_str),
|
64
|
+
response_headers
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Builds and returns a connection
|
69
|
+
#
|
70
|
+
# @return [Connections::Connection]
|
71
|
+
#
|
72
|
+
def __build_connection(host, options={}, block=nil)
|
73
|
+
client = ::Curl::Easy.new
|
74
|
+
|
75
|
+
apply_headers(client, options)
|
76
|
+
client.url = __full_url(host)
|
77
|
+
|
78
|
+
if host[:user]
|
79
|
+
client.http_auth_types = host[:auth_type] || :basic
|
80
|
+
client.username = host[:user]
|
81
|
+
client.password = host[:password]
|
82
|
+
end
|
83
|
+
|
84
|
+
client.instance_eval(&block) if block
|
85
|
+
|
86
|
+
Connections::Connection.new :host => host, :connection => client
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns an array of implementation specific connection errors.
|
90
|
+
#
|
91
|
+
# @return [Array]
|
92
|
+
#
|
93
|
+
def host_unreachable_exceptions
|
94
|
+
[
|
95
|
+
::Curl::Err::HostResolutionError,
|
96
|
+
::Curl::Err::ConnectionFailedError,
|
97
|
+
::Curl::Err::GotNothingError,
|
98
|
+
::Curl::Err::RecvError,
|
99
|
+
::Curl::Err::SendError,
|
100
|
+
::Curl::Err::TimeoutError
|
101
|
+
]
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def user_agent_header(client)
|
107
|
+
@user_agent ||= begin
|
108
|
+
meta = ["RUBY_VERSION: #{RUBY_VERSION}"]
|
109
|
+
if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
|
110
|
+
meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
|
111
|
+
end
|
112
|
+
meta << "Curb #{Curl::CURB_VERSION}"
|
113
|
+
"elastic-transport-ruby/#{VERSION} (#{meta.join('; ')})"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
module Transport
|
20
|
+
module Transport
|
21
|
+
module HTTP
|
22
|
+
# The default transport implementation, using the [_Faraday_](https://rubygems.org/gems/faraday)
|
23
|
+
# library for abstracting the HTTP client.
|
24
|
+
#
|
25
|
+
# @see Transport::Base
|
26
|
+
#
|
27
|
+
class Faraday
|
28
|
+
include Base
|
29
|
+
|
30
|
+
# Performs the request by invoking {Transport::Base#perform_request} with a block.
|
31
|
+
#
|
32
|
+
# @return [Response]
|
33
|
+
# @see Transport::Base#perform_request
|
34
|
+
#
|
35
|
+
def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
|
36
|
+
super do |connection, url|
|
37
|
+
headers = if connection.connection.headers
|
38
|
+
if !headers.nil?
|
39
|
+
connection.connection.headers.merge(headers)
|
40
|
+
else
|
41
|
+
connection.connection.headers
|
42
|
+
end
|
43
|
+
else
|
44
|
+
headers
|
45
|
+
end
|
46
|
+
|
47
|
+
response = connection.connection.run_request(method.downcase.to_sym,
|
48
|
+
url,
|
49
|
+
( body ? __convert_to_json(body) : nil ),
|
50
|
+
headers)
|
51
|
+
|
52
|
+
Response.new response.status, decompress_response(response.body), response.headers
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Builds and returns a connection
|
57
|
+
#
|
58
|
+
# @return [Connections::Connection]
|
59
|
+
#
|
60
|
+
def __build_connection(host, options={}, block=nil)
|
61
|
+
client = ::Faraday.new(__full_url(host), options, &block)
|
62
|
+
apply_headers(client, options)
|
63
|
+
Connections::Connection.new :host => host, :connection => client
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns an array of implementation specific connection errors.
|
67
|
+
#
|
68
|
+
# @return [Array]
|
69
|
+
#
|
70
|
+
def host_unreachable_exceptions
|
71
|
+
[
|
72
|
+
::Faraday::ConnectionFailed,
|
73
|
+
::Faraday::TimeoutError,
|
74
|
+
::Faraday.const_defined?(:ServerError) ? ::Faraday::ServerError : nil,
|
75
|
+
::Faraday::SSLError
|
76
|
+
].compact
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def user_agent_header(client)
|
82
|
+
@user_agent ||= begin
|
83
|
+
meta = ["RUBY_VERSION: #{RUBY_VERSION}"]
|
84
|
+
if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
|
85
|
+
meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
|
86
|
+
end
|
87
|
+
meta << "#{client.headers[USER_AGENT_STR]}"
|
88
|
+
"elastic-transport-ruby/#{VERSION} (#{meta.join('; ')})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
require 'manticore'
|
19
|
+
|
20
|
+
module Elastic
|
21
|
+
module Transport
|
22
|
+
module Transport
|
23
|
+
module HTTP
|
24
|
+
# Alternative HTTP transport implementation for JRuby,
|
25
|
+
# using the [_Manticore_](https://github.com/cheald/manticore) client,
|
26
|
+
#
|
27
|
+
# @example HTTP
|
28
|
+
#
|
29
|
+
# require 'elasticsearch/transport/transport/http/manticore'
|
30
|
+
#
|
31
|
+
# client = Elasticsearch::Client.new transport_class: Elasticsearch::Transport::Transport::HTTP::Manticore
|
32
|
+
#
|
33
|
+
# client.transport.connections.first.connection
|
34
|
+
# => #<Manticore::Client:0x56bf7ca6 ...>
|
35
|
+
#
|
36
|
+
# client.info['status']
|
37
|
+
# => 200
|
38
|
+
#
|
39
|
+
# @example HTTPS (All SSL settings are optional,
|
40
|
+
# see http://www.rubydoc.info/gems/manticore/Manticore/Client:initialize)
|
41
|
+
#
|
42
|
+
# require 'elasticsearch/transport/transport/http/manticore'
|
43
|
+
#
|
44
|
+
# client = Elasticsearch::Client.new \
|
45
|
+
# url: 'https://elasticsearch.example.com',
|
46
|
+
# transport_class: Elasticsearch::Transport::Transport::HTTP::Manticore,
|
47
|
+
# ssl: {
|
48
|
+
# truststore: '/tmp/truststore.jks',
|
49
|
+
# truststore_password: 'password',
|
50
|
+
# keystore: '/tmp/keystore.jks',
|
51
|
+
# keystore_password: 'secret',
|
52
|
+
# }
|
53
|
+
#
|
54
|
+
# client.transport.connections.first.connection
|
55
|
+
# => #<Manticore::Client:0xdeadbeef ...>
|
56
|
+
#
|
57
|
+
# client.info['status']
|
58
|
+
# => 200
|
59
|
+
#
|
60
|
+
# @see Transport::Base
|
61
|
+
#
|
62
|
+
class Manticore
|
63
|
+
include Base
|
64
|
+
|
65
|
+
def initialize(arguments={}, &block)
|
66
|
+
@request_options = { headers: (arguments.dig(:transport_options, :headers) || {}) }
|
67
|
+
@manticore = build_client(arguments[:options] || {})
|
68
|
+
super(arguments, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Should just be run once at startup
|
72
|
+
def build_client(options={})
|
73
|
+
client_options = options[:transport_options] || {}
|
74
|
+
client_options[:ssl] = options[:ssl] || {}
|
75
|
+
|
76
|
+
@manticore = ::Manticore::Client.new(client_options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Performs the request by invoking {Transport::Base#perform_request} with a block.
|
80
|
+
#
|
81
|
+
# @return [Response]
|
82
|
+
# @see Transport::Base#perform_request
|
83
|
+
#
|
84
|
+
def perform_request(method, path, params={}, body=nil, headers=nil, opts={})
|
85
|
+
super do |connection, url|
|
86
|
+
params[:body] = __convert_to_json(body) if body
|
87
|
+
params[:headers] = headers if headers
|
88
|
+
params = params.merge @request_options
|
89
|
+
case method
|
90
|
+
when "GET"
|
91
|
+
resp = connection.connection.get(url, params)
|
92
|
+
when "HEAD"
|
93
|
+
resp = connection.connection.head(url, params)
|
94
|
+
when "PUT"
|
95
|
+
resp = connection.connection.put(url, params)
|
96
|
+
when "POST"
|
97
|
+
resp = connection.connection.post(url, params)
|
98
|
+
when "DELETE"
|
99
|
+
resp = connection.connection.delete(url, params)
|
100
|
+
else
|
101
|
+
raise ArgumentError.new "Method #{method} not supported"
|
102
|
+
end
|
103
|
+
Response.new resp.code, resp.read_body, resp.headers
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Builds and returns a collection of connections.
|
108
|
+
# Each connection is a Manticore::Client
|
109
|
+
#
|
110
|
+
# @return [Connections::Collection]
|
111
|
+
#
|
112
|
+
def __build_connections
|
113
|
+
apply_headers(@request_options, options[:transport_options])
|
114
|
+
apply_headers(@request_options, options)
|
115
|
+
|
116
|
+
Connections::Collection.new \
|
117
|
+
:connections => hosts.map { |host|
|
118
|
+
host[:protocol] = host[:scheme] || DEFAULT_PROTOCOL
|
119
|
+
host[:port] ||= DEFAULT_PORT
|
120
|
+
|
121
|
+
host.delete(:user) # auth is not supported here.
|
122
|
+
host.delete(:password) # use the headers
|
123
|
+
|
124
|
+
Connections::Connection.new \
|
125
|
+
:host => host,
|
126
|
+
:connection => @manticore
|
127
|
+
},
|
128
|
+
:selector_class => options[:selector_class],
|
129
|
+
:selector => options[:selector]
|
130
|
+
end
|
131
|
+
|
132
|
+
# Closes all connections by marking them as dead
|
133
|
+
# and closing the underlying HttpClient instances
|
134
|
+
#
|
135
|
+
# @return [Connections::Collection]
|
136
|
+
#
|
137
|
+
def __close_connections
|
138
|
+
# The Manticore adapter uses a single long-lived instance
|
139
|
+
# of Manticore::Client, so we don't close the connections.
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns an array of implementation specific connection errors.
|
143
|
+
#
|
144
|
+
# @return [Array]
|
145
|
+
#
|
146
|
+
def host_unreachable_exceptions
|
147
|
+
[
|
148
|
+
::Manticore::Timeout,
|
149
|
+
::Manticore::SocketException,
|
150
|
+
::Manticore::ClientProtocolException,
|
151
|
+
::Manticore::ResolutionFailure
|
152
|
+
]
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def apply_headers(request_options, options)
|
158
|
+
headers = options&.[](:headers) || {}
|
159
|
+
headers[CONTENT_TYPE_STR] = find_value(headers, CONTENT_TYPE_REGEX) || DEFAULT_CONTENT_TYPE
|
160
|
+
headers[USER_AGENT_STR] = find_value(headers, USER_AGENT_REGEX) || user_agent_header
|
161
|
+
headers[ACCEPT_ENCODING] = GZIP if use_compression?
|
162
|
+
request_options[:headers].merge!(headers)
|
163
|
+
end
|
164
|
+
|
165
|
+
def user_agent_header
|
166
|
+
@user_agent ||= begin
|
167
|
+
meta = ["RUBY_VERSION: #{JRUBY_VERSION}"]
|
168
|
+
if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
|
169
|
+
meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
|
170
|
+
end
|
171
|
+
meta << "Manticore #{::Manticore::VERSION}"
|
172
|
+
"elasticsearch-ruby/#{VERSION} (#{meta.join('; ')})"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Licensed to Elasticsearch B.V. under one or more contributor
|
2
|
+
# license agreements. See the NOTICE file distributed with
|
3
|
+
# this work for additional information regarding copyright
|
4
|
+
# ownership. Elasticsearch B.V. licenses this file to you under
|
5
|
+
# the Apache License, Version 2.0 (the "License"); you may
|
6
|
+
# not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing,
|
12
|
+
# software distributed under the License is distributed on an
|
13
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
14
|
+
# KIND, either express or implied. See the License for the
|
15
|
+
# specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
module Elastic
|
19
|
+
# Module to encapsulate all logging functionality.
|
20
|
+
#
|
21
|
+
# @since 7.0.0
|
22
|
+
module Loggable
|
23
|
+
# Log a debug message.
|
24
|
+
#
|
25
|
+
# @example Log a debug message.
|
26
|
+
# log_debug('Message')
|
27
|
+
#
|
28
|
+
# @param [ String ] message The message to log.
|
29
|
+
#
|
30
|
+
# @since 7.0.0
|
31
|
+
def log_debug(message)
|
32
|
+
logger.debug(message) if logger && logger.debug?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Log an error message.
|
36
|
+
#
|
37
|
+
# @example Log an error message.
|
38
|
+
# log_error('Message')
|
39
|
+
#
|
40
|
+
# @param [ String ] message The message to log.
|
41
|
+
#
|
42
|
+
# @since 7.0.0
|
43
|
+
def log_error(message)
|
44
|
+
logger.error(message) if logger && logger.error?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Log a fatal message.
|
48
|
+
#
|
49
|
+
# @example Log a fatal message.
|
50
|
+
# log_fatal('Message')
|
51
|
+
#
|
52
|
+
# @param [ String ] message The message to log.
|
53
|
+
#
|
54
|
+
# @since 7.0.0
|
55
|
+
def log_fatal(message)
|
56
|
+
logger.fatal(message) if logger && logger.fatal?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Log an info message.
|
60
|
+
#
|
61
|
+
# @example Log an info message.
|
62
|
+
# log_info('Message')
|
63
|
+
#
|
64
|
+
# @param [ String ] message The message to log.
|
65
|
+
#
|
66
|
+
# @since 7.0.0
|
67
|
+
def log_info(message)
|
68
|
+
logger.info(message) if logger && logger.info?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Log a warn message.
|
72
|
+
#
|
73
|
+
# @example Log a warn message.
|
74
|
+
# log_warn('Message')
|
75
|
+
#
|
76
|
+
# @param [ String ] message The message to log.
|
77
|
+
#
|
78
|
+
# @since 7.0.0
|
79
|
+
def log_warn(message)
|
80
|
+
logger.warn(message) if logger && logger.warn?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|