logtail 0.1.19 → 0.1.20

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd895e734e1d553b0dfdf1011f0a46b6f043a50c391d27822ee015a517759650
4
- data.tar.gz: 964ab22275d822db5df10f3a4616e25d500b0d9292057ea26f4467be40786e63
3
+ metadata.gz: 0d8397873f23b58750258e46a3162866eaa3ab6ffaf551078d28b25b44b5ad69
4
+ data.tar.gz: 83a5def59147d9b94a890db6e7f07dd2107bdfa68ba7eb929ca47fa5a44a212b
5
5
  SHA512:
6
- metadata.gz: 6556a1a6135a0c12070fab2080f43800e48fc0c7acfebab062d70ccc99999dadcbcb913145312d3fd61a0a5ad9f3ab411e6017a2cfe5cb55988867f882a748ec
7
- data.tar.gz: 10e8559ff2347820d3e7d0af52c019fbdd84c82507653f232b33e1bfdd9cd35382ba04a109537e6c537b429df857e0383bd2b3930cd60f576d121fb1e1f0f5ab
6
+ metadata.gz: d710225c254e58dbdfa66e6e87515167fcc651ce4cd14d7ddeb6cb682d9b547f8eba13c9729a939249ca565229fcd68e4f9867db17900e798051abe5b0699473
7
+ data.tar.gz: 5d785a4b048a73403e2d389780d5e83702b0ada279fb2dd727e2b192176f15133620668e6f2a6eccdb5b142a29d9283fd03a3058f2d3f31aace47f42794ca820
@@ -32,6 +32,10 @@ module Logtail
32
32
 
33
33
  attr_writer :http_body_limit
34
34
 
35
+ def initialize
36
+ @debug_logger = nil
37
+ end
38
+
35
39
  # Whether a particular {Logtail::LogEntry} should be sent to Better Stack
36
40
  def send_to_better_stack?(log_entry)
37
41
  !@better_stack_filters&.any? { |blocker| blocker.call(log_entry) }
@@ -22,6 +22,8 @@ module Logtail
22
22
  DEFAULT_INGESTING_SCHEME = "https".freeze
23
23
  CONTENT_TYPE = "application/msgpack".freeze
24
24
  USER_AGENT = "Logtail Ruby/#{Logtail::VERSION} (HTTP)".freeze
25
+ INITIAL_RECONNECT_WAIT = 1 # second
26
+ MAX_RECONNECT_WAIT = 30 # seconds
25
27
 
26
28
  # Instantiates a new HTTP log device that can be passed to {Logtail::Logger#initialize}.
27
29
  #
@@ -83,6 +85,7 @@ module Logtail
83
85
  @request_queue = options[:request_queue] || FlushableDroppingSizedQueue.new(25)
84
86
  @successive_error_count = 0
85
87
  @requests_in_flight = 0
88
+ @reconnect_wait = INITIAL_RECONNECT_WAIT
86
89
  end
87
90
 
88
91
  # Write a new log line message to the buffer, and flush asynchronously if the
@@ -298,15 +301,19 @@ Logtail::Config.instance.debug_logger = ::Logger.new(STDOUT)
298
301
  http
299
302
  end
300
303
 
301
- # Creates a loop that processes the `@request_queue` on an interval.
304
+ # Creates a loop that processes the `@request_queue` on an interval. After a failed
305
+ # connection it waits before reconnecting, twice as long after every consecutive
306
+ # failure up to {MAX_RECONNECT_WAIT}, so an unreachable host is not retried in a busy
307
+ # loop. A delivered request starts the wait over (see {#deliver_requests}).
302
308
  def request_outlet
303
309
  loop do
304
310
  http = build_http
311
+ connection_healthy = false
305
312
 
306
313
  begin
307
314
  Logtail::Config.instance.debug { "Starting HTTP connection" }
308
315
 
309
- http.start do |conn|
316
+ connection_healthy = http.start do |conn|
310
317
  deliver_requests(conn)
311
318
  end
312
319
  rescue => e
@@ -315,6 +322,12 @@ Logtail::Config.instance.debug_logger = ::Logger.new(STDOUT)
315
322
  Logtail::Config.instance.debug { "Finishing HTTP connection" }
316
323
  http.finish if http.started?
317
324
  end
325
+
326
+ next if connection_healthy
327
+
328
+ Logtail::Config.instance.debug { "Reconnecting in #{@reconnect_wait} seconds" }
329
+ sleep(@reconnect_wait)
330
+ @reconnect_wait = [@reconnect_wait * 2, MAX_RECONNECT_WAIT].min
318
331
  end
319
332
  end
320
333
 
@@ -360,6 +373,7 @@ Logtail::Config.instance.debug_logger = ::Logger.new(STDOUT)
360
373
  num_reqs += 1
361
374
 
362
375
  @last_resp = resp
376
+ @reconnect_wait = INITIAL_RECONNECT_WAIT
363
377
 
364
378
  Logtail::Config.instance.debug do
365
379
  if resp.code == "202"
@@ -1,3 +1,3 @@
1
1
  module Logtail
2
- VERSION = "0.1.19"
2
+ VERSION = "0.1.20"
3
3
  end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+
3
+ describe Logtail::Config do
4
+ describe "#debug_logger" do
5
+ # Ruby before 3.0 warns under `ruby -w` when an unset instance variable is read, and the
6
+ # HTTP device reads the debug logger on every connection attempt.
7
+ it "can be read before it is set without an uninitialized instance variable warning" do
8
+ config = described_class.send(:new)
9
+ verbose, $VERBOSE = $VERBOSE, true
10
+
11
+ expect { config.debug_logger }.not_to output.to_stderr
12
+ ensure
13
+ $VERBOSE = verbose
14
+ end
15
+ end
16
+ end
@@ -148,6 +148,87 @@ describe Logtail::LogDevices::HTTP do
148
148
 
149
149
  http.close
150
150
  end
151
+
152
+ context "when connecting or delivering fails" do
153
+ # Raised from the stubs below to leave the outlet's endless loop. It is not a
154
+ # StandardError, so the outlet's own `rescue => e` lets it through.
155
+ let(:stop_outlet) { Class.new(Exception) }
156
+ let(:http_device) { described_class.new("MYKEY", flush_continuously: false, requests_per_conn: 1) }
157
+ let(:request_queue) { http_device.instance_variable_get(:@request_queue) }
158
+ let(:waits) { [] }
159
+
160
+ before do
161
+ allow(http_device).to receive(:sleep) { |seconds| waits << seconds }
162
+ end
163
+
164
+ it "waits before reconnecting, twice as long after every refused connection, up to 30 seconds" do
165
+ connection_attempts = 0
166
+ allow_any_instance_of(Net::HTTP).to receive(:start) do
167
+ connection_attempts += 1
168
+ raise stop_outlet if connection_attempts > 7
169
+ raise Errno::ECONNREFUSED
170
+ end
171
+
172
+ expect { http_device.send(:request_outlet) }.to raise_error(stop_outlet)
173
+ expect(waits).to eq([1, 2, 4, 8, 16, 30, 30])
174
+ end
175
+
176
+ it "waits the same way when a request fails on an open connection, and still drops it after 3 attempts" do
177
+ request_queue.enq(Logtail::LogDevices::HTTP::RequestAttempt.new(Net::HTTP::Post.new("/")))
178
+ request_attempts = 0
179
+ allow_any_instance_of(Net::HTTP).to receive(:request) do
180
+ request_attempts += 1
181
+ raise Errno::ECONNRESET
182
+ end
183
+ allow(http_device).to receive(:sleep) do |seconds|
184
+ waits << seconds
185
+ raise stop_outlet if waits.size == 3
186
+ end
187
+
188
+ expect { http_device.send(:request_outlet) }.to raise_error(stop_outlet)
189
+ expect(waits).to eq([1, 2, 4])
190
+ expect(request_attempts).to eq(3)
191
+ expect(request_queue.size).to eq(0)
192
+ end
193
+
194
+ it "starts over at 1 second once a request is delivered" do
195
+ request_queue.enq(Logtail::LogDevices::HTTP::RequestAttempt.new(Net::HTTP::Post.new("/")))
196
+ connection = double("connection", request: double("response", code: "202"))
197
+ connections = [:refused, :refused, :delivers, :refused, :refused]
198
+ allow_any_instance_of(Net::HTTP).to receive(:start) do |_http, &block|
199
+ case connections.shift
200
+ when :refused then raise Errno::ECONNREFUSED
201
+ when :delivers then block.call(connection)
202
+ else raise stop_outlet
203
+ end
204
+ end
205
+
206
+ expect { http_device.send(:request_outlet) }.to raise_error(stop_outlet)
207
+ expect(waits).to eq([1, 2, 1, 2])
208
+ end
209
+ end
210
+
211
+ it "lets close stop the outlet while it waits to reconnect" do
212
+ connection_attempts = 0
213
+ allow_any_instance_of(Net::HTTP).to receive(:start) do
214
+ connection_attempts += 1
215
+ raise Errno::ECONNREFUSED
216
+ end
217
+ http_device = described_class.new("MYKEY")
218
+ http_device.send(:ensure_flush_threads_are_started)
219
+ outlet = http_device.instance_variable_get(:@request_outlet_thread)
220
+ # Up to 5 seconds for the thread's first attempt, which is slow on a cold TruffleRuby.
221
+ 500.times do
222
+ break if connection_attempts > 0 && outlet.status == "sleep"
223
+ sleep 0.01
224
+ end
225
+
226
+ closing = Process.clock_gettime(Process::CLOCK_MONOTONIC)
227
+ http_device.close
228
+ expect(Process.clock_gettime(Process::CLOCK_MONOTONIC) - closing).to be < 0.5
229
+ expect(outlet).not_to be_alive
230
+ expect(connection_attempts).to eq(1)
231
+ end
151
232
  end
152
233
 
153
234
  describe "#deliver_requests" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logtail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.19
4
+ version: 0.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Better Stack
@@ -188,6 +188,7 @@ files:
188
188
  - lib/logtail/version.rb
189
189
  - logtail.gemspec
190
190
  - spec/README.md
191
+ - spec/logtail/config_spec.rb
191
192
  - spec/logtail/current_context_spec.rb
192
193
  - spec/logtail/events/controller_call_spec.rb
193
194
  - spec/logtail/events/error_spec.rb