logstash_writer 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/logstash_writer.rb +68 -40
  4. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 834ed0e5a393419b960245f1d036722ff97412a4009e3586fc9dc7d860fe79e7
4
- data.tar.gz: bb9ef3b35721e7768348f74b83bcea4315ab0b6b3f968bc7874118d7d58be318
3
+ metadata.gz: 3b3f279724a1874b171c9080fb4aae626e844c13b845bec88a50ae832b660a85
4
+ data.tar.gz: cb3463bfddf9673808fb874cc1bca6b1b51527f2eb626134bada7cacff91b856
5
5
  SHA512:
6
- metadata.gz: 2a31b62d8a681acc81a21491cf5b2f26807114bc81237e03f79eb5f053238bd3dab8fcc2394548f3546854534a54f6fce234ef622002ced68ed7a642360460e8
7
- data.tar.gz: 7b857ce439143902b975132ee2f020220bebcdb4010f0c1bbc611cd114aa1d127df75aa11747af3b0b7776cf94c195c3e829a0d6ee37136bd762143391c3545b
6
+ metadata.gz: 41611118bbcc2992196a8c2dc49bec74ce00dc3ec0505b9682132c9e6bcf6e7a8dd48355f35f06cd393f8e2c41460a24b28c53b0c0b0f5ee48c8ec6ec9964503
7
+ data.tar.gz: 5527ef9d6bf2a9ed3de0c3483bf43ed6ffd5e69a4e7b8687bdb9d33d258651bdae95a9b990617007578fb10003533840b6f397d87fa2e955836704f9eb064955
data/README.md CHANGED
@@ -146,9 +146,9 @@ The metrics that are exposed are:
146
146
 
147
147
  * **`logstash_writer_connected_to_server`** -- this flag timeseries (can be
148
148
  either `1` or `0`) is simply a way for you to quickly determine whether
149
- the writer has a server to talk to, if it wants one. That is, this time
150
- series will only be `0` if there's an event to write but no logstash
151
- server can be found to write it to.
149
+ the writer has a server to talk to, if it wants one, and which server it
150
+ is connected to. That is, this time series will only be `0` if there's an
151
+ event to write but no logstash server can be found to write it to.
152
152
 
153
153
  * **`logstash_writer_connect_exceptions_total`** -- a count of exceptions
154
154
  raised whilst attempting to connect to a logstash server, labelled by the
@@ -183,7 +183,7 @@ class LogstashWriter
183
183
  @logger.error("LogstashWriter") { (["Worker thread terminated with exception: #{ex.message} (#{ex.class})"] + ex.backtrace).join("\n ") }
184
184
  end
185
185
  @worker_thread = nil
186
- @socket_mutex.synchronize { (@current_socket.close; @current_socket = nil) if @current_socket }
186
+ @socket_mutex.synchronize { (@current_target.close; @current_target = nil) if @current_target }
187
187
  end
188
188
  end
189
189
 
@@ -201,11 +201,11 @@ class LogstashWriter
201
201
  #
202
202
  def force_disconnect!
203
203
  @socket_mutex.synchronize do
204
- return if @current_socket.nil?
204
+ return if @current_target.nil?
205
205
 
206
- @logger.info("LogstashWriter") { "Forced disconnect from #{describe_peer(@current_socket) }" }
207
- @current_socket.close if @current_socket
208
- @current_socket = nil
206
+ @logger.info("LogstashWriter") { "Forced disconnect from #{@current_target.describe_peer}" }
207
+ @current_target.close
208
+ @current_target = nil
209
209
  end
210
210
 
211
211
  nil
@@ -237,20 +237,14 @@ class LogstashWriter
237
237
  event = @queue.shift
238
238
  end
239
239
 
240
- current_socket do |s|
241
- s.puts event[:content].to_json
242
- stat_sent(describe_peer(s), event[:arrival_timestamp])
240
+ current_target do |t|
241
+ t.socket.puts event[:content].to_json
242
+ stat_sent(t.to_s, event[:arrival_timestamp])
243
243
  @metrics[:write_loop_ok].set({}, 1)
244
244
  error_wait = INITIAL_RETRY_WAIT
245
245
  end
246
246
  rescue StandardError => ex
247
247
  @logger.error("LogstashWriter") { (["Exception in write_loop: #{ex.message} (#{ex.class})"] + ex.backtrace).join("\n ") }
248
- # If there was some sort of error, there's a non-trivial chance the
249
- # socket has gone *boom*, so let's invalidate it and go around again
250
- if @current_socket
251
- @current_socket.close
252
- @current_socket = nil
253
- end
254
248
  @queue_mutex.synchronize { @queue.unshift(event) if event }
255
249
  @metrics[:write_loop_exception].increment(class: ex.class.to_s)
256
250
  @metrics[:write_loop_ok].set({}, 0)
@@ -265,14 +259,14 @@ class LogstashWriter
265
259
  end
266
260
  end
267
261
 
268
- # Yield a TCPSocket connected to the server we currently believe to be
262
+ # Yield a Target connected to the server we currently believe to be
269
263
  # accepting log entries, so that something can send log entries to it.
270
264
  #
271
265
  # The yielding allows us to centralise all error detection and handling
272
266
  # within this one method, and retry sending just by calling `yield` again
273
267
  # when we've connected to another server.
274
268
  #
275
- def current_socket
269
+ def current_target
276
270
  # This could all be handled more cleanly with recursion, but I don't
277
271
  # want to fill the stack if we have to retry a lot of times. Also
278
272
  # can't just use `retry` because not all of the "go around again"
@@ -281,19 +275,25 @@ class LogstashWriter
281
275
 
282
276
  until done
283
277
  @socket_mutex.synchronize do
284
- if @current_socket
278
+ if @current_target
285
279
  begin
286
- yield @current_socket
287
- @metrics[:connected].set({}, 1)
280
+ # Check that our socket is still good to go; if we don't do this,
281
+ # the other end can disconnect, and because we're never normally
282
+ # reading from the socket, we never get the EOFError that normally
283
+ # results, and so the socket remains in CLOSE_WAIT state *forever*.
284
+ @current_target.socket.read_nonblock(1)
285
+
286
+ yield @current_target
287
+ @metrics[:connected].set({ server: @current_target.describe_peer }, 1)
288
288
  done = true
289
- rescue SystemCallError => ex
289
+ rescue SystemCallError, IOError => ex
290
290
  # Something went wrong during the send; disconnect from this
291
291
  # server and recycle
292
- @metrics[:write_exception].increment(server: describe_peer(@current_socket), class: ex.class.to_s)
293
- @logger.info("LogstashWriter") { "Error while writing to current server: #{ex.message} (#{ex.class})" }
294
- @current_socket.close
295
- @current_socket = nil
296
- @metrics[:connected].set({}, 0)
292
+ @metrics[:write_exception].increment(server: @current_target.describe_peer, class: ex.class.to_s)
293
+ @metrics[:connected].set({ server: @current_target.describe_peer }, 0)
294
+ @logger.info("LogstashWriter") { "Error while writing to current server #{@current_target.describe_peer}: #{ex.message} (#{ex.class})" }
295
+ @current_target.close
296
+ @current_target = nil
297
297
 
298
298
  sleep INITIAL_RETRY_WAIT
299
299
  end
@@ -313,10 +313,12 @@ class LogstashWriter
313
313
 
314
314
  if next_server
315
315
  @logger.debug("LogstashWriter") { "Trying to connect to #{next_server.to_s}" }
316
- @current_socket = next_server.socket
316
+ @current_target = next_server
317
+ # Trigger a connection attempt
318
+ @current_target.socket
317
319
  else
318
320
  @logger.debug("LogstashWriter") { "Could not connect to any server; pausing before trying again" }
319
- @current_socket = nil
321
+ @current_target = nil
320
322
  sleep retry_delay
321
323
 
322
324
  # Calculate a longer retry delay next time we fail to connect
@@ -341,18 +343,6 @@ class LogstashWriter
341
343
  end
342
344
  end
343
345
 
344
- # Generate a human-readable description of the remote end of the given
345
- # socket.
346
- #
347
- def describe_peer(s)
348
- pa = s.peeraddr
349
- if pa[0] == "AF_INET6"
350
- "[#{pa[3]}]:#{pa[1]}"
351
- else
352
- "#{pa[3]}:#{pa[1]}"
353
- end
354
- end
355
-
356
346
  # Turn the server_name given in the constructor into a list of Target
357
347
  # objects, suitable for iterating through to find someone to talk to.
358
348
  #
@@ -489,7 +479,17 @@ class LogstashWriter
489
479
  # for any reason.
490
480
  #
491
481
  def socket
492
- TCPSocket.new(@addr, @port)
482
+ @socket ||= TCPSocket.new(@addr, @port)
483
+ end
484
+
485
+ # Shut down the connection.
486
+ #
487
+ # @return [NilClass]
488
+ #
489
+ def close
490
+ @socket.close if @socket
491
+ @socket = nil
492
+ @describe_peer = nil
493
493
  end
494
494
 
495
495
  # Simple string representation of the target.
@@ -499,6 +499,34 @@ class LogstashWriter
499
499
  def to_s
500
500
  "#{@addr}:#{@port}"
501
501
  end
502
+
503
+ # Provide as accurate a representation of what we're *actually* connected
504
+ # to as we can, given the constraints of whether we're connected.
505
+ #
506
+ # To prevent unpleasantness when the other end disconnects but we still
507
+ # want to know who we *were* connected to, we cache the result of our
508
+ # cogitations. Just in case.
509
+ #
510
+ # @return [String]
511
+ #
512
+ def describe_peer
513
+ @describe_peer ||= begin
514
+ if @socket
515
+ pa = @socket.peeraddr
516
+ if pa[0] == "AF_INET6"
517
+ "[#{pa[3]}]:#{pa[1]}"
518
+ else
519
+ "#{pa[3]}:#{pa[1]}"
520
+ end
521
+ else
522
+ nil
523
+ end
524
+ rescue Errno::ENOTCONN
525
+ # Peer disconnected apparently means "I forgot who I was connected
526
+ # to"... ¯\_(ツ)_/¯
527
+ nil
528
+ end || to_s
529
+ end
502
530
  end
503
531
 
504
532
  private_constant :Target
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash_writer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Palmer