logstash-input-redis 3.6.1 → 3.7.0
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 +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
|