1
1
require "logstash/outputs/elasticsearch/template_manager"
2
- require "logstash/outputs/elasticsearch/buffer"
3
2
4
3
module LogStash ; module Outputs ; class ElasticSearch ;
5
4
module Common
@@ -13,16 +12,11 @@ def register
13
12
setup_hosts # properly sets @hosts
14
13
build_client
15
14
install_template
16
- setup_buffer_and_handler
17
15
check_action_validity
18
16
19
17
@logger . info ( "New Elasticsearch output" , :class => self . class . name , :hosts => @hosts )
20
18
end
21
19
22
- def receive ( event )
23
- @buffer << event_action_tuple ( event )
24
- end
25
-
26
20
# Receive an array of events and immediately attempt to index them (no buffering)
27
21
def multi_receive ( events )
28
22
events . each_slice ( @flush_size ) do |slice |
@@ -37,10 +31,6 @@ def event_action_tuple(event)
37
31
[ action , params , event ]
38
32
end
39
33
40
- def flush
41
- @buffer . flush
42
- end
43
-
44
34
def setup_hosts
45
35
@hosts = Array ( @hosts )
46
36
if @hosts . empty?
@@ -53,12 +43,6 @@ def install_template
53
43
TemplateManager . install_template ( self )
54
44
end
55
45
56
- def setup_buffer_and_handler
57
- @buffer = ::LogStash ::Outputs ::ElasticSearch ::Buffer . new ( @logger , @flush_size , @idle_flush_time ) do |actions |
58
- retrying_submit ( actions )
59
- end
60
- end
61
-
62
46
def check_action_validity
63
47
raise LogStash ::ConfigurationError , "No action specified!" unless @action
64
48
@@ -75,33 +59,55 @@ def valid_actions
75
59
VALID_HTTP_ACTIONS
76
60
end
77
61
78
- def retrying_submit ( actions )
62
+ def retrying_submit ( actions )
79
63
# Initially we submit the full list of actions
80
64
submit_actions = actions
81
65
66
+ sleep_interval = @retry_initial_interval
67
+
82
68
while submit_actions && submit_actions . length > 0
83
- return if ! submit_actions || submit_actions . empty? # If everything's a success we move along
69
+
84
70
# We retry with whatever is didn't succeed
85
71
begin
86
72
submit_actions = submit ( submit_actions )
73
+ if submit_actions && submit_actions . size > 0
74
+ @logger . error ( "Retrying individual actions" )
75
+ submit_actions . each { |action | @logger . error ( "Action" , action ) }
76
+ end
87
77
rescue => e
88
- @logger . warn ( "Encountered an unexpected error submitting a bulk request! Will retry." ,
89
- :message => e . message ,
78
+ @logger . error ( "Encountered an unexpected error submitting a bulk request! Will retry." ,
79
+ :error_message => e . message ,
90
80
:class => e . class . name ,
91
81
:backtrace => e . backtrace )
92
82
end
93
83
94
- sleep @retry_max_interval if submit_actions && submit_actions . length > 0
84
+ # Everything was a success!
85
+ break if !submit_actions || submit_actions . empty?
86
+
87
+ # If we're retrying the action sleep for the recommended interval
88
+ # Double the interval for the next time through to achieve exponential backoff
89
+ Stud . stoppable_sleep ( sleep_interval ) { @stopping . true? }
90
+ sleep_interval = next_sleep_interval ( sleep_interval )
95
91
end
96
92
end
97
93
98
- def submit ( actions )
99
- es_actions = actions . map { |a , doc , event | [ a , doc , event . to_hash ] }
94
+ def sleep_for_interval ( sleep_interval )
95
+ Stud . stoppable_sleep ( sleep_interval ) { @stopping . true? }
96
+ next_sleep_interval ( sleep_interval )
97
+ end
100
98
101
- bulk_response = safe_bulk ( es_actions , actions )
99
+ def next_sleep_interval ( current_interval )
100
+ doubled = current_interval * 2
101
+ doubled > @retry_max_interval ? @retry_max_interval : doubled
102
+ end
102
103
103
- # If there are no errors, we're done here!
104
- return unless bulk_response [ "errors" ]
104
+ def submit ( actions )
105
+ bulk_response = safe_bulk ( actions )
106
+
107
+ # If the response is nil that means we were in a retry loop
108
+ # and aborted since we're shutting down
109
+ # If it did return and there are no errors we're good as well
110
+ return if bulk_response . nil? || !bulk_response [ "errors" ]
105
111
106
112
actions_to_retry = [ ]
107
113
bulk_response [ "items" ] . each_with_index do |response , idx |
@@ -168,38 +174,64 @@ def get_event_type(event)
168
174
end
169
175
170
176
# Rescue retryable errors during bulk submission
171
- def safe_bulk ( es_actions , actions )
172
- @client . bulk ( es_actions )
173
- rescue Manticore ::SocketException , Manticore ::SocketTimeout => e
174
- # If we can't even connect to the server let's just print out the URL (:hosts is actually a URL)
175
- # and let the user sort it out from there
176
- @logger . error (
177
- "Attempted to send a bulk request to Elasticsearch configured at '#{ @client . client_options [ :hosts ] } '," +
178
- " but Elasticsearch appears to be unreachable or down!" ,
179
- :error_message => e . message ,
180
- :class => e . class . name ,
181
- :client_config => @client . client_options ,
182
- )
183
- @logger . debug ( "Failed actions for last bad bulk request!" , :actions => actions )
184
-
185
- # We retry until there are no errors! Errors should all go to the retry queue
186
- sleep @retry_max_interval
187
- retry unless @stopping . true?
188
- rescue => e
189
- # For all other errors print out full connection issues
190
- @logger . error (
191
- "Attempted to send a bulk request to Elasticsearch configured at '#{ @client . client_options [ :hosts ] } '," +
192
- " but an error occurred and it failed! Are you sure you can reach elasticsearch from this machine using " +
193
- "the configuration provided?" ,
194
- :error_message => e . message ,
195
- :error_class => e . class . name ,
196
- :backtrace => e . backtrace ,
197
- :client_config => @client . client_options ,
198
- )
199
-
200
- @logger . debug ( "Failed actions for last bad bulk request!" , :actions => actions )
201
-
202
- raise e
177
+ def safe_bulk ( actions )
178
+ sleep_interval = @retry_initial_interval
179
+ begin
180
+ es_actions = actions . map { |action_type , params , event | [ action_type , params , event . to_hash ] }
181
+ response = @client . bulk ( es_actions )
182
+ response
183
+ rescue ::LogStash ::Outputs ::ElasticSearch ::HttpClient ::Pool ::HostUnreachableError => e
184
+ # If we can't even connect to the server let's just print out the URL (:hosts is actually a URL)
185
+ # and let the user sort it out from there
186
+ @logger . error (
187
+ "Attempted to send a bulk request to elasticsearch'" +
188
+ " but Elasticsearch appears to be unreachable or down!" ,
189
+ :error_message => e . message ,
190
+ :class => e . class . name ,
191
+ :will_retry_in_seconds => sleep_interval
192
+ )
193
+ @logger . debug ( "Failed actions for last bad bulk request!" , :actions => actions )
194
+
195
+ # We retry until there are no errors! Errors should all go to the retry queue
196
+ sleep_interval = sleep_for_interval ( sleep_interval )
197
+ retry unless @stopping . true?
198
+ rescue ::LogStash ::Outputs ::ElasticSearch ::HttpClient ::Pool ::NoConnectionAvailableError => e
199
+ @logger . error (
200
+ "Attempted to send a bulk request to elasticsearch, but no there are no living connections in the connection pool. Perhaps Elasticsearch is unreachable or down?" ,
201
+ :error_message => e . message ,
202
+ :class => e . class . name ,
203
+ :will_retry_in_seconds => sleep_interval
204
+ )
205
+ Stud . stoppable_sleep ( sleep_interval ) { @stopping . true? }
206
+ sleep_interval = next_sleep_interval ( sleep_interval )
207
+ retry unless @stopping . true?
208
+ rescue ::LogStash ::Outputs ::ElasticSearch ::HttpClient ::Pool ::BadResponseCodeError => e
209
+ if RETRYABLE_CODES . include? ( e . response_code )
210
+ log_hash = { :code => e . response_code , :url => e . url }
211
+ log_hash [ :body ] = e . body if @logger . debug? # Generally this is too verbose
212
+ @logger . error ( "Attempted to send a bulk request to elasticsearch but received a bad HTTP response code!" , log_hash )
213
+
214
+ sleep_interval = sleep_for_interval ( sleep_interval )
215
+ retry unless @stopping . true?
216
+ else
217
+ @logger . error ( "Got a bad response code from server, but this code is not considered retryable. Request will be dropped" , :code => e . code )
218
+ end
219
+ rescue => e
220
+ # Stuff that should never happen
221
+ # For all other errors print out full connection issues
222
+ @logger . error (
223
+ "An unknown error occurred sending a bulk request to Elasticsearch. We will retry indefinitely" ,
224
+ :error_message => e . message ,
225
+ :error_class => e . class . name ,
226
+ :backtrace => e . backtrace
227
+ )
228
+
229
+ @logger . debug ( "Failed actions for last bad bulk request!" , :actions => actions )
230
+
231
+ # We retry until there are no errors! Errors should all go to the retry queue
232
+ sleep_interval = sleep_for_interval ( sleep_interval )
233
+ retry unless @stopping . true?
234
+ end
203
235
end
204
236
end
205
237
end ; end ; end
0 commit comments