logtail-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +33 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +10 -0
- data/LICENSE.md +15 -0
- data/README.md +4 -0
- data/Rakefile +72 -0
- data/lib/logtail.rb +36 -0
- data/lib/logtail/config.rb +154 -0
- data/lib/logtail/config/integrations.rb +17 -0
- data/lib/logtail/context.rb +9 -0
- data/lib/logtail/contexts.rb +12 -0
- data/lib/logtail/contexts/http.rb +31 -0
- data/lib/logtail/contexts/release.rb +52 -0
- data/lib/logtail/contexts/runtime.rb +23 -0
- data/lib/logtail/contexts/session.rb +24 -0
- data/lib/logtail/contexts/system.rb +29 -0
- data/lib/logtail/contexts/user.rb +28 -0
- data/lib/logtail/current_context.rb +168 -0
- data/lib/logtail/event.rb +36 -0
- data/lib/logtail/events.rb +10 -0
- data/lib/logtail/events/controller_call.rb +44 -0
- data/lib/logtail/events/error.rb +40 -0
- data/lib/logtail/events/exception.rb +10 -0
- data/lib/logtail/events/sql_query.rb +26 -0
- data/lib/logtail/events/template_render.rb +25 -0
- data/lib/logtail/integration.rb +40 -0
- data/lib/logtail/integrator.rb +50 -0
- data/lib/logtail/log_devices.rb +8 -0
- data/lib/logtail/log_devices/http.rb +368 -0
- data/lib/logtail/log_devices/http/flushable_dropping_sized_queue.rb +52 -0
- data/lib/logtail/log_devices/http/request_attempt.rb +20 -0
- data/lib/logtail/log_entry.rb +110 -0
- data/lib/logtail/logger.rb +270 -0
- data/lib/logtail/logtail.rb +36 -0
- data/lib/logtail/timer.rb +21 -0
- data/lib/logtail/util.rb +7 -0
- data/lib/logtail/util/non_nil_hash_builder.rb +40 -0
- data/lib/logtail/version.rb +3 -0
- data/logtail-ruby.gemspec +43 -0
- data/spec/README.md +13 -0
- data/spec/logtail/current_context_spec.rb +113 -0
- data/spec/logtail/events/controller_call_spec.rb +12 -0
- data/spec/logtail/events/error_spec.rb +15 -0
- data/spec/logtail/log_devices/http_spec.rb +185 -0
- data/spec/logtail/log_entry_spec.rb +22 -0
- data/spec/logtail/logger_spec.rb +227 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/logtail.rb +5 -0
- data/spec/support/socket_hostname.rb +12 -0
- data/spec/support/timecop.rb +3 -0
- data/spec/support/webmock.rb +3 -0
- metadata +238 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
# Base (must come first, order matters)
|
2
|
+
require "logtail/version"
|
3
|
+
require "logtail/config"
|
4
|
+
require "logtail/util"
|
5
|
+
|
6
|
+
# Load frameworks
|
7
|
+
|
8
|
+
# Other (sorted alphabetically)
|
9
|
+
require "logtail/contexts"
|
10
|
+
require "logtail/current_context"
|
11
|
+
require "logtail/events"
|
12
|
+
require "logtail/integration"
|
13
|
+
require "logtail/log_devices"
|
14
|
+
require "logtail/log_entry"
|
15
|
+
require "logtail/logger"
|
16
|
+
require "logtail/timer"
|
17
|
+
require "logtail/integrator"
|
18
|
+
require "logtail/integration"
|
19
|
+
|
20
|
+
module Logtail
|
21
|
+
# Access the main configuration object. Please see {{Logtail::Config}} for more details.
|
22
|
+
def self.config
|
23
|
+
Config.instance
|
24
|
+
end
|
25
|
+
|
26
|
+
# Starts a timer for timing events. Please see {{Logtail::Logtail.start}} for more details.
|
27
|
+
def self.start_timer
|
28
|
+
Timer.start
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds context to all logs written within the passed block. Please see
|
32
|
+
# {{Logtail::CurrentContext.with}} for a more detailed description with examples.
|
33
|
+
def self.with_context(context, &block)
|
34
|
+
CurrentContext.with(context, &block)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Logtail
|
2
|
+
# This is an ultra-simple abstraction for timing code. This provides a little
|
3
|
+
# more control around how Logtail automatically processes "timers".
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# timer = Logtail::Timer.start
|
7
|
+
# # ... code to time
|
8
|
+
# logger.info("My log message", my_event: {time_ms: timer})
|
9
|
+
module Timer
|
10
|
+
# Abstract for starting a logtail. Currently this is simply calling `Time.now`.
|
11
|
+
def self.start
|
12
|
+
Time.now
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get the duration in milliseconds from the object returned in {#start}
|
16
|
+
def self.duration_ms(timer)
|
17
|
+
now = Time.now
|
18
|
+
(now - timer) * 1000.0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/logtail/util.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Logtail
|
4
|
+
module Util
|
5
|
+
# @private
|
6
|
+
#
|
7
|
+
# The purpose of this class is to efficiently build a hash that does not
|
8
|
+
# include nil values. It's proactive instead of reactive, avoiding the
|
9
|
+
# need to traverse and reduce a new hash dropping blanks.
|
10
|
+
class NonNilHashBuilder
|
11
|
+
class << self
|
12
|
+
def build(&block)
|
13
|
+
builder = new
|
14
|
+
yield builder
|
15
|
+
builder.target
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :target
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@target = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(k, v, options = {})
|
26
|
+
if !v.nil?
|
27
|
+
if options[:json_encode]
|
28
|
+
v = v.to_json
|
29
|
+
end
|
30
|
+
|
31
|
+
if options[:limit]
|
32
|
+
v = v.byteslice(0, options[:limit])
|
33
|
+
end
|
34
|
+
|
35
|
+
@target[k] = v
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "logtail/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "logtail-ruby"
|
7
|
+
spec.version = Logtail::VERSION
|
8
|
+
spec.platform = Gem::Platform::RUBY
|
9
|
+
spec.authors = ["Logtail"]
|
10
|
+
spec.email = ["hi@logtail.com"]
|
11
|
+
spec.homepage = "https://github.com/logtail/logtail-ruby"
|
12
|
+
spec.license = "ISC"
|
13
|
+
|
14
|
+
spec.summary = "Query logs like you query your database. https://logtail.com"
|
15
|
+
|
16
|
+
spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
|
17
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/tree/master/CHANGELOG.md"
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
|
21
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.2.0")
|
22
|
+
|
23
|
+
spec.files = `git ls-files`.split("\n")
|
24
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency('msgpack', '~> 1.0')
|
29
|
+
|
30
|
+
spec.add_development_dependency('bundler-audit', '>= 0')
|
31
|
+
spec.add_development_dependency('rails_stdout_logging', '>= 0')
|
32
|
+
spec.add_development_dependency('rake', '>= 0')
|
33
|
+
spec.add_development_dependency('rspec', '~> 3.4')
|
34
|
+
spec.add_development_dependency('rspec-its', '>= 0')
|
35
|
+
spec.add_development_dependency('timecop', '>= 0')
|
36
|
+
spec.add_development_dependency('webmock', '~> 2.3')
|
37
|
+
|
38
|
+
if RUBY_PLATFORM == "java"
|
39
|
+
spec.add_development_dependency('activerecord-jdbcsqlite3-adapter', '>= 0')
|
40
|
+
else
|
41
|
+
spec.add_development_dependency('sqlite3', '>= 0')
|
42
|
+
end
|
43
|
+
end
|
data/spec/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Logtail::CurrentContext do
|
4
|
+
describe ".initialize" do
|
5
|
+
it "should not set the release context" do
|
6
|
+
context = described_class.send(:new)
|
7
|
+
expect(context.send(:hash)[:release]).to be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should set the system context" do
|
11
|
+
context = described_class.send(:new)
|
12
|
+
system_content = context.fetch(:system)
|
13
|
+
expect(system_content[:hostname]).to_not be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set the runtime context" do
|
17
|
+
context = described_class.send(:new)
|
18
|
+
runtime_context = context.fetch(:runtime)
|
19
|
+
expect(runtime_context[:thread_id]).to_not be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
context "with Heroku dyno metadata" do
|
23
|
+
around(:each) do |example|
|
24
|
+
ENV['HEROKU_SLUG_COMMIT'] = "2c3a0b24069af49b3de35b8e8c26765c1dba9ff0"
|
25
|
+
ENV['HEROKU_RELEASE_CREATED_AT'] = "2015-04-02T18:00:42Z"
|
26
|
+
ENV['HEROKU_RELEASE_VERSION'] = "v2.3.1"
|
27
|
+
|
28
|
+
example.run
|
29
|
+
|
30
|
+
ENV.delete('HEROKU_SLUG_COMMIT')
|
31
|
+
ENV.delete('HEROKU_RELEASE_CREATED_AT')
|
32
|
+
ENV.delete('HEROKU_RELEASE_VERSION')
|
33
|
+
|
34
|
+
described_class.reset
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should automatically set the release context" do
|
38
|
+
context = described_class.send(:new)
|
39
|
+
expect(context.send(:hash)[:release]).to eq({:commit_hash=>"2c3a0b24069af49b3de35b8e8c26765c1dba9ff0", :created_at=>"2015-04-02T18:00:42Z", :version=>"v2.3.1"})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with generic env vars" do
|
44
|
+
around(:each) do |example|
|
45
|
+
ENV['RELEASE_COMMIT'] = "2c3a0b24069af49b3de35b8e8c26765c1dba9ff0"
|
46
|
+
ENV['RELEASE_CREATED_AT'] = "2015-04-02T18:00:42Z"
|
47
|
+
ENV['RELEASE_VERSION'] = "v2.3.1"
|
48
|
+
|
49
|
+
example.run
|
50
|
+
|
51
|
+
ENV.delete('RELEASE_COMMIT')
|
52
|
+
ENV.delete('RELEASE_CREATED_AT')
|
53
|
+
ENV.delete('RELEASE_VERSION')
|
54
|
+
|
55
|
+
described_class.reset
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should automatically set the release context" do
|
59
|
+
context = described_class.send(:new)
|
60
|
+
expect(context.send(:hash)[:release]).to eq({:commit_hash=>"2c3a0b24069af49b3de35b8e8c26765c1dba9ff0", :created_at=>"2015-04-02T18:00:42Z", :version=>"v2.3.1"})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ".add" do
|
66
|
+
after(:each) do
|
67
|
+
described_class.reset
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should add the context" do
|
71
|
+
expect(described_class.instance.send(:hash)[:build]).to be_nil
|
72
|
+
|
73
|
+
described_class.add({build: {version: "1.0.0"}})
|
74
|
+
expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
|
75
|
+
|
76
|
+
described_class.add({testing: {key: "value"}})
|
77
|
+
expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
|
78
|
+
expect(described_class.instance.send(:hash)[:testing]).to eq({:key=>"value"})
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe ".remove" do
|
83
|
+
it "should remove context by key" do
|
84
|
+
context = {:build=>{:version=>"1.0.0"}}
|
85
|
+
described_class.add(context)
|
86
|
+
expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
|
87
|
+
|
88
|
+
described_class.remove(:build)
|
89
|
+
expect(described_class.instance.send(:hash)[:build]).to be_nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe ".with" do
|
94
|
+
it "should merge the context and cleanup on block exit" do
|
95
|
+
expect(described_class.instance.send(:hash)[:build]).to be_nil
|
96
|
+
|
97
|
+
described_class.with({build: {version: "1.0.0"}}) do
|
98
|
+
expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
|
99
|
+
|
100
|
+
described_class.with({testing: {key: "value"}}) do
|
101
|
+
expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
|
102
|
+
expect(described_class.instance.send(:hash)[:testing]).to eq({:key=>"value"})
|
103
|
+
end
|
104
|
+
|
105
|
+
expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
|
106
|
+
expect(described_class.instance.send(:hash)[:testing]).to be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
expect(described_class.instance.send(:hash)[:build]).to be_nil
|
110
|
+
expect(described_class.instance.send(:hash)[:testing]).to be_nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Logtail::Events::ControllerCall do
|
6
|
+
describe ".initialize" do
|
7
|
+
it "sanitizes the password param" do
|
8
|
+
# event = described_class.new(controller: 'controller', action: 'action', params: {password: 'password'})
|
9
|
+
# expect(event.params).to eq({'password' => '[sanitized]'})
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Logtail::Events::Error do
|
4
|
+
describe "#to_hash" do
|
5
|
+
it "should jsonify the stacktrace" do
|
6
|
+
backtrace = [
|
7
|
+
"/path/to/file1.rb:26:in `function1'",
|
8
|
+
"path/to/file2.rb:86:in `function2'"
|
9
|
+
]
|
10
|
+
|
11
|
+
exception_event = described_class.new(name: "RuntimeError", error_message: "Boom", backtrace: backtrace)
|
12
|
+
expect(exception_event.backtrace_json).to eq("[\"/path/to/file1.rb:26:in `function1'\",\"path/to/file2.rb:86:in `function2'\"]")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
# Note: these tests access instance variables and private methods as a means of
|
4
|
+
# not muddying the public API. This object should expose a simple buffer like
|
5
|
+
# API, tests should not alter that.
|
6
|
+
describe Logtail::LogDevices::HTTP do
|
7
|
+
describe "#initialize" do
|
8
|
+
it "should initialize properly" do
|
9
|
+
http = described_class.new("MYKEY", flush_interval: 0.1)
|
10
|
+
|
11
|
+
# Ensure that threads have not started
|
12
|
+
thread = http.instance_variable_get(:@flush_thread)
|
13
|
+
expect(thread).to be_nil
|
14
|
+
thread = http.instance_variable_get(:@request_outlet_thread)
|
15
|
+
expect(thread).to be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#write" do
|
20
|
+
let(:http) { described_class.new("MYKEY") }
|
21
|
+
let(:msg_queue) { http.instance_variable_get(:@msg_queue) }
|
22
|
+
|
23
|
+
it "should buffer the messages" do
|
24
|
+
http.write("test log message")
|
25
|
+
expect(msg_queue.flush).to eq(["test log message"])
|
26
|
+
http.close
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should start the flush threads" do
|
30
|
+
http.write("test log message")
|
31
|
+
|
32
|
+
thread = http.instance_variable_get(:@flush_thread)
|
33
|
+
expect(thread).to be_alive
|
34
|
+
thread = http.instance_variable_get(:@request_outlet_thread)
|
35
|
+
expect(thread).to be_alive
|
36
|
+
expect(http).to receive(:flush).exactly(1).times
|
37
|
+
http.close
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with a low batch size" do
|
41
|
+
let(:http) { described_class.new("MYKEY", :batch_size => 2) }
|
42
|
+
|
43
|
+
it "should attempt a delivery when the limit is exceeded" do
|
44
|
+
http.write("test")
|
45
|
+
expect(http).to receive(:flush_async).exactly(1).times
|
46
|
+
http.write("my log message")
|
47
|
+
expect(http).to receive(:flush).exactly(1).times
|
48
|
+
http.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#close" do
|
54
|
+
let(:http) { described_class.new("MYKEY") }
|
55
|
+
|
56
|
+
it "should kill the threads" do
|
57
|
+
http.send(:ensure_flush_threads_are_started)
|
58
|
+
http.close
|
59
|
+
thread = http.instance_variable_get(:@flush_thread)
|
60
|
+
sleep 0.1 # too fast!
|
61
|
+
expect(thread).to_not be_alive
|
62
|
+
thread = http.instance_variable_get(:@request_outlet_thread)
|
63
|
+
sleep 0.1 # too fast!
|
64
|
+
expect(thread).to_not be_alive
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should attempt a delivery" do
|
68
|
+
message = "a" * 19
|
69
|
+
http.write(message)
|
70
|
+
expect(http).to receive(:flush).exactly(1).times
|
71
|
+
http.close
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Testing a private method because it helps break down our tests
|
76
|
+
describe "#flush" do
|
77
|
+
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
78
|
+
|
79
|
+
it "should deliver the request" do
|
80
|
+
http = described_class.new("MYKEY", flush_continuously: false)
|
81
|
+
log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
|
82
|
+
http.write(log_entry)
|
83
|
+
log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
|
84
|
+
http.write(log_entry)
|
85
|
+
expect(http).to receive(:flush_async).exactly(2).times
|
86
|
+
http.send(:flush)
|
87
|
+
http.close
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Testing a private method because it helps break down our tests
|
92
|
+
describe "#flush_async" do
|
93
|
+
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
94
|
+
|
95
|
+
it "should add a request to the queue" do
|
96
|
+
http = described_class.new("MYKEY", flush_continuously: false)
|
97
|
+
log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
|
98
|
+
http.write(log_entry)
|
99
|
+
log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
|
100
|
+
http.write(log_entry)
|
101
|
+
http.send(:flush_async)
|
102
|
+
request_queue = http.instance_variable_get(:@request_queue)
|
103
|
+
request_attempt = request_queue.deq
|
104
|
+
expect(request_attempt.request).to be_kind_of(Net::HTTP::Post)
|
105
|
+
expect(request_attempt.request.body).to start_with("\x92\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 1".force_encoding("ASCII-8BIT"))
|
106
|
+
|
107
|
+
message_queue = http.instance_variable_get(:@msg_queue)
|
108
|
+
expect(message_queue.size).to eq(0)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Testing a private method because it helps break down our tests
|
113
|
+
describe "#intervaled_flush" do
|
114
|
+
it "should start a intervaled flush thread and flush on an interval" do
|
115
|
+
http = described_class.new("MYKEY", flush_interval: 0.1)
|
116
|
+
http.send(:ensure_flush_threads_are_started)
|
117
|
+
expect(http).to receive(:flush_async).at_least(3).times
|
118
|
+
sleep 1.1 # iterations check every 0.5 seconds
|
119
|
+
http.close
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Outlet
|
124
|
+
describe "#request_outlet" do
|
125
|
+
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
126
|
+
|
127
|
+
it "should deliver requests on an interval" do
|
128
|
+
stub = stub_request(:post, "https://logs.logtail.com/").
|
129
|
+
with(
|
130
|
+
:body => start_with("\x92\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 1".force_encoding("ASCII-8BIT")),
|
131
|
+
:headers => {
|
132
|
+
'Authorization' => 'Bearer MYKEY',
|
133
|
+
'Content-Type' => 'application/msgpack',
|
134
|
+
'User-Agent' => "Logtail Ruby/#{Logtail::VERSION} (HTTP)"
|
135
|
+
}
|
136
|
+
).
|
137
|
+
to_return(:status => 200, :body => "", :headers => {})
|
138
|
+
|
139
|
+
http = described_class.new("MYKEY", flush_interval: 0.1)
|
140
|
+
log_entry1 = Logtail::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
|
141
|
+
http.write(log_entry1)
|
142
|
+
log_entry2 = Logtail::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
|
143
|
+
http.write(log_entry2)
|
144
|
+
sleep 2
|
145
|
+
|
146
|
+
expect(stub).to have_been_requested.times(1)
|
147
|
+
|
148
|
+
http.close
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "#deliver_requests" do
|
153
|
+
it "should handle exceptions properly and return" do
|
154
|
+
allow_any_instance_of(Net::HTTP).to receive(:request).and_raise("boom")
|
155
|
+
|
156
|
+
http_device = described_class.new("MYKEY", flush_continuously: false)
|
157
|
+
req_queue = http_device.instance_variable_get(:@request_queue)
|
158
|
+
|
159
|
+
# Place a request on the queue
|
160
|
+
request = Net::HTTP::Post.new("/")
|
161
|
+
request_attempt = Logtail::LogDevices::HTTP::RequestAttempt.new(request)
|
162
|
+
request_attempt.attempted!
|
163
|
+
req_queue.enq(request_attempt)
|
164
|
+
|
165
|
+
# Start a HTTP connection to test the method directly
|
166
|
+
http = http_device.send(:build_http)
|
167
|
+
http.start do |conn|
|
168
|
+
result = http_device.send(:deliver_requests, conn)
|
169
|
+
expect(result).to eq(false)
|
170
|
+
end
|
171
|
+
|
172
|
+
expect(req_queue.size).to eq(1)
|
173
|
+
|
174
|
+
# Start a HTTP connection to test the method directly
|
175
|
+
http = http_device.send(:build_http)
|
176
|
+
http.start do |conn|
|
177
|
+
result = http_device.send(:deliver_requests, conn)
|
178
|
+
expect(result).to eq(false)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Ensure the request gets discards after 3 attempts
|
182
|
+
expect(req_queue.size).to eq(0)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|