elastic-transport 8.0.0.pre1
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/.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
|