Skip to content

Commit 93542f9

Browse files
committed
Added support for DLQ
1. Used the same logic as elasticsearch output plugin to find out if dlq is enabled or not [execution_context? && execution_context.dlq_writer? && execution_context.dlq_writer is not a dummy writer?] 2. if dlq is enabled, send it to dlq_writer or log and drop the the events otherwise. Fixes logstash-plugins#109 Signed-off-by: RashmiRam <[email protected]>
1 parent 2cca9dc commit 93542f9

File tree

2 files changed

+82
-3
lines changed

2 files changed

+82
-3
lines changed

lib/logstash/outputs/http.rb

+27-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ class LogStash::Outputs::Http < LogStash::Outputs::Base
6060
# If encountered as response codes this plugin will retry these requests
6161
config :retryable_codes, :validate => :number, :list => true, :default => [429, 500, 502, 503, 504]
6262

63+
# If encountered as response codes, this plugin will write these events to DLQ
64+
config :dlq_retryable_codes, :validate => :number, :list => true, :default => [400, 403, 404, 401]
65+
6366
# If you would like to consider some non-2xx codes to be successes
6467
# enumerate them here. Responses returning these codes will be considered successes
6568
config :ignorable_codes, :validate => :number, :list => true
@@ -97,7 +100,7 @@ def register
97100
# tokens must be added back by the client on success
98101
@request_tokens = SizedQueue.new(@pool_max)
99102
@pool_max.times {|t| @request_tokens << true }
100-
103+
@dlq_writer = dlq_enabled? ? execution_context.dlq_writer : nil
101104
@requests = Array.new
102105

103106
if @content_type.nil?
@@ -154,6 +157,15 @@ def log_error_response(response, url, event)
154157
)
155158
end
156159

160+
def write_to_dlq(url, event, response)
161+
# To support bwc, we check if DLQ exists. otherwise we log and drop event (previous behavior)
162+
if @dlq_writer
163+
@dlq_writer.write(event, "Sending #{response.code} erred HTTP request to DLQ, url: #{url}, response: #{response}")
164+
else
165+
log_error_response(response, url, event)
166+
end
167+
end
168+
157169
def send_events(events)
158170
successes = java.util.concurrent.atomic.AtomicInteger.new(0)
159171
failures = java.util.concurrent.atomic.AtomicInteger.new(0)
@@ -242,6 +254,9 @@ def send_event(event, attempt)
242254
if retryable_response?(response)
243255
log_retryable_response(response)
244256
return :retry, event, attempt
257+
elsif dlq_retryable_response?(response)
258+
write_to_dlq(url, event, response)
259+
return :failure, event, attempt
245260
else
246261
log_error_response(response, url, event)
247262
return :failure, event, attempt
@@ -287,6 +302,10 @@ def retryable_response?(response)
287302
@retryable_codes && @retryable_codes.include?(response.code)
288303
end
289304

305+
def dlq_retryable_response?(response)
306+
@dlq_retryable_codes && @dlq_retryable_codes.include?(response.code)
307+
end
308+
290309
def retryable_exception?(exception)
291310
RETRYABLE_MANTICORE_EXCEPTIONS.any? {|me| exception.is_a?(me) }
292311
end
@@ -379,4 +398,11 @@ def validate_format!
379398
end
380399
end
381400
end
401+
402+
def dlq_enabled?
403+
# this is the only way to determine if current logstash is supporting a dlq and dlq is also enabled
404+
# Reference: https://github.com/elastic/logstash/issues/8064
405+
respond_to?(:execution_context) && execution_context.respond_to?(:dlq_writer) &&
406+
!execution_context.dlq_writer.inner_writer.is_a?(::LogStash::Util::DummyDeadLetterQueueWriter)
407+
end
382408
end

spec/outputs/http_spec.rb

+55-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ def self.retry_fail_count()
5656
end
5757

5858
multiroute(%w(get post put patch delete), "/bad") do
59+
self.class.last_request = request
60+
[415, "YUP"]
61+
end
62+
63+
multiroute(%w(get post put patch delete), "/dlq_bad") do
5964
self.class.last_request = request
6065
[400, "YUP"]
6166
end
@@ -117,14 +122,23 @@ def sinatra_run_wait(app, opts)
117122

118123
let(:expected_method) { method.clone.to_sym }
119124
let(:client) { subject.client }
125+
let(:dlq_enabled) { false }
126+
let(:dlq_writer) { double("dlq_writer") }
127+
let(:execution_context) { double("execution_context") }
120128

121129
before do
130+
allow(subject).to receive(:dlq_enabled?).with(any_args).and_return(dlq_enabled)
131+
allow(subject).to receive(:execution_context).with(any_args).and_return(execution_context)
132+
allow(execution_context).to receive(:dlq_writer).with(any_args).and_return(dlq_writer)
133+
allow(dlq_writer).to receive(:write).with(any_args)
134+
122135
subject.register
123136
allow(client).to receive(:send).
124137
with(expected_method, url, anything).
125138
and_call_original
126139
allow(subject).to receive(:log_failure).with(any_args)
127140
allow(subject).to receive(:log_retryable_response).with(any_args)
141+
allow(subject).to receive(:write_to_dlq).with(any_args).and_call_original
128142
end
129143

130144
context 'sending no events' do
@@ -165,21 +179,60 @@ def sinatra_run_wait(app, opts)
165179
it "should log a failure" do
166180
expect(subject).to have_received(:log_failure).with(any_args)
167181
end
182+
183+
it "should not be sent to dlq" do
184+
expect(subject).not_to have_received(:write_to_dlq).with(any_args)
185+
end
168186
end
169187

170188
context "with ignorable failing requests" do
171189
let(:url) { "http://localhost:#{port}/bad"}
172-
let(:verb_behavior_config) { super.merge("ignorable_codes" => [400]) }
190+
let(:verb_behavior_config) { super.merge("ignorable_codes" => [415]) }
173191

174192
before do
175193
subject.multi_receive([event])
176194
end
177195

178-
it "should log a failure" do
196+
it "should not log a failure" do
197+
expect(subject).not_to have_received(:log_failure).with(any_args)
198+
end
199+
200+
it "should not be sent to dlq" do
201+
expect(subject).not_to have_received(:write_to_dlq).with(any_args)
202+
end
203+
end
204+
205+
context "with DLQ qualified failing requests" do
206+
let(:url) { "http://localhost:#{port}/dlq_bad"}
207+
let(:dlq_enabled) { true }
208+
let(:verb_behavior_config) { super.merge("dlq_retryable_codes" => [400]) }
209+
210+
before do
211+
subject.multi_receive([event])
212+
end
213+
214+
it "should write to dlq" do
215+
expect(subject).to have_received(:write_to_dlq).with(any_args)
216+
end
217+
218+
it "should not log a failure" do
179219
expect(subject).not_to have_received(:log_failure).with(any_args)
180220
end
181221
end
182222

223+
context "when DLQ is not enabled" do
224+
let(:url) { "http://localhost:#{port}/dlq_bad"}
225+
let(:verb_behavior_config) { super.merge("dlq_retryable_codes" => [400]) }
226+
227+
before do
228+
subject.multi_receive([event])
229+
end
230+
231+
it "should not send the event to the DLQ instead, instead log" do
232+
expect(subject).to have_received(:log_failure).with(any_args)
233+
end
234+
end
235+
183236
context "with retryable failing requests" do
184237
let(:url) { "http://localhost:#{port}/retry"}
185238

0 commit comments

Comments
 (0)