logstash_writer 0.0.4 → 0.0.5

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.
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