logstash-input-redis 3.6.1 → 3.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/logstash/inputs/redis.rb +62 -42
- data/logstash-input-redis.gemspec +1 -1
- data/spec/inputs/redis_spec.rb +80 -9
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0be6dfe6d1465214b44d17ae4a45482ba364735abb7032fa26e37660845aaba8
|
4
|
+
data.tar.gz: d68f634cac89442c536f8f57713354af640f61dc0cc72561ba0972503c9b2d10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c662434d97dc7d4811f062ef9dfd3c0d6fe1eec8083f27b7f0a0638e35e5ad7ef10a5b322439ed5df5bf45acaa9fff645bf9592cd5a481b9c8a9d753cd022c6
|
7
|
+
data.tar.gz: f96f01c43dfaaa0843645fb737af52408709c27b962231b10781353951c849aa8b83bad85dc4a92fc2b76c5731bca1d01bb5e1998aa2e79799744078b64b8c42
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## 3.7.0
|
2
|
+
- Fix: better (Redis) exception handling [#89](https://github.com/logstash-plugins/logstash-input-redis/pull/89)
|
3
|
+
- Test: start running integration specs on CI
|
4
|
+
|
1
5
|
## 3.6.1
|
2
6
|
- Fix: resolve crash when commands_map is set [#86](https://github.com/logstash-plugins/logstash-input-redis/pull/86)
|
3
7
|
|
@@ -107,26 +107,22 @@ module LogStash module Inputs class Redis < LogStash::Inputs::Threadable
|
|
107
107
|
|
108
108
|
# private
|
109
109
|
def redis_params
|
110
|
+
params = {
|
111
|
+
:timeout => @timeout,
|
112
|
+
:db => @db,
|
113
|
+
:password => @password.nil? ? nil : @password.value,
|
114
|
+
:ssl => @ssl
|
115
|
+
}
|
116
|
+
|
110
117
|
if @path.nil?
|
111
|
-
|
112
|
-
|
113
|
-
:port => @port
|
114
|
-
}
|
118
|
+
params[:host] = @host
|
119
|
+
params[:port] = @port
|
115
120
|
else
|
116
121
|
@logger.warn("Parameter 'path' is set, ignoring parameters: 'host' and 'port'")
|
117
|
-
|
118
|
-
:path => @path
|
119
|
-
}
|
122
|
+
params[:path] = @path
|
120
123
|
end
|
121
124
|
|
122
|
-
|
123
|
-
:timeout => @timeout,
|
124
|
-
:db => @db,
|
125
|
-
:password => @password.nil? ? nil : @password.value,
|
126
|
-
:ssl => @ssl
|
127
|
-
}
|
128
|
-
|
129
|
-
return connectionParams.merge(baseParams)
|
125
|
+
params
|
130
126
|
end
|
131
127
|
|
132
128
|
def new_redis_instance
|
@@ -174,9 +170,12 @@ EOF
|
|
174
170
|
|
175
171
|
# private
|
176
172
|
def list_stop
|
177
|
-
|
173
|
+
redis = @redis # might change during method invocation
|
174
|
+
return if redis.nil? || !redis.connected?
|
178
175
|
|
179
|
-
|
176
|
+
redis.quit rescue nil # does client.disconnect internally
|
177
|
+
# check if input retried while executing
|
178
|
+
list_stop unless redis.equal? @redis
|
180
179
|
@redis = nil
|
181
180
|
end
|
182
181
|
|
@@ -186,15 +185,9 @@ EOF
|
|
186
185
|
begin
|
187
186
|
@redis ||= connect
|
188
187
|
@list_method.call(@redis, output_queue)
|
189
|
-
rescue
|
190
|
-
|
191
|
-
|
192
|
-
@logger.warn("Redis connection problem", info)
|
193
|
-
# Reset the redis variable to trigger reconnect
|
194
|
-
@redis = nil
|
195
|
-
# this sleep does not need to be stoppable as its
|
196
|
-
# in a while !stop? loop
|
197
|
-
sleep 1
|
188
|
+
rescue => e
|
189
|
+
log_error(e)
|
190
|
+
retry if reset_for_error_retry(e)
|
198
191
|
end
|
199
192
|
end
|
200
193
|
end
|
@@ -248,18 +241,19 @@ EOF
|
|
248
241
|
|
249
242
|
# private
|
250
243
|
def subscribe_stop
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
if
|
244
|
+
redis = @redis # might change during method invocation
|
245
|
+
return if redis.nil? || !redis.connected?
|
246
|
+
|
247
|
+
if redis.subscribed?
|
255
248
|
if @data_type == 'pattern_channel'
|
256
|
-
|
249
|
+
redis.punsubscribe
|
257
250
|
else
|
258
|
-
|
251
|
+
redis.unsubscribe
|
259
252
|
end
|
260
|
-
else
|
261
|
-
@redis.disconnect!
|
262
253
|
end
|
254
|
+
redis.close rescue nil # does client.disconnect
|
255
|
+
# check if input retried while executing
|
256
|
+
subscribe_stop unless redis.equal? @redis
|
263
257
|
@redis = nil
|
264
258
|
end
|
265
259
|
|
@@ -268,15 +262,43 @@ EOF
|
|
268
262
|
begin
|
269
263
|
@redis ||= connect
|
270
264
|
yield
|
271
|
-
rescue
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
265
|
+
rescue => e
|
266
|
+
log_error(e)
|
267
|
+
retry if reset_for_error_retry(e)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def log_error(e)
|
272
|
+
info = { message: e.message, exception: e.class }
|
273
|
+
info[:backtrace] = e.backtrace if @logger.debug?
|
274
|
+
|
275
|
+
case e
|
276
|
+
when ::Redis::TimeoutError
|
277
|
+
# expected for channels in case no data is available
|
278
|
+
@logger.debug("Redis timeout, retrying", info)
|
279
|
+
when ::Redis::BaseConnectionError, ::Redis::ProtocolError
|
280
|
+
@logger.warn("Redis connection error", info)
|
281
|
+
when ::Redis::BaseError
|
282
|
+
@logger.error("Redis error", info)
|
283
|
+
when ::LogStash::ShutdownSignal
|
284
|
+
@logger.debug("Received shutdown signal")
|
285
|
+
else
|
286
|
+
info[:backtrace] ||= e.backtrace
|
287
|
+
@logger.error("Unexpected error", info)
|
277
288
|
end
|
278
289
|
end
|
279
290
|
|
291
|
+
# @return [true] if operation is fine to retry
|
292
|
+
def reset_for_error_retry(e)
|
293
|
+
return if e.is_a?(::LogStash::ShutdownSignal)
|
294
|
+
|
295
|
+
# Reset the redis variable to trigger reconnect
|
296
|
+
@redis = nil
|
297
|
+
|
298
|
+
Stud.stoppable_sleep(1) { stop? }
|
299
|
+
!stop? # retry if not stop-ing
|
300
|
+
end
|
301
|
+
|
280
302
|
# private
|
281
303
|
def channel_runner(output_queue)
|
282
304
|
redis_runner do
|
@@ -324,6 +346,4 @@ EOF
|
|
324
346
|
end
|
325
347
|
end
|
326
348
|
|
327
|
-
# end
|
328
|
-
|
329
349
|
end end end # Redis Inputs LogStash
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
|
3
3
|
s.name = 'logstash-input-redis'
|
4
|
-
s.version = '3.
|
4
|
+
s.version = '3.7.0'
|
5
5
|
s.licenses = ['Apache License (2.0)']
|
6
6
|
s.summary = "Reads events from a Redis instance"
|
7
7
|
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
|
data/spec/inputs/redis_spec.rb
CHANGED
@@ -17,11 +17,15 @@ def populate(key, event_count)
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def process(conf, event_count)
|
20
|
-
events = input(conf) do |
|
21
|
-
|
20
|
+
events = input(conf) do |_, queue|
|
21
|
+
sleep 0.1 until queue.size >= event_count
|
22
|
+
queue.size.times.map { queue.pop }
|
22
23
|
end
|
23
|
-
|
24
|
-
|
24
|
+
# due multiple workers we get events out-of-order in the output
|
25
|
+
events.sort! { |a, b| a.get('sequence') <=> b.get('sequence') }
|
26
|
+
expect(events[0].get('sequence')).to eq(0)
|
27
|
+
expect(events[100].get('sequence')).to eq(100)
|
28
|
+
expect(events[1000].get('sequence')).to eq(1000)
|
25
29
|
end
|
26
30
|
|
27
31
|
# integration tests ---------------------
|
@@ -31,7 +35,6 @@ describe "inputs/redis", :redis => true do
|
|
31
35
|
it "should read events from a list" do
|
32
36
|
key = SecureRandom.hex
|
33
37
|
event_count = 1000 + rand(50)
|
34
|
-
# event_count = 100
|
35
38
|
conf = <<-CONFIG
|
36
39
|
input {
|
37
40
|
redis {
|
@@ -163,7 +166,6 @@ describe LogStash::Inputs::Redis do
|
|
163
166
|
allow_any_instance_of( Redis::Client ).to receive(:call_with_timeout) do |_, command, timeout, &block|
|
164
167
|
expect(command[0]).to eql :blpop
|
165
168
|
expect(command[1]).to eql ['foo', 0]
|
166
|
-
expect(command[2]).to eql 1
|
167
169
|
end.and_return ['foo', "{\"foo1\":\"bar\""], nil
|
168
170
|
|
169
171
|
tt = Thread.new do
|
@@ -178,6 +180,69 @@ describe LogStash::Inputs::Redis do
|
|
178
180
|
expect( queue.size ).to be > 0
|
179
181
|
end
|
180
182
|
|
183
|
+
it 'keeps running when a connection error occurs' do
|
184
|
+
raised = false
|
185
|
+
allow_any_instance_of( Redis::Client ).to receive(:call_with_timeout) do |_, command, timeout, &block|
|
186
|
+
expect(command[0]).to eql :blpop
|
187
|
+
unless raised
|
188
|
+
raised = true
|
189
|
+
raise Redis::CannotConnectError.new('test')
|
190
|
+
end
|
191
|
+
['foo', "{\"after\":\"raise\"}"]
|
192
|
+
end
|
193
|
+
|
194
|
+
expect(subject.logger).to receive(:warn).with('Redis connection error',
|
195
|
+
hash_including(:message=>"test", :exception=>Redis::CannotConnectError)
|
196
|
+
).and_call_original
|
197
|
+
|
198
|
+
tt = Thread.new do
|
199
|
+
sleep 2.0 # allow for retry (sleep) after handle_error
|
200
|
+
subject.do_stop
|
201
|
+
end
|
202
|
+
|
203
|
+
subject.run(queue)
|
204
|
+
|
205
|
+
tt.join
|
206
|
+
|
207
|
+
try(3) { expect( queue.size ).to be > 0 }
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'error handling' do
|
211
|
+
|
212
|
+
let(:config) do
|
213
|
+
super().merge 'batch_count' => 2
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'keeps running when a (non-Redis) io error occurs' do
|
217
|
+
raised = false
|
218
|
+
allow(subject).to receive(:connect).and_return redis = double('redis')
|
219
|
+
allow(redis).to receive(:blpop).and_return nil
|
220
|
+
expect(redis).to receive(:evalsha) do
|
221
|
+
unless raised
|
222
|
+
raised = true
|
223
|
+
raise IOError.new('closed stream')
|
224
|
+
end
|
225
|
+
[]
|
226
|
+
end.at_least(1)
|
227
|
+
redis
|
228
|
+
allow(subject).to receive(:stop)
|
229
|
+
|
230
|
+
expect(subject.logger).to receive(:error).with('Unexpected error',
|
231
|
+
hash_including(:message=>'closed stream', :exception=>IOError)
|
232
|
+
).and_call_original
|
233
|
+
|
234
|
+
tt = Thread.new do
|
235
|
+
sleep 2.0 # allow for retry (sleep) after handle_error
|
236
|
+
subject.do_stop
|
237
|
+
end
|
238
|
+
|
239
|
+
subject.run(queue)
|
240
|
+
|
241
|
+
tt.join
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
181
246
|
context "when the batch size is greater than 1" do
|
182
247
|
let(:batch_count) { 10 }
|
183
248
|
|
@@ -233,9 +298,6 @@ describe LogStash::Inputs::Redis do
|
|
233
298
|
end
|
234
299
|
|
235
300
|
it 'multiple close calls, calls to redis once' do
|
236
|
-
# subject.use_redis(redis)
|
237
|
-
# allow(redis).to receive(:blpop).and_return(['foo', 'l1'])
|
238
|
-
# expect(redis).to receive(:connected?).and_return(connected.last)
|
239
301
|
allow_any_instance_of( Redis::Client ).to receive(:connected?).and_return true, false
|
240
302
|
# allow_any_instance_of( Redis::Client ).to receive(:disconnect)
|
241
303
|
quit_calls.each do |call|
|
@@ -249,6 +311,9 @@ describe LogStash::Inputs::Redis do
|
|
249
311
|
end
|
250
312
|
|
251
313
|
context 'for the subscribe data_types' do
|
314
|
+
|
315
|
+
before { subject.register }
|
316
|
+
|
252
317
|
def run_it_thread(inst)
|
253
318
|
Thread.new(inst) do |subj|
|
254
319
|
subj.run(queue)
|
@@ -289,6 +354,8 @@ describe LogStash::Inputs::Redis do
|
|
289
354
|
let(:data_type) { 'channel' }
|
290
355
|
let(:quit_calls) { [:unsubscribe, :connection] }
|
291
356
|
|
357
|
+
before { subject.register }
|
358
|
+
|
292
359
|
context 'mocked redis' do
|
293
360
|
it 'multiple stop calls, calls to redis once', type: :mocked do
|
294
361
|
subject.do_stop
|
@@ -367,6 +434,10 @@ describe LogStash::Inputs::Redis do
|
|
367
434
|
|
368
435
|
["list", "channel", "pattern_channel"].each do |data_type|
|
369
436
|
context data_type do
|
437
|
+
# TODO pending
|
438
|
+
# redis-rb ends up in a read wait loop since we do not use subscribe_with_timeout
|
439
|
+
next unless data_type == 'list'
|
440
|
+
|
370
441
|
it_behaves_like "an interruptible input plugin", :redis => true do
|
371
442
|
let(:config) { { 'key' => 'foo', 'data_type' => data_type } }
|
372
443
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logstash-input-redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elastic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04
|
11
|
+
date: 2021-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|