timberio 1.0.0.beta1
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 +7 -0
- data/.codeclimate.yml +34 -0
- data/.gitignore +14 -0
- data/Appraisals +37 -0
- data/Gemfile +22 -0
- data/LICENSE +38 -0
- data/README.md +22 -0
- data/Rakefile +4 -0
- data/TODO +4 -0
- data/benchmark/README.md +26 -0
- data/benchmark/rails_request.rb +68 -0
- data/benchmark/support/rails.rb +69 -0
- data/circle.yml +27 -0
- data/docs/installation/rails_on_heroku.md +31 -0
- data/docs/installation/rails_over_http.md +22 -0
- data/gemfiles/rails_3.0.X.gemfile +25 -0
- data/gemfiles/rails_3.1.X.gemfile +25 -0
- data/gemfiles/rails_3.2.X.gemfile +25 -0
- data/gemfiles/rails_4.0.X.gemfile +26 -0
- data/gemfiles/rails_4.1.X.gemfile +26 -0
- data/gemfiles/rails_4.2.X.gemfile +26 -0
- data/gemfiles/rails_5.0.X.gemfile +26 -0
- data/gemfiles/rails_edge.gemfile +27 -0
- data/lib/timber/api_settings.rb +17 -0
- data/lib/timber/bootstrap.rb +45 -0
- data/lib/timber/config.rb +25 -0
- data/lib/timber/context.rb +76 -0
- data/lib/timber/context_snapshot.rb +64 -0
- data/lib/timber/contexts/dynamic_values.rb +59 -0
- data/lib/timber/contexts/exception.rb +40 -0
- data/lib/timber/contexts/http_request.rb +22 -0
- data/lib/timber/contexts/http_requests/action_controller_specific.rb +48 -0
- data/lib/timber/contexts/http_requests/rack/params.rb +26 -0
- data/lib/timber/contexts/http_requests/rack.rb +105 -0
- data/lib/timber/contexts/http_response.rb +19 -0
- data/lib/timber/contexts/http_responses/action_controller.rb +76 -0
- data/lib/timber/contexts/logger.rb +33 -0
- data/lib/timber/contexts/organization.rb +33 -0
- data/lib/timber/contexts/organizations/action_controller.rb +34 -0
- data/lib/timber/contexts/server.rb +21 -0
- data/lib/timber/contexts/servers/heroku_specific.rb +48 -0
- data/lib/timber/contexts/sql_queries/active_record.rb +30 -0
- data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +37 -0
- data/lib/timber/contexts/sql_queries/active_record_specific.rb +59 -0
- data/lib/timber/contexts/sql_query.rb +18 -0
- data/lib/timber/contexts/template_render.rb +17 -0
- data/lib/timber/contexts/template_renders/action_view.rb +29 -0
- data/lib/timber/contexts/template_renders/action_view_specific.rb +51 -0
- data/lib/timber/contexts/user.rb +39 -0
- data/lib/timber/contexts/users/action_controller.rb +34 -0
- data/lib/timber/contexts.rb +23 -0
- data/lib/timber/current_context.rb +58 -0
- data/lib/timber/current_line_indexes.rb +35 -0
- data/lib/timber/frameworks/rails.rb +24 -0
- data/lib/timber/frameworks.rb +21 -0
- data/lib/timber/internal_logger.rb +35 -0
- data/lib/timber/log_device.rb +40 -0
- data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +14 -0
- data/lib/timber/log_devices/heroku_logplex.rb +14 -0
- data/lib/timber/log_devices/http/log_pile.rb +86 -0
- data/lib/timber/log_devices/http/log_truck/delivery.rb +116 -0
- data/lib/timber/log_devices/http/log_truck.rb +87 -0
- data/lib/timber/log_devices/http.rb +28 -0
- data/lib/timber/log_devices/io/formatter.rb +46 -0
- data/lib/timber/log_devices/io/hybrid_formatter.rb +41 -0
- data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +36 -0
- data/lib/timber/log_devices/io/json_formatter.rb +11 -0
- data/lib/timber/log_devices/io/logfmt_formatter.rb +11 -0
- data/lib/timber/log_devices/io.rb +41 -0
- data/lib/timber/log_devices.rb +4 -0
- data/lib/timber/log_line.rb +33 -0
- data/lib/timber/logger.rb +20 -0
- data/lib/timber/macros/compactor.rb +16 -0
- data/lib/timber/macros/date_formatter.rb +9 -0
- data/lib/timber/macros/deep_merger.rb +11 -0
- data/lib/timber/macros/logfmt_encoder.rb +77 -0
- data/lib/timber/macros.rb +4 -0
- data/lib/timber/patterns/delegated_singleton.rb +21 -0
- data/lib/timber/patterns/to_json.rb +22 -0
- data/lib/timber/patterns/to_logfmt.rb +9 -0
- data/lib/timber/patterns.rb +3 -0
- data/lib/timber/probe.rb +21 -0
- data/lib/timber/probes/action_controller_base.rb +31 -0
- data/lib/timber/probes/action_dispatch_debug_exceptions.rb +57 -0
- data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +15 -0
- data/lib/timber/probes/active_support_log_subscriber/action_view.rb +26 -0
- data/lib/timber/probes/active_support_log_subscriber/active_record.rb +13 -0
- data/lib/timber/probes/active_support_log_subscriber.rb +62 -0
- data/lib/timber/probes/heroku.rb +30 -0
- data/lib/timber/probes/logger.rb +31 -0
- data/lib/timber/probes/rack.rb +36 -0
- data/lib/timber/probes/server.rb +18 -0
- data/lib/timber/probes.rb +24 -0
- data/lib/timber/version.rb +3 -0
- data/lib/timber.rb +27 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/action_controller.rb +4 -0
- data/spec/support/action_view.rb +4 -0
- data/spec/support/active_record.rb +28 -0
- data/spec/support/coveralls.rb +2 -0
- data/spec/support/rails/templates/_partial.html +1 -0
- data/spec/support/rails/templates/template.html +1 -0
- data/spec/support/rails.rb +33 -0
- data/spec/support/simplecov.rb +9 -0
- data/spec/support/socket_hostname.rb +12 -0
- data/spec/support/timber.rb +23 -0
- data/spec/support/timecop.rb +3 -0
- data/spec/support/webmock.rb +2 -0
- data/spec/timber/bootstrap_spec.rb +31 -0
- data/spec/timber/context_snapshot_spec.rb +10 -0
- data/spec/timber/context_spec.rb +4 -0
- data/spec/timber/contexts/exception_spec.rb +34 -0
- data/spec/timber/contexts/organizations/action_controller_spec.rb +49 -0
- data/spec/timber/contexts/users/action_controller_spec.rb +65 -0
- data/spec/timber/current_line_indexes_spec.rb +40 -0
- data/spec/timber/frameworks/rails_spec.rb +9 -0
- data/spec/timber/log_devices/heroku_logplex_spec.rb +45 -0
- data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +66 -0
- data/spec/timber/log_devices/http/log_truck_spec.rb +65 -0
- data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +28 -0
- data/spec/timber/log_line_spec.rb +49 -0
- data/spec/timber/macros/compactor_spec.rb +19 -0
- data/spec/timber/macros/logfmt_encoder_spec.rb +89 -0
- data/spec/timber/patterns/to_json_spec.rb +40 -0
- data/spec/timber/probes/action_controller_base_spec.rb +43 -0
- data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +35 -0
- data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +44 -0
- data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +26 -0
- data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +45 -0
- data/spec/timber/probes/logger_spec.rb +20 -0
- data/spec/timber/probes/rack_spec.rb +26 -0
- data/timberio.gemspec +20 -0
- metadata +210 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
|
|
3
|
+
# Stub out the hostname for tests only. This can't use a normal stub in the
|
|
4
|
+
# test life cycle since our test rails app is loaded once upon initialization.
|
|
5
|
+
# In other words, the rails app gets loaded with the server context inserted
|
|
6
|
+
# before any tests are run.
|
|
7
|
+
|
|
8
|
+
class ::Socket
|
|
9
|
+
def self.gethostname
|
|
10
|
+
"computer-name.domain.com"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Must require last in order to be mocked via webmock
|
|
2
|
+
require 'timber'
|
|
3
|
+
|
|
4
|
+
# Config
|
|
5
|
+
Timber::Config.tap do |config|
|
|
6
|
+
config.application_key = "my_key"
|
|
7
|
+
config.logger.level = ::Logger::FATAL
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
RSpec.configure do |config|
|
|
11
|
+
config.after(:each) do
|
|
12
|
+
Timber::CurrentLineIndexes.reset!
|
|
13
|
+
Timber::LogDevices::HTTP::LogPile.each { |log_pile| log_pile.empty }
|
|
14
|
+
|
|
15
|
+
# Reset permanent context caches since we mock, etc.
|
|
16
|
+
Timber::CurrentContext.send(:stack).each do |context|
|
|
17
|
+
context.instance_variable_set(:"@as_json", nil)
|
|
18
|
+
context.instance_variable_set(:"@json_payload", nil)
|
|
19
|
+
context.instance_variable_set(:"@to_json", nil)
|
|
20
|
+
context.instance_variable_set(:"@to_logfmt", nil)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::Bootstrap do
|
|
4
|
+
describe ".bootstrap!" do
|
|
5
|
+
let(:middleware) { Rack::Builder.new }
|
|
6
|
+
let(:insert_before) { ::Rails::Rack::Logger }
|
|
7
|
+
|
|
8
|
+
def self.it_should_not_bootstrap
|
|
9
|
+
it "should not bootstrap" do
|
|
10
|
+
expect(Timber::Probes).to_not receive(:insert!)
|
|
11
|
+
expect(described_class.bootstrap!(middleware, insert_before)).to be false
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.it_should_bootstrap
|
|
16
|
+
it "should bootstrap" do
|
|
17
|
+
expect(Timber::Probes).to receive(:insert!).once
|
|
18
|
+
expect(described_class.bootstrap!(middleware, insert_before)).to be true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it_should_bootstrap
|
|
23
|
+
|
|
24
|
+
context "disabled" do
|
|
25
|
+
before(:each) { Timber::Config.enabled = false }
|
|
26
|
+
after(:each) { Timber::Config.enabled = true }
|
|
27
|
+
|
|
28
|
+
it_should_not_bootstrap
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::Contexts::Exception do
|
|
4
|
+
let(:exception) do
|
|
5
|
+
begin
|
|
6
|
+
raise StandardError.new("this is a message")
|
|
7
|
+
rescue Exception => e
|
|
8
|
+
e
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
let(:context) { described_class.new(exception) }
|
|
12
|
+
|
|
13
|
+
describe ".as_json" do
|
|
14
|
+
subject { context.as_json }
|
|
15
|
+
its([:exception, :backtrace]) { should_not be_nil }
|
|
16
|
+
its([:exception, :name]) { should eq("StandardError") }
|
|
17
|
+
its([:exception, :message]) { should eq("this is a message") }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe ".backtrace" do
|
|
21
|
+
subject { context.backtrace }
|
|
22
|
+
its(:size) { should eq(5) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe ".name" do
|
|
26
|
+
subject { context.name }
|
|
27
|
+
it { should eq("StandardError") }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe ".message" do
|
|
31
|
+
subject { context.message }
|
|
32
|
+
it { should eq("this is a message") }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::Contexts::Organizations::ActionController do
|
|
4
|
+
around(:each) do |example|
|
|
5
|
+
class PagesController < ActionController::Base
|
|
6
|
+
layout nil
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
render json: {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def method_for_action(action_name)
|
|
13
|
+
action_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
def current_organization
|
|
18
|
+
# I want this to execute a query and test logging that query
|
|
19
|
+
Organization.first
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
example.run
|
|
24
|
+
|
|
25
|
+
Object.send(:remove_const, :PagesController)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
let(:context) { described_class.new(PagesController.new) }
|
|
29
|
+
|
|
30
|
+
describe "#name" do
|
|
31
|
+
subject { context.name }
|
|
32
|
+
it { should be_nil }
|
|
33
|
+
|
|
34
|
+
context "with an organization" do
|
|
35
|
+
before(:each) { Organization.create!(name: "Timber") }
|
|
36
|
+
it { should eq("Timber") }
|
|
37
|
+
|
|
38
|
+
context "with an organization context" do
|
|
39
|
+
around(:each) do |example|
|
|
40
|
+
Timber::CurrentContext.add(context) do
|
|
41
|
+
example.run
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it { should eq("Timber") }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::Contexts::Users::ActionController do
|
|
4
|
+
around(:each) do |example|
|
|
5
|
+
class PagesController < ActionController::Base
|
|
6
|
+
layout nil
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
render json: {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def method_for_action(action_name)
|
|
13
|
+
action_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
def current_user
|
|
18
|
+
# I want this to execute a query and test logging that query
|
|
19
|
+
@user ||= User.first
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
example.run
|
|
24
|
+
|
|
25
|
+
Object.send(:remove_const, :PagesController)
|
|
26
|
+
|
|
27
|
+
Timber::Probes::ActiveSupportLogSubscriber.insert!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:context) { described_class.new(PagesController.new) }
|
|
31
|
+
|
|
32
|
+
describe "#email" do
|
|
33
|
+
subject { context.email }
|
|
34
|
+
it { should be_nil }
|
|
35
|
+
|
|
36
|
+
context "with a user" do
|
|
37
|
+
before(:each) { User.create!(email: "a@a.com") }
|
|
38
|
+
it { should eq("a@a.com") }
|
|
39
|
+
|
|
40
|
+
context "with a user context" do
|
|
41
|
+
around(:each) do |example|
|
|
42
|
+
Timber::CurrentContext.add(context) do
|
|
43
|
+
example.run
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it { should eq("a@a.com") }
|
|
48
|
+
|
|
49
|
+
context "with a debug log level" do
|
|
50
|
+
around(:each) do |example|
|
|
51
|
+
old_level = ::Rails.logger.level
|
|
52
|
+
::Rails.logger.level = ::Logger::DEBUG
|
|
53
|
+
example.run
|
|
54
|
+
::Rails.logger.level = old_level
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# If the user object is not cached, it will create an infinite loop.
|
|
58
|
+
# This is because getting the user executes a query, which in turn creates
|
|
59
|
+
# logs, with tries to grab the user again, etc.
|
|
60
|
+
it { should eq("a@a.com") }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::CurrentLineIndexes do
|
|
4
|
+
def add_log_line
|
|
5
|
+
Timber::LogLine.new("test")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe "#log_line_added" do
|
|
9
|
+
it "only includes the valid stack" do
|
|
10
|
+
expect(Timber::CurrentContext).to receive(:valid_stack).twice.and_return([])
|
|
11
|
+
add_log_line
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "with a context" do
|
|
15
|
+
let(:heroku_context) { Timber::Contexts::Servers::HerokuSpecific.new("web.1")}
|
|
16
|
+
|
|
17
|
+
around(:each) do |example|
|
|
18
|
+
Timber::CurrentContext.add(heroku_context) do
|
|
19
|
+
example.run
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context "with a log line" do
|
|
24
|
+
before(:each) { add_log_line }
|
|
25
|
+
|
|
26
|
+
it "sets the context to 0" do
|
|
27
|
+
expect(described_class.indexes[heroku_context]).to eq(0)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context "with an additional log line" do
|
|
31
|
+
before(:each) { add_log_line }
|
|
32
|
+
|
|
33
|
+
it "increments properly" do
|
|
34
|
+
expect(described_class.indexes[heroku_context]).to eq(1)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::LogDevices::HerokuLogplex do
|
|
4
|
+
let(:io) { STDOUT }
|
|
5
|
+
let(:log_device) { described_class.new }
|
|
6
|
+
|
|
7
|
+
describe ".write" do
|
|
8
|
+
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
|
9
|
+
|
|
10
|
+
before(:each) do
|
|
11
|
+
server_context = Timber::CurrentContext.get(Timber::Contexts::Server)
|
|
12
|
+
allow(server_context).to receive(:_dt).and_return(time)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "writes a proper logfmt line" do
|
|
16
|
+
expect(io).to receive(:write).with("\e7\e[1;30m@timber.io \e8\e[Kserver.hostname=comp\e8\e[Kuter-name.domain.com\e8\e[K server._dt=2016-09-\e8\e[K01T12:00:00.000000Z \e8\e[Kserver._version=1 se\e8\e[Krver._index=0 _versi\e8\e[Kon=1 _hierarchy=[ser\e8\e[Kver] @original \e8\e[K\e[0mthis is a message\n")
|
|
17
|
+
# Notice we do not have dt for the log line since Heroku provides this
|
|
18
|
+
log_device.write("this is a message\n")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
context "with a heroku context" do
|
|
22
|
+
around(:each) do |example|
|
|
23
|
+
heroku = Timber::Contexts::Servers::HerokuSpecific.new("web.1")
|
|
24
|
+
Timber::CurrentContext.add(heroku) { example.run }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# No need for the heroku context since logplex includes that data by default
|
|
28
|
+
it "does not include the heroku context" do
|
|
29
|
+
expect(io).to receive(:write).with("\e7\e[1;30m@timber.io \e8\e[Kserver.hostname=comp\e8\e[Kuter-name.domain.com\e8\e[K server._dt=2016-09-\e8\e[K01T12:00:00.000000Z \e8\e[Kserver._version=1 se\e8\e[Krver._index=0 _versi\e8\e[Kon=1 _hierarchy=[ser\e8\e[Kver,server.heroku] @original \e8\e[K\e[0mthis is a message\n")
|
|
30
|
+
# Notice we do not have dt for the log line since Heroku provides this
|
|
31
|
+
log_device.write("this is a message\n")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
context "with multiple lines" do |variable|
|
|
36
|
+
it "does not include the heroku context" do
|
|
37
|
+
expect(io).to receive(:write).with("\e7\e[1;30m@timber.io \e8\e[Kserver.hostname=comp\e8\e[Kuter-name.domain.com\e8\e[K server._dt=2016-09-\e8\e[K01T12:00:00.000000Z \e8\e[Kserver._version=1 se\e8\e[Krver._index=0 _versi\e8\e[Kon=1 _hierarchy=[ser\e8\e[Kver] @original \e8\e[K\e[0mline 1\n")
|
|
38
|
+
expect(io).to receive(:write).with("\e7\e[1;30m@timber.io \e8\e[Kserver.hostname=comp\e8\e[Kuter-name.domain.com\e8\e[K server._dt=2016-09-\e8\e[K01T12:00:00.000000Z \e8\e[Kserver._version=1 se\e8\e[Krver._index=1 _versi\e8\e[Kon=1 _hierarchy=[ser\e8\e[Kver] @original \e8\e[K\e[0mline 2\n")
|
|
39
|
+
expect(io).to receive(:write).with("\e7\e[1;30m@timber.io \e8\e[Kserver.hostname=comp\e8\e[Kuter-name.domain.com\e8\e[K server._dt=2016-09-\e8\e[K01T12:00:00.000000Z \e8\e[Kserver._version=1 se\e8\e[Krver._index=2 _versi\e8\e[Kon=1 _hierarchy=[ser\e8\e[Kver] @original \e8\e[K\e[0mline 3\n")
|
|
40
|
+
# Notice we do not have dt for the log line since Heroku provides this
|
|
41
|
+
log_device.write("line 1\nline 2\nline 3\n")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::LogDevices::HTTP::LogTruck::Delivery do
|
|
4
|
+
describe "#deliver!" do
|
|
5
|
+
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
|
6
|
+
|
|
7
|
+
before(:each) { ActiveSupport.send(:silence_warnings) { described_class::RETRY_COUNT = 0 } }
|
|
8
|
+
after(:each) { ActiveSupport.send(:silence_warnings) { described_class::RETRY_COUNT = 3 } }
|
|
9
|
+
|
|
10
|
+
def new_stub
|
|
11
|
+
stub_request(:post, "https://timber-odin.herokuapp.com/agent_log_frames").
|
|
12
|
+
with(:body => "{\"agent_log_frame\": {\"log_lines\": [{\"dt\":\"2016-09-01T12:00:00.000000Z\",\"message\":\"hello\",\"context\":{\"server\":{\"hostname\":\"computer-name.domain.com\",\"_dt\":\"2016-09-01T12:00:00.000000Z\",\"_version\":1,\"_index\":0},\"_version\":1,\"_hierarchy\":[\"server\"]}}]}}",
|
|
13
|
+
:headers => {'Content-Type'=>'application/json'})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
around(:each) do |example|
|
|
17
|
+
Timecop.freeze(time) { example.run }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
before(:each) do
|
|
21
|
+
server_context = Timber::CurrentContext.get(Timber::Contexts::Server)
|
|
22
|
+
allow(server_context).to receive(:_dt).and_return(time)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
let(:log_lines) { [Timber::LogLine.new("hello")] }
|
|
26
|
+
let(:delivery) { described_class.new(Timber::Config.application_key, log_lines) }
|
|
27
|
+
let(:stub) { new_stub }
|
|
28
|
+
|
|
29
|
+
before(:each) { stub }
|
|
30
|
+
|
|
31
|
+
it "should delivery successfully" do
|
|
32
|
+
delivery.deliver!
|
|
33
|
+
expect(stub).to have_been_requested
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context "timeout error" do
|
|
37
|
+
let(:stub) {
|
|
38
|
+
new_stub.to_timeout
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
it "should raise an error" do
|
|
42
|
+
expect { delivery.deliver! }.to raise_error(Timber::LogDevices::HTTP::LogTruck::Delivery::DeliveryError)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context "random error" do
|
|
47
|
+
let(:stub) {
|
|
48
|
+
new_stub.to_raise(StandardError.new("some error"))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
it "should raise an error" do
|
|
52
|
+
expect { delivery.deliver! }.to raise_error(Timber::LogDevices::HTTP::LogTruck::Delivery::DeliveryError)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "internal server error" do
|
|
57
|
+
let(:stub) {
|
|
58
|
+
new_stub.to_return(status: [500, "Internal Server Error"])
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
it "should raise an error" do
|
|
62
|
+
expect { delivery.deliver! }.to raise_error(Timber::LogDevices::HTTP::LogTruck::Delivery::DeliveryError)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::LogDevices::HTTP::LogTruck do
|
|
4
|
+
describe ".start!" do
|
|
5
|
+
it "spawns a new thread" do
|
|
6
|
+
expect(Thread).to receive(:new).once
|
|
7
|
+
described_class.start!
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "delivers" do
|
|
11
|
+
expect(described_class).to receive(:deliver).once
|
|
12
|
+
described_class.start! do |thread|
|
|
13
|
+
thread.kill
|
|
14
|
+
end.join
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe ".deliver" do
|
|
19
|
+
let(:log_pile) { Timber::LogDevices::HTTP::LogPile.get(Timber::Config.application_key) }
|
|
20
|
+
|
|
21
|
+
it "doesn't deliver because there is nothing to deliver" do
|
|
22
|
+
expect(log_pile).to_not receive(:deliver)
|
|
23
|
+
described_class.deliver
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "with a log pile" do
|
|
27
|
+
before(:each) do
|
|
28
|
+
log_line = Timber::LogLine.new("this is a log line")
|
|
29
|
+
log_pile.drop(log_line)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "delivers once and empties the log pile" do
|
|
33
|
+
expect(log_pile.size).to eq(1)
|
|
34
|
+
expect_any_instance_of(described_class).to receive(:deliver!).once
|
|
35
|
+
described_class.deliver
|
|
36
|
+
expect(log_pile.size).to eq(0)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "#initialize" do
|
|
42
|
+
let(:log_lines) { [] }
|
|
43
|
+
let(:log_truck) { described_class.new(Timber::Config.application_key, log_lines) }
|
|
44
|
+
subject { log_truck }
|
|
45
|
+
|
|
46
|
+
it "should raise an exception" do
|
|
47
|
+
expect { subject }.to raise_exception(Timber::LogDevices::HTTP::LogTruck::NoPayloadError)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context "with log lines" do
|
|
51
|
+
let(:log_lines) { [Timber::LogLine.new("hello")] }
|
|
52
|
+
its(:log_lines) { should eq(log_lines) }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "#deliver!" do
|
|
57
|
+
let(:log_lines) { [Timber::LogLine.new("hello")] }
|
|
58
|
+
let(:log_truck) { described_class.new(Timber::Config.application_key, log_lines) }
|
|
59
|
+
|
|
60
|
+
it "should delivery successfully" do
|
|
61
|
+
expect_any_instance_of(Timber::LogDevices::HTTP::LogTruck::Delivery).to receive(:deliver!)
|
|
62
|
+
log_truck.deliver!
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::LogDevices::IO::HybridHiddenFormatter do
|
|
4
|
+
describe ".format" do
|
|
5
|
+
let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
|
|
6
|
+
let(:formatter) { described_class.new }
|
|
7
|
+
let(:message) { "a message" }
|
|
8
|
+
let(:log_line) { Timber::LogLine.new(message) }
|
|
9
|
+
subject { formatter.format(log_line) }
|
|
10
|
+
|
|
11
|
+
before(:each) do
|
|
12
|
+
server_context = Timber::CurrentContext.get(Timber::Contexts::Server)
|
|
13
|
+
allow(server_context).to receive(:_dt).and_return(time)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it { should eq("\e7\e[1;30m@timber.io \e8\e[Kserver.hostname=comp\e8\e[Kuter-name.domain.com\e8\e[K server._dt=2016-09-\e8\e[K01T12:00:00.000000Z \e8\e[Kserver._version=1 se\e8\e[Krver._index=0 _versi\e8\e[Kon=1 _hierarchy=[ser\e8\e[Kver] @original \e8\e[K\e[0ma message") }
|
|
17
|
+
|
|
18
|
+
context "with a slash" do
|
|
19
|
+
let(:message) { "this is a long message that exceeds the step size".insert(described_class::CLEAR_STEP_SIZE - 1, "\\") }
|
|
20
|
+
|
|
21
|
+
before(:each) do
|
|
22
|
+
allow(formatter).to receive(:encoded_context).and_return(message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it { should eq("\e7\e[1;30m@timber.io \e8\e[Kthis is a long mess\\a\e8\e[Kge that exceeds the \e8\e[Kstep size @original \e8\e[K\e[0mthis is a long mess\\a\e8\e[Kge that exceeds the \e8\e[Kstep size") }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::LogLine do
|
|
4
|
+
let(:message) { "this is a message" }
|
|
5
|
+
let(:log_line) { described_class.new(message) }
|
|
6
|
+
|
|
7
|
+
around(:each) do |example|
|
|
8
|
+
heroku = Timber::Contexts::Servers::HerokuSpecific.new("web.1")
|
|
9
|
+
Timber::CurrentContext.add(heroku) { example.run }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "#initialize" do
|
|
13
|
+
subject { log_line }
|
|
14
|
+
|
|
15
|
+
its(:dt) { should_not be_nil }
|
|
16
|
+
its(:message) { should equal(message) }
|
|
17
|
+
|
|
18
|
+
context "non string" do
|
|
19
|
+
let(:message) { :"this is a message" }
|
|
20
|
+
its(:message) { should eq(message.to_s) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context "exceeds bytesize limit" do
|
|
24
|
+
let(:limit) { Timber::APISettings::MESSAGE_BYTE_SIZE_MAX }
|
|
25
|
+
let(:message) { (1..(limit + 1)).collect { "A" }.join }
|
|
26
|
+
its(:message) { should_not eq(message) }
|
|
27
|
+
its(:message) { should eq(message.byteslice(0, Timber::APISettings::MESSAGE_BYTE_SIZE_MAX)) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "notifies CurrentLineIndexes" do
|
|
31
|
+
expect(Timber::CurrentLineIndexes).to receive(:log_line_added).once
|
|
32
|
+
subject
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe "#to_json" do
|
|
37
|
+
# Note: very important that we keep the iso8601 format. Otherwise the Timber API
|
|
38
|
+
# will recognized the date as invalid.
|
|
39
|
+
let(:as_json) do
|
|
40
|
+
{
|
|
41
|
+
dt: log_line.dt.iso8601(6),
|
|
42
|
+
message: log_line.message,
|
|
43
|
+
}.merge(log_line.context_snapshot.as_json)
|
|
44
|
+
end
|
|
45
|
+
let(:json) { as_json.to_json }
|
|
46
|
+
subject { log_line.to_json }
|
|
47
|
+
it { should eq(json) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::Macros::Compactor do
|
|
4
|
+
describe ".compact" do
|
|
5
|
+
let(:hash) { {} }
|
|
6
|
+
subject { described_class.compact(hash) }
|
|
7
|
+
it { should eq({}) }
|
|
8
|
+
|
|
9
|
+
context "nested" do
|
|
10
|
+
let(:hash) { {:whatever => {:nested => nil}} }
|
|
11
|
+
it { should eq({}) }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "nested with other values" do
|
|
15
|
+
let(:hash) { {:whatever => {:nested => nil, :with_val => 1}, :another => 1} }
|
|
16
|
+
it { should eq({:whatever => {:with_val => 1}, :another => 1}) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Timber::Macros::LogfmtEncoder do
|
|
4
|
+
describe ".encode" do
|
|
5
|
+
subject { described_class.encode(target) }
|
|
6
|
+
|
|
7
|
+
context "nil" do
|
|
8
|
+
let(:target) { nil }
|
|
9
|
+
it "should raise" do
|
|
10
|
+
expect { subject }.to raise_error(ArgumentError)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context "blank hash" do
|
|
15
|
+
let(:target) { {} }
|
|
16
|
+
it { should eq("") }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "simple hash" do
|
|
20
|
+
let(:target) { {"key" => "value"} }
|
|
21
|
+
it { should eq("key=value") }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "with space in value" do
|
|
25
|
+
let(:target) { {"key" => "this is a value"} }
|
|
26
|
+
it { should eq("key=\"this is a value\"") }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "with quote in value" do
|
|
30
|
+
let(:target) { {"key" => "value\"another"} }
|
|
31
|
+
it { should eq("key=\"value\\\"another\"") }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context "with a space in the key" do
|
|
35
|
+
let(:target) { {"this is a key" => "value"} }
|
|
36
|
+
it { should eq("\"this is a key\"=value") }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context "with a . in the key" do
|
|
40
|
+
let(:target) { {"this.is.a.key" => "value"} }
|
|
41
|
+
it { should eq("\"this.is.a.key\"=value") }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context "with a nested hash" do
|
|
45
|
+
let(:target) { {"key" => {"sub_key" => "value"}} }
|
|
46
|
+
it { should eq("key.sub_key=value") }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context "with a nested hash and space in the key" do
|
|
50
|
+
let(:target) { {"key" => {"sub key" => "value"}} }
|
|
51
|
+
it { should eq("key.\"sub key\"=value") }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "with a nested hash and . in the key" do
|
|
55
|
+
let(:target) { {"key" => {"sub.key" => "value"}} }
|
|
56
|
+
it { should eq("key.\"sub.key\"=value") }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "with an integer value" do
|
|
60
|
+
let(:target) { {"key" => 1} }
|
|
61
|
+
it { should eq("key=1") }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context "with a float value" do
|
|
65
|
+
let(:target) { {"key" => 1.23} }
|
|
66
|
+
it { should eq("key=1.23") }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
context "with a true value" do
|
|
70
|
+
let(:target) { {"key" => true} }
|
|
71
|
+
it { should eq("key=true") }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
context "with a false value" do
|
|
75
|
+
let(:target) { {"key" => false} }
|
|
76
|
+
it { should eq("key=false") }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
context "with an array value" do
|
|
80
|
+
let(:target) { {"key" => ["this is a value",2,3]} }
|
|
81
|
+
it { should eq("key=[\"this is a value\",2,3]") }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context "with an array value that has a ," do
|
|
85
|
+
let(:target) { {"key" => ["this,is a value",2,3]} }
|
|
86
|
+
it { should eq("key=[\"this,is a value\",2,3]") }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|