appsignal 0.11.18 → 0.12.beta.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG.md +4 -38
  3. data/Rakefile +14 -6
  4. data/appsignal.gemspec +3 -1
  5. data/benchmark.rake +12 -16
  6. data/ext/appsignal_extension.c +183 -0
  7. data/ext/extconf.rb +39 -0
  8. data/gemfiles/capistrano2.gemfile +0 -1
  9. data/gemfiles/capistrano3.gemfile +0 -1
  10. data/gemfiles/rails-4.2.gemfile +1 -1
  11. data/lib/appsignal.rb +23 -61
  12. data/lib/appsignal/capistrano.rb +1 -2
  13. data/lib/appsignal/config.rb +13 -1
  14. data/lib/appsignal/event_formatter.rb +67 -0
  15. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +23 -0
  16. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +74 -0
  17. data/lib/appsignal/event_formatter/moped/query_formatter.rb +80 -0
  18. data/lib/appsignal/event_formatter/net_http/request_formatter.rb +13 -0
  19. data/lib/appsignal/instrumentations/net_http.rb +6 -4
  20. data/lib/appsignal/integrations/resque.rb +2 -10
  21. data/lib/appsignal/integrations/sidekiq.rb +2 -2
  22. data/lib/appsignal/integrations/sinatra.rb +1 -0
  23. data/lib/appsignal/js_exception_transaction.rb +44 -28
  24. data/lib/appsignal/marker.rb +11 -13
  25. data/lib/appsignal/params_sanitizer.rb +5 -8
  26. data/lib/appsignal/rack/instrumentation.rb +2 -0
  27. data/lib/appsignal/rack/js_exception_catcher.rb +1 -0
  28. data/lib/appsignal/rack/listener.rb +1 -1
  29. data/lib/appsignal/rack/sinatra_instrumentation.rb +2 -12
  30. data/lib/appsignal/subscriber.rb +59 -0
  31. data/lib/appsignal/transaction.rb +117 -174
  32. data/lib/appsignal/transmitter.rb +8 -37
  33. data/lib/appsignal/version.rb +2 -1
  34. data/spec/lib/appsignal/config_spec.rb +25 -4
  35. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +42 -0
  36. data/spec/lib/appsignal/{aggregator/middleware/active_record_sanitizer_spec.rb → event_formatter/active_record/sql_formatter_spec.rb} +61 -61
  37. data/spec/lib/appsignal/{event/moped_event_spec.rb → event_formatter/moped/query_formatter_spec.rb} +32 -78
  38. data/spec/lib/appsignal/event_formatter/net_http/request_formatter_spec.rb +26 -0
  39. data/spec/lib/appsignal/event_formatter_spec.rb +102 -0
  40. data/spec/lib/appsignal/extension_spec.rb +75 -0
  41. data/spec/lib/appsignal/instrumentations/net_http_spec.rb +20 -4
  42. data/spec/lib/appsignal/integrations/delayed_job_spec.rb +3 -2
  43. data/spec/lib/appsignal/integrations/rails_spec.rb +0 -7
  44. data/spec/lib/appsignal/integrations/resque_spec.rb +51 -55
  45. data/spec/lib/appsignal/integrations/sequel_spec.rb +8 -3
  46. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +4 -21
  47. data/spec/lib/appsignal/integrations/sinatra_spec.rb +0 -6
  48. data/spec/lib/appsignal/js_exception_transaction_spec.rb +57 -60
  49. data/spec/lib/appsignal/params_sanitizer_spec.rb +11 -27
  50. data/spec/lib/appsignal/rack/listener_spec.rb +6 -6
  51. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +2 -43
  52. data/spec/lib/appsignal/subscriber_spec.rb +162 -0
  53. data/spec/lib/appsignal/transaction_spec.rb +283 -615
  54. data/spec/lib/appsignal/transmitter_spec.rb +3 -32
  55. data/spec/lib/appsignal_spec.rb +41 -90
  56. data/spec/lib/generators/appsignal/appsignal_generator_spec.rb +0 -17
  57. data/spec/spec_helper.rb +18 -22
  58. data/spec/support/helpers/notification_helpers.rb +1 -1
  59. data/spec/support/helpers/time_helpers.rb +11 -0
  60. data/spec/support/helpers/transaction_helpers.rb +6 -18
  61. data/spec/support/project_fixture/config/appsignal.yml +1 -2
  62. metadata +68 -78
  63. checksums.yaml +0 -7
  64. data/gemfiles/padrino-0.13.gemfile +0 -7
  65. data/gemfiles/resque.gemfile +0 -5
  66. data/lib/appsignal/agent.rb +0 -217
  67. data/lib/appsignal/aggregator.rb +0 -67
  68. data/lib/appsignal/aggregator/middleware.rb +0 -4
  69. data/lib/appsignal/aggregator/middleware/action_view_sanitizer.rb +0 -23
  70. data/lib/appsignal/aggregator/middleware/active_record_sanitizer.rb +0 -65
  71. data/lib/appsignal/aggregator/middleware/chain.rb +0 -101
  72. data/lib/appsignal/aggregator/middleware/delete_blanks.rb +0 -16
  73. data/lib/appsignal/aggregator/post_processor.rb +0 -32
  74. data/lib/appsignal/event.rb +0 -20
  75. data/lib/appsignal/event/moped_event.rb +0 -90
  76. data/lib/appsignal/integrations/padrino.rb +0 -64
  77. data/lib/appsignal/integrations/passenger.rb +0 -13
  78. data/lib/appsignal/integrations/rake.rb +0 -29
  79. data/lib/appsignal/integrations/unicorn.rb +0 -25
  80. data/lib/appsignal/ipc.rb +0 -68
  81. data/lib/appsignal/transaction/formatter.rb +0 -85
  82. data/lib/appsignal/transaction/params_sanitizer.rb +0 -4
  83. data/lib/appsignal/zipped_payload.rb +0 -37
  84. data/spec/lib/appsignal/agent_spec.rb +0 -592
  85. data/spec/lib/appsignal/aggregator/middleware/action_view_sanitizer_spec.rb +0 -44
  86. data/spec/lib/appsignal/aggregator/middleware/chain_spec.rb +0 -168
  87. data/spec/lib/appsignal/aggregator/middleware/delete_blanks_spec.rb +0 -37
  88. data/spec/lib/appsignal/aggregator/post_processor_spec.rb +0 -99
  89. data/spec/lib/appsignal/aggregator_spec.rb +0 -186
  90. data/spec/lib/appsignal/event_spec.rb +0 -48
  91. data/spec/lib/appsignal/integrations/padrino_spec.rb +0 -171
  92. data/spec/lib/appsignal/integrations/passenger_spec.rb +0 -22
  93. data/spec/lib/appsignal/integrations/rake_spec.rb +0 -92
  94. data/spec/lib/appsignal/integrations/unicorn_spec.rb +0 -48
  95. data/spec/lib/appsignal/ipc_spec.rb +0 -128
  96. data/spec/lib/appsignal/transaction/formatter_spec.rb +0 -247
  97. data/spec/lib/appsignal/zipped_payload_spec.rb +0 -42
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::EventFormatter::NetHttp::RequestFormatter do
4
+ let(:klass) { Appsignal::EventFormatter::NetHttp::RequestFormatter }
5
+ let(:formatter) { klass.new }
6
+
7
+ it "should register request.net_http" do
8
+ Appsignal::EventFormatter.registered?('request.net_http', klass).should be_true
9
+ end
10
+
11
+ describe "#format" do
12
+ let(:payload) do
13
+ {
14
+ :protocol => 'http',
15
+ :url => 'appsignal.com',
16
+ :domain => 'appsignal.com',
17
+ :path => '/about',
18
+ :method => 'GET'
19
+ }
20
+ end
21
+
22
+ subject { formatter.format(payload) }
23
+
24
+ it { should == ['GET http://appsignal.com', nil] }
25
+ end
26
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ class MockFormatter < Appsignal::EventFormatter
4
+ register 'mock'
5
+
6
+ attr_reader :body
7
+
8
+ def initialize
9
+ @body = 'some value'
10
+ end
11
+
12
+ def format(payload)
13
+ ['title', @body]
14
+ end
15
+ end
16
+
17
+ class MissingFormatMockFormatter < Appsignal::EventFormatter
18
+ def transform(payload)
19
+ end
20
+ end
21
+
22
+ class IncorrectFormatMockFormatter < Appsignal::EventFormatter
23
+ def format
24
+ end
25
+ end
26
+
27
+ class MockDependentFormatter < Appsignal::EventFormatter
28
+ register 'mock.dependent'
29
+
30
+ def initialize
31
+ NonsenseDependency.something
32
+ end
33
+ end
34
+
35
+ describe Appsignal::EventFormatter do
36
+ before do
37
+ Appsignal::EventFormatter.initialize_formatters
38
+ end
39
+
40
+ let(:klass) { Appsignal::EventFormatter }
41
+
42
+ context "registering and unregistering formatters" do
43
+ it "should register a formatter" do
44
+ klass.formatters['mock'].should be_instance_of(MockFormatter)
45
+ end
46
+
47
+ it "should know wether a formatter is registered" do
48
+ klass.registered?('mock').should be_true
49
+ klass.registered?('mock', MockFormatter).should be_true
50
+ klass.registered?('mock', Hash).should be_false
51
+ klass.registered?('nonsense').should be_false
52
+ end
53
+
54
+ it "doesn't register formatters that raise a name error in the initializer" do
55
+ klass.registered?('mock.dependent').should be_false
56
+ end
57
+
58
+ it "doesn't register formatters that don't have a format(payload) method" do
59
+ klass.register('mock.missing_format', MissingFormatMockFormatter)
60
+ klass.register('mock.incorrect_format', IncorrectFormatMockFormatter)
61
+
62
+ Appsignal::EventFormatter.initialize_formatters
63
+
64
+ klass.registered?('mock.missing_format').should be_false
65
+ klass.registered?('mock.incorrect_format').should be_false
66
+ end
67
+
68
+ it "should register a custom formatter" do
69
+ klass.register('mock.specific', MockFormatter)
70
+ Appsignal::EventFormatter.initialize_formatters
71
+
72
+ klass.formatter_classes['mock.specific'].should == MockFormatter
73
+ klass.registered?('mock.specific').should be_true
74
+ klass.formatters['mock.specific'].should be_instance_of(MockFormatter)
75
+ klass.formatters['mock.specific'].body.should == 'some value'
76
+ end
77
+
78
+ it "should not have a formatter that's not registered" do
79
+ klass.formatters['nonsense'].should be_nil
80
+ end
81
+
82
+ it "should unregister a formatter if the registered one has the same class" do
83
+ klass.register('mock.unregister', MockFormatter)
84
+
85
+ klass.unregister('mock.unregister', Hash)
86
+ klass.registered?('mock.unregister').should be_true
87
+
88
+ klass.unregister('mock.unregister', MockFormatter)
89
+ klass.registered?('mock.unregister').should be_false
90
+ end
91
+ end
92
+
93
+ context "calling formatters" do
94
+ it "should return nil if there is no formatter registered" do
95
+ klass.format('nonsense', {}).should == nil
96
+ end
97
+
98
+ it "should call the formatter if it is registered and use a value set in the initializer" do
99
+ klass.format('mock', {}).should == ['title', 'some value']
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Extension do
4
+ context "call native methods without errors" do
5
+ subject { Appsignal::Extension }
6
+
7
+ it "should have a start method" do
8
+ subject.start
9
+ end
10
+
11
+ context "with a valid config" do
12
+ before do
13
+ project_fixture_config.write_to_environment
14
+ end
15
+
16
+ it "should have a start method" do
17
+ subject.start
18
+ end
19
+
20
+ it "should have a start_transaction method" do
21
+ subject.start_transaction('request_id')
22
+ end
23
+
24
+ it "should have a start_event method" do
25
+ subject.start_event('request_id')
26
+ end
27
+
28
+ it "should have a finish_event method" do
29
+ subject.finish_event(
30
+ 'request_id',
31
+ 'name',
32
+ 'title',
33
+ 'body'
34
+ )
35
+ end
36
+
37
+ it "should have a set_transaction_error method" do
38
+ subject.set_transaction_error(
39
+ 'request_id',
40
+ 'name',
41
+ 'message'
42
+ )
43
+ end
44
+
45
+ it "should have a set_transaction_error_data method" do
46
+ subject.set_transaction_error_data(
47
+ 'request_id',
48
+ 'params',
49
+ '{}'
50
+ )
51
+ end
52
+
53
+ it "should have a set_transaction_basedata method" do
54
+ subject.set_transaction_basedata(
55
+ 'request_id',
56
+ 'kind',
57
+ 'action',
58
+ 100
59
+ )
60
+ end
61
+
62
+ it "should have a set_transaction_metadata method" do
63
+ subject.set_transaction_metadata(
64
+ 'request_id',
65
+ 'key',
66
+ 'value'
67
+ )
68
+ end
69
+
70
+ it "should have a finish_transaction method" do
71
+ subject.finish_transaction('request_id')
72
+ end
73
+ end
74
+ end
75
+ end
@@ -9,15 +9,31 @@ describe "Net::HTTP instrumentation" do
9
9
  end
10
10
  end
11
11
 
12
- it "should instrument request" do
13
- stub_request(:any, 'www.google.com')
12
+ it "should generate an event for a http request" do
13
+ stub_request(:any, 'http://www.google.com/')
14
14
 
15
15
  Net::HTTP.get_response(URI.parse('http://www.google.com'))
16
16
 
17
17
  event = events.last
18
18
  event.name.should == 'request.net_http'
19
- event.payload[:host].should == 'www.google.com'
20
- event.payload[:scheme].should == 'http'
19
+ event.payload[:protocol].should == 'http'
20
+ event.payload[:domain].should == 'www.google.com'
21
+ event.payload[:path].should == '/'
22
+ event.payload[:method].should == 'GET'
23
+ end
24
+
25
+ it "should generate an event for a https request" do
26
+ stub_request(:any, 'https://www.google.com/')
27
+
28
+ uri = URI.parse('https://www.google.com')
29
+ http = Net::HTTP.new(uri.host, uri.port)
30
+ http.use_ssl = true
31
+ http.get(uri.request_uri)
32
+
33
+ event = events.last
34
+ event.name.should == 'request.net_http'
35
+ event.payload[:protocol].should == 'https'
36
+ event.payload[:domain].should == 'www.google.com'
21
37
  event.payload[:path].should == '/'
22
38
  event.payload[:method].should == 'GET'
23
39
  end
@@ -99,9 +99,10 @@ describe "Delayed Job integration" do
99
99
 
100
100
  context "with an erroring call" do
101
101
  it "should add the error to the transaction" do
102
- Appsignal::Transaction.any_instance.should_receive(:add_exception).with(error)
102
+ Appsignal::Transaction.any_instance.should_receive(:set_error).with(error)
103
+ Appsignal::Transaction.should_receive(:complete_current!)
104
+
103
105
  invoked_block.stub(:call).and_raise(error)
104
- Appsignal::Transaction.any_instance.should_receive(:complete!)
105
106
 
106
107
  lambda {
107
108
  plugin.invoke_with_instrumentation(job, invoked_block)
@@ -51,13 +51,6 @@ if rails_present?
51
51
  end
52
52
  end
53
53
 
54
- context "agent" do
55
- before { Appsignal::Integrations::Railtie.initialize_appsignal(app) }
56
- subject { Appsignal.agent }
57
-
58
- it { should be_a(Appsignal::Agent) }
59
- end
60
-
61
54
  context "listener middleware" do
62
55
  it "should have added the listener middleware" do
63
56
  expect( app.middleware ).to receive(:insert_before).with(
@@ -1,82 +1,78 @@
1
1
  require 'spec_helper'
2
2
 
3
- if resque_present?
4
- describe "Resque integration" do
5
- let(:file) { File.expand_path('lib/appsignal/integrations/resque.rb') }
3
+ describe "Resque integration" do
4
+ let(:file) { File.expand_path('lib/appsignal/integrations/resque.rb') }
6
5
 
7
- context "with resque" do
8
- before do
9
- load file
10
- start_agent
11
-
12
- class TestJob
13
- extend Appsignal::Integrations::ResquePlugin
6
+ context "with resque" do
7
+ before do
8
+ module Resque
14
9
 
15
- def self.perform
16
- end
10
+ def self.before_first_fork
17
11
  end
18
12
 
19
- class BrokenTestJob
20
- extend Appsignal::Integrations::ResquePlugin
13
+ def self.after_fork
14
+ end
21
15
 
22
- def self.perform
23
- raise VerySpecificError.new('broken')
24
- end
16
+ class Job
25
17
  end
26
18
 
19
+ class TestError < StandardError
20
+ end
27
21
  end
28
22
 
29
- describe :around_perform_resque_plugin do
30
- let(:transaction) { Appsignal::Transaction.new(1, {}) }
31
- let(:job) { Resque::Job.new('default', {'class' => 'TestJob'}) }
32
- before do
33
- transaction.stub(:complete! => true)
34
- Appsignal::Transaction.stub(:current => transaction)
35
- end
23
+ load file
24
+ start_agent
25
+ end
36
26
 
37
- context "without exception" do
38
- it "should create a new transaction" do
39
- Appsignal::Transaction.should_receive(:create).and_return(transaction)
40
- end
27
+ describe :around_perform_resque_plugin do
28
+ let(:transaction) { Appsignal::Transaction.new('1', {}) }
29
+ let(:job) { Resque::Job }
30
+ let(:invoked_job) { nil }
31
+ before do
32
+ transaction.stub(:complete! => true)
33
+ Appsignal::Transaction.stub(:current => transaction)
34
+ end
41
35
 
42
- it "should wrap in a transaction with the correct params" do
43
- Appsignal.should_receive(:monitor_transaction).with(
44
- 'perform_job.resque',
45
- :class => 'TestJob',
46
- :method => 'perform'
47
- )
48
- end
36
+ context "without exception" do
37
+ it "should create a new transaction" do
38
+ Appsignal::Transaction.should_receive(:create).and_return(transaction)
39
+ end
49
40
 
50
- it "should close the transaction" do
51
- transaction.should_receive(:complete!)
52
- end
41
+ it "should wrap in a transaction with the correct params" do
42
+ Appsignal.should_receive(:monitor_transaction).with(
43
+ 'perform_job.resque',
44
+ :class => 'Resque::Job',
45
+ :method => 'perform'
46
+ )
47
+ end
53
48
 
54
- after { job.perform }
49
+ it "should close the transaction" do
50
+ Appsignal::Transaction.should_receive(:complete_current!)
55
51
  end
56
52
 
57
- context "with exception" do
58
- let(:job) { Resque::Job.new('default', {'class' => 'BrokenTestJob'}) }
53
+ after { job.around_perform_resque_plugin { invoked_job } }
54
+ end
59
55
 
60
- it "should set the exception" do
61
- transaction.should_receive(:add_exception)
62
- end
56
+ context "with exception" do
57
+ it "should set the exception" do
58
+ transaction.should_receive(:set_error)
59
+ end
63
60
 
64
- after do
65
- begin
66
- job.perform
67
- rescue VerySpecificError
68
- # Do nothing
69
- end
61
+ after do
62
+ begin
63
+ job.around_perform_resque_plugin { raise(Resque::TestError.new('the roof')) }
64
+ rescue Resque::TestError
65
+ # Do nothing
70
66
  end
71
67
  end
72
68
  end
73
69
  end
70
+ end
74
71
 
75
- context "without resque" do
76
- before(:all) { Object.send(:remove_const, :Resque) }
72
+ context "without resque" do
73
+ before(:all) { Object.send(:remove_const, :Resque) }
77
74
 
78
- specify { expect { ::Resque }.to raise_error(NameError) }
79
- specify { expect { load file }.to_not raise_error }
80
- end
75
+ specify { expect { ::Resque }.to raise_error(NameError) }
76
+ specify { expect { load file }.to_not raise_error }
81
77
  end
82
78
  end
@@ -13,9 +13,14 @@ describe "Sequel integration", if: sequel_present? do
13
13
  before { Appsignal::Transaction.create('uuid', 'test') }
14
14
 
15
15
  it "should instrument queries" do
16
- expect { db['SELECT 1'].all }
17
- .to change { Appsignal::Transaction.current.events.empty? }
18
- .from(true).to(false)
16
+ expect( Appsignal::Extension ).to receive(:start_event)
17
+ .at_least(:once)
18
+ .with('uuid')
19
+ expect( Appsignal::Extension ).to receive(:finish_event)
20
+ .at_least(:once)
21
+ .with('uuid', "sql.sequel", "", "")
22
+
23
+ db['SELECT 1'].all
19
24
  end
20
25
  end
21
26
  end
@@ -47,23 +47,6 @@ describe "Sidekiq integration" do
47
47
  )
48
48
  end
49
49
 
50
- it "reports the correct job class for a ActiveJob wrapped job" do
51
- item['wrapped'] = 'ActiveJobClass'
52
- Appsignal.should_receive(:monitor_transaction).with(
53
- 'perform_job.sidekiq',
54
- :class => 'ActiveJobClass',
55
- :method => 'perform',
56
- :metadata => {
57
- 'retry_count' => "0",
58
- 'queue' => 'default',
59
- 'extra' => 'data',
60
- 'wrapped' => 'ActiveJobClass'
61
- },
62
- :params => ['Model', "1"],
63
- :queue_start => Time.parse('01-01-2001 10:00:00UTC')
64
- )
65
- end
66
-
67
50
  after do
68
51
  Timecop.freeze(Time.parse('01-01-2001 10:01:00UTC')) do
69
52
  Appsignal::Integrations::SidekiqPlugin.new.call(worker, item, queue) do
@@ -76,7 +59,7 @@ describe "Sidekiq integration" do
76
59
  context "with an erroring call" do
77
60
  let(:error) { VerySpecificError.new('the roof') }
78
61
  it "should add the exception to appsignal" do
79
- current_transaction.should_receive(:add_exception).with(error)
62
+ current_transaction.should_receive(:set_error).with(error)
80
63
  end
81
64
 
82
65
  after do
@@ -121,11 +104,11 @@ describe "Sidekiq integration" do
121
104
 
122
105
  describe "#truncate" do
123
106
  let(:very_long_text) do
124
- "a" * 400
107
+ "a" * 200
125
108
  end
126
109
 
127
- it "should truncate the text to 200 chars max" do
128
- plugin.truncate(very_long_text).should == "#{'a' * 197}..."
110
+ it "should truncate the text to 100 chars max" do
111
+ plugin.truncate(very_long_text).should == "#{'a' * 97}..."
129
112
  end
130
113
  end
131
114