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.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +34 -0
  3. data/.gitignore +14 -0
  4. data/Appraisals +37 -0
  5. data/Gemfile +22 -0
  6. data/LICENSE +38 -0
  7. data/README.md +22 -0
  8. data/Rakefile +4 -0
  9. data/TODO +4 -0
  10. data/benchmark/README.md +26 -0
  11. data/benchmark/rails_request.rb +68 -0
  12. data/benchmark/support/rails.rb +69 -0
  13. data/circle.yml +27 -0
  14. data/docs/installation/rails_on_heroku.md +31 -0
  15. data/docs/installation/rails_over_http.md +22 -0
  16. data/gemfiles/rails_3.0.X.gemfile +25 -0
  17. data/gemfiles/rails_3.1.X.gemfile +25 -0
  18. data/gemfiles/rails_3.2.X.gemfile +25 -0
  19. data/gemfiles/rails_4.0.X.gemfile +26 -0
  20. data/gemfiles/rails_4.1.X.gemfile +26 -0
  21. data/gemfiles/rails_4.2.X.gemfile +26 -0
  22. data/gemfiles/rails_5.0.X.gemfile +26 -0
  23. data/gemfiles/rails_edge.gemfile +27 -0
  24. data/lib/timber/api_settings.rb +17 -0
  25. data/lib/timber/bootstrap.rb +45 -0
  26. data/lib/timber/config.rb +25 -0
  27. data/lib/timber/context.rb +76 -0
  28. data/lib/timber/context_snapshot.rb +64 -0
  29. data/lib/timber/contexts/dynamic_values.rb +59 -0
  30. data/lib/timber/contexts/exception.rb +40 -0
  31. data/lib/timber/contexts/http_request.rb +22 -0
  32. data/lib/timber/contexts/http_requests/action_controller_specific.rb +48 -0
  33. data/lib/timber/contexts/http_requests/rack/params.rb +26 -0
  34. data/lib/timber/contexts/http_requests/rack.rb +105 -0
  35. data/lib/timber/contexts/http_response.rb +19 -0
  36. data/lib/timber/contexts/http_responses/action_controller.rb +76 -0
  37. data/lib/timber/contexts/logger.rb +33 -0
  38. data/lib/timber/contexts/organization.rb +33 -0
  39. data/lib/timber/contexts/organizations/action_controller.rb +34 -0
  40. data/lib/timber/contexts/server.rb +21 -0
  41. data/lib/timber/contexts/servers/heroku_specific.rb +48 -0
  42. data/lib/timber/contexts/sql_queries/active_record.rb +30 -0
  43. data/lib/timber/contexts/sql_queries/active_record_specific/binds.rb +37 -0
  44. data/lib/timber/contexts/sql_queries/active_record_specific.rb +59 -0
  45. data/lib/timber/contexts/sql_query.rb +18 -0
  46. data/lib/timber/contexts/template_render.rb +17 -0
  47. data/lib/timber/contexts/template_renders/action_view.rb +29 -0
  48. data/lib/timber/contexts/template_renders/action_view_specific.rb +51 -0
  49. data/lib/timber/contexts/user.rb +39 -0
  50. data/lib/timber/contexts/users/action_controller.rb +34 -0
  51. data/lib/timber/contexts.rb +23 -0
  52. data/lib/timber/current_context.rb +58 -0
  53. data/lib/timber/current_line_indexes.rb +35 -0
  54. data/lib/timber/frameworks/rails.rb +24 -0
  55. data/lib/timber/frameworks.rb +21 -0
  56. data/lib/timber/internal_logger.rb +35 -0
  57. data/lib/timber/log_device.rb +40 -0
  58. data/lib/timber/log_devices/heroku_logplex/hybrid_formatter.rb +14 -0
  59. data/lib/timber/log_devices/heroku_logplex.rb +14 -0
  60. data/lib/timber/log_devices/http/log_pile.rb +86 -0
  61. data/lib/timber/log_devices/http/log_truck/delivery.rb +116 -0
  62. data/lib/timber/log_devices/http/log_truck.rb +87 -0
  63. data/lib/timber/log_devices/http.rb +28 -0
  64. data/lib/timber/log_devices/io/formatter.rb +46 -0
  65. data/lib/timber/log_devices/io/hybrid_formatter.rb +41 -0
  66. data/lib/timber/log_devices/io/hybrid_hidden_formatter.rb +36 -0
  67. data/lib/timber/log_devices/io/json_formatter.rb +11 -0
  68. data/lib/timber/log_devices/io/logfmt_formatter.rb +11 -0
  69. data/lib/timber/log_devices/io.rb +41 -0
  70. data/lib/timber/log_devices.rb +4 -0
  71. data/lib/timber/log_line.rb +33 -0
  72. data/lib/timber/logger.rb +20 -0
  73. data/lib/timber/macros/compactor.rb +16 -0
  74. data/lib/timber/macros/date_formatter.rb +9 -0
  75. data/lib/timber/macros/deep_merger.rb +11 -0
  76. data/lib/timber/macros/logfmt_encoder.rb +77 -0
  77. data/lib/timber/macros.rb +4 -0
  78. data/lib/timber/patterns/delegated_singleton.rb +21 -0
  79. data/lib/timber/patterns/to_json.rb +22 -0
  80. data/lib/timber/patterns/to_logfmt.rb +9 -0
  81. data/lib/timber/patterns.rb +3 -0
  82. data/lib/timber/probe.rb +21 -0
  83. data/lib/timber/probes/action_controller_base.rb +31 -0
  84. data/lib/timber/probes/action_dispatch_debug_exceptions.rb +57 -0
  85. data/lib/timber/probes/active_support_log_subscriber/action_controller.rb +15 -0
  86. data/lib/timber/probes/active_support_log_subscriber/action_view.rb +26 -0
  87. data/lib/timber/probes/active_support_log_subscriber/active_record.rb +13 -0
  88. data/lib/timber/probes/active_support_log_subscriber.rb +62 -0
  89. data/lib/timber/probes/heroku.rb +30 -0
  90. data/lib/timber/probes/logger.rb +31 -0
  91. data/lib/timber/probes/rack.rb +36 -0
  92. data/lib/timber/probes/server.rb +18 -0
  93. data/lib/timber/probes.rb +24 -0
  94. data/lib/timber/version.rb +3 -0
  95. data/lib/timber.rb +27 -0
  96. data/spec/spec_helper.rb +27 -0
  97. data/spec/support/action_controller.rb +4 -0
  98. data/spec/support/action_view.rb +4 -0
  99. data/spec/support/active_record.rb +28 -0
  100. data/spec/support/coveralls.rb +2 -0
  101. data/spec/support/rails/templates/_partial.html +1 -0
  102. data/spec/support/rails/templates/template.html +1 -0
  103. data/spec/support/rails.rb +33 -0
  104. data/spec/support/simplecov.rb +9 -0
  105. data/spec/support/socket_hostname.rb +12 -0
  106. data/spec/support/timber.rb +23 -0
  107. data/spec/support/timecop.rb +3 -0
  108. data/spec/support/webmock.rb +2 -0
  109. data/spec/timber/bootstrap_spec.rb +31 -0
  110. data/spec/timber/context_snapshot_spec.rb +10 -0
  111. data/spec/timber/context_spec.rb +4 -0
  112. data/spec/timber/contexts/exception_spec.rb +34 -0
  113. data/spec/timber/contexts/organizations/action_controller_spec.rb +49 -0
  114. data/spec/timber/contexts/users/action_controller_spec.rb +65 -0
  115. data/spec/timber/current_line_indexes_spec.rb +40 -0
  116. data/spec/timber/frameworks/rails_spec.rb +9 -0
  117. data/spec/timber/log_devices/heroku_logplex_spec.rb +45 -0
  118. data/spec/timber/log_devices/http/log_truck/delivery_spec.rb +66 -0
  119. data/spec/timber/log_devices/http/log_truck_spec.rb +65 -0
  120. data/spec/timber/log_devices/io/hybrid_hidden_formatter_spec.rb +28 -0
  121. data/spec/timber/log_line_spec.rb +49 -0
  122. data/spec/timber/macros/compactor_spec.rb +19 -0
  123. data/spec/timber/macros/logfmt_encoder_spec.rb +89 -0
  124. data/spec/timber/patterns/to_json_spec.rb +40 -0
  125. data/spec/timber/probes/action_controller_base_spec.rb +43 -0
  126. data/spec/timber/probes/action_controller_log_subscriber/action_controller_spec.rb +35 -0
  127. data/spec/timber/probes/action_controller_log_subscriber/action_view_spec.rb +44 -0
  128. data/spec/timber/probes/action_controller_log_subscriber/active_record_spec.rb +26 -0
  129. data/spec/timber/probes/action_dispatch_debug_exceptions_spec.rb +45 -0
  130. data/spec/timber/probes/logger_spec.rb +20 -0
  131. data/spec/timber/probes/rack_spec.rb +26 -0
  132. data/timberio.gemspec +20 -0
  133. metadata +210 -0
@@ -0,0 +1,9 @@
1
+ require 'simplecov'
2
+
3
+ # save to CircleCI's artifacts directory if we're on CircleCI
4
+ if ENV['CIRCLE_ARTIFACTS']
5
+ dir = File.join(ENV['CIRCLE_ARTIFACTS'], "coverage")
6
+ SimpleCov.coverage_dir(dir)
7
+ end
8
+
9
+ SimpleCov.start
@@ -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,3 @@
1
+ require 'timecop'
2
+
3
+ Timecop.safe_mode = true
@@ -0,0 +1,2 @@
1
+ require 'webmock/rspec'
2
+ WebMock.disable_net_connect!
@@ -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,10 @@
1
+ require "spec_helper"
2
+
3
+ describe Timber::ContextSnapshot do
4
+ describe "#initialize" do
5
+ it "only includes the valid stack" do
6
+ expect(Timber::CurrentContext).to receive(:valid_stack).once.and_return([])
7
+ described_class.new
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ require "spec_helper"
2
+
3
+ describe Timber::Context do
4
+ 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,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe Timber::Frameworks::Rails do
4
+ # describe described_class::Railtie do
5
+ # it "includes the initializer" do
6
+ # expect(RailsApp.instance.initializers.collect { |i| i.name }).to include("timber.bootstrap")
7
+ # end
8
+ # end
9
+ 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