appsignal 0.12.beta.31 → 0.12.beta.32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/Rakefile +1 -0
  4. data/benchmark.rake +20 -20
  5. data/ext/appsignal_extension.c +31 -23
  6. data/gemfiles/padrino.gemfile +7 -0
  7. data/lib/appsignal.rb +50 -27
  8. data/lib/appsignal/capistrano.rb +2 -1
  9. data/lib/appsignal/config.rb +94 -39
  10. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +12 -17
  11. data/lib/appsignal/integrations/padrino.rb +65 -0
  12. data/lib/appsignal/integrations/rails.rb +4 -2
  13. data/lib/appsignal/integrations/rake.rb +30 -0
  14. data/lib/appsignal/integrations/sidekiq.rb +2 -2
  15. data/lib/appsignal/integrations/sinatra.rb +0 -1
  16. data/lib/appsignal/js_exception_transaction.rb +4 -9
  17. data/lib/appsignal/params_sanitizer.rb +8 -5
  18. data/lib/appsignal/rack/rails_instrumentation.rb +41 -0
  19. data/lib/appsignal/rack/sinatra_instrumentation.rb +31 -24
  20. data/lib/appsignal/subscriber.rb +2 -9
  21. data/lib/appsignal/transaction.rb +86 -75
  22. data/lib/appsignal/transmitter.rb +30 -3
  23. data/lib/appsignal/version.rb +2 -2
  24. data/spec/lib/appsignal/cli_spec.rb +1 -1
  25. data/spec/lib/appsignal/config_spec.rb +38 -131
  26. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +27 -29
  27. data/spec/lib/appsignal/extension_spec.rb +11 -29
  28. data/spec/lib/appsignal/integrations/padrino_spec.rb +191 -0
  29. data/spec/lib/appsignal/integrations/rails_spec.rb +3 -4
  30. data/spec/lib/appsignal/integrations/rake_spec.rb +78 -0
  31. data/spec/lib/appsignal/integrations/resque_spec.rb +2 -2
  32. data/spec/lib/appsignal/integrations/sequel_spec.rb +2 -3
  33. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +22 -5
  34. data/spec/lib/appsignal/integrations/sinatra_spec.rb +0 -6
  35. data/spec/lib/appsignal/js_exception_transaction_spec.rb +4 -6
  36. data/spec/lib/appsignal/params_sanitizer_spec.rb +27 -11
  37. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +79 -0
  38. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +71 -71
  39. data/spec/lib/appsignal/subscriber_spec.rb +3 -37
  40. data/spec/lib/appsignal/transaction_spec.rb +290 -155
  41. data/spec/lib/appsignal/transmitter_spec.rb +10 -0
  42. data/spec/lib/appsignal_spec.rb +80 -47
  43. data/spec/spec_helper.rb +21 -2
  44. data/spec/support/helpers/env_helpers.rb +31 -0
  45. data/spec/support/helpers/notification_helpers.rb +1 -30
  46. data/spec/support/helpers/transaction_helpers.rb +7 -7
  47. data/spec/support/project_fixture/config/appsignal.yml +2 -0
  48. metadata +14 -8
  49. data/lib/appsignal/rack/instrumentation.rb +0 -32
  50. data/lib/appsignal/rack/listener.rb +0 -32
  51. data/spec/lib/appsignal/rack/instrumentation_spec.rb +0 -72
  52. data/spec/lib/appsignal/rack/listener_spec.rb +0 -104
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ begin
4
+ require 'padrino'
5
+ rescue LoadError
6
+ end
7
+
8
+ if padrino_present?
9
+ describe "Padrino integration" do
10
+ require File.expand_path('lib/appsignal/integrations/padrino.rb')
11
+
12
+ class ClassWithRouter
13
+ include Padrino::Routing
14
+ end
15
+
16
+ before do
17
+ Appsignal.stub(
18
+ :active? => true,
19
+ :start => true,
20
+ :start_logger => true
21
+ )
22
+ end
23
+
24
+ describe "Appsignal::Integrations::PadrinoPlugin" do
25
+ it "should start the logger on init" do
26
+ expect( Appsignal ).to receive(:start_logger)
27
+ end
28
+
29
+ it "should start appsignal on init" do
30
+ expect( Appsignal ).to receive(:start)
31
+ end
32
+
33
+ context "when not active" do
34
+ before { Appsignal.stub(:active? => false) }
35
+
36
+ it "should not add the Listener middleware to the stack" do
37
+ expect( Padrino ).to_not receive(:use)
38
+ end
39
+ end
40
+
41
+ after { Appsignal::Integrations::PadrinoPlugin.init }
42
+ end
43
+
44
+ describe "Padrino::Routing::InstanceMethods" do
45
+ let(:base) { double }
46
+ let(:router) { ClassWithRouter.new }
47
+ let(:env) { {} }
48
+ let(:settings) { double(:name => 'TestApp') }
49
+
50
+ describe "#route!" do
51
+ let(:request) do
52
+ double(
53
+ :params => {'id' => 1},
54
+ :session => {'user_id' => 123},
55
+ :request_method => 'GET',
56
+ :path => '/users/1',
57
+ :controller => 'users',
58
+ :action => 'show',
59
+ :env => {}
60
+ )
61
+ end
62
+
63
+ before do
64
+ router.stub(
65
+ :route_without_appsignal => true,
66
+ :request => request,
67
+ :env => env,
68
+ :settings => settings,
69
+ :get_payload_action => 'controller#action'
70
+ )
71
+ end
72
+
73
+ context "when Sinatra tells us it's a static file" do
74
+ let(:env) { {'sinatra.static_file' => true} }
75
+
76
+ it "should call the original method" do
77
+ expect( router ).to receive(:route_without_appsignal)
78
+ end
79
+
80
+ it "should not instrument the request" do
81
+ expect( ActiveSupport::Notifications ).to_not receive(:instrument)
82
+ end
83
+
84
+ after { router.route!(base) }
85
+ end
86
+
87
+ context "when appsignal is not active" do
88
+ before { Appsignal.stub(:active? => false) }
89
+
90
+ it "should call the original method" do
91
+ expect( router ).to receive(:route_without_appsignal)
92
+ end
93
+
94
+ it "should not instrument the request" do
95
+ expect( ActiveSupport::Notifications ).to_not receive(:instrument)
96
+ end
97
+
98
+ after { router.route!(base) }
99
+ end
100
+
101
+ context "with a dynamic request" do
102
+ let(:transaction) do
103
+ double(
104
+ :set_http_or_background_action => nil,
105
+ :set_http_or_background_queue_start => nil,
106
+ :set_metadata => nil,
107
+ :set_action => nil,
108
+ :set_error => nil
109
+ )
110
+ end
111
+ before { Appsignal::Transaction.stub(:create => transaction) }
112
+
113
+ context "without an error" do
114
+ it "should create a transaction" do
115
+ expect( Appsignal::Transaction ).to receive(:create).with(
116
+ kind_of(String),
117
+ Appsignal::Transaction::HTTP_REQUEST,
118
+ request
119
+ ).and_return(transaction)
120
+ end
121
+
122
+ it "should call the original routing method" do
123
+ expect( router ).to receive(:route_without_appsignal)
124
+ end
125
+
126
+ it "should instrument the action" do
127
+ expect( ActiveSupport::Notifications ).to receive(:instrument).with('process_action.padrino')
128
+ end
129
+
130
+ it "should set metadata" do
131
+ expect( transaction ).to receive(:set_metadata).twice
132
+ end
133
+
134
+ it "should set the action on the transaction" do
135
+ expect( transaction ).to receive(:set_action).with('controller#action')
136
+ end
137
+
138
+ after { router.route!(base) }
139
+ end
140
+
141
+ context "with an error" do
142
+ let(:error) { VerySpecificError.new }
143
+ before { router.stub(:route_without_appsignal).and_raise(error) }
144
+
145
+ it "should add the exception to the current transaction" do
146
+ expect( transaction ).to receive(:set_error).with(error)
147
+
148
+ router.route!(base) rescue VerySpecificError
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ describe "#get_payload_action" do
155
+ before { router.stub(:settings => settings) }
156
+
157
+ context "when request is nil" do
158
+ it "should return the site" do
159
+ expect( router.get_payload_action(nil) ).to eql('TestApp')
160
+ end
161
+ end
162
+
163
+ context "when there's no route object" do
164
+ let(:request) { double(:controller => 'Controller', :action => 'action') }
165
+
166
+ it "should return the site name, controller and action" do
167
+ expect( router.get_payload_action(request) ).to eql('TestApp:Controller#action')
168
+ end
169
+
170
+ context "when there's no action" do
171
+ let(:request) { double(:controller => 'Controller', :fullpath => '/action') }
172
+
173
+ it "should return the site name, controller and fullpath" do
174
+ expect( router.get_payload_action(request) ).to eql('TestApp:Controller#/action')
175
+ end
176
+ end
177
+ end
178
+
179
+ context "when request has a route object" do
180
+ let(:request) { double }
181
+ let(:route_object) { double(:original_path => '/accounts/edit/:id') }
182
+ before { request.stub(:route_obj => route_object) }
183
+
184
+ it "should return the original path" do
185
+ expect( router.get_payload_action(request) ).to eql('TestApp:/accounts/edit/:id')
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -46,7 +46,6 @@ if rails_present?
46
46
  ENV.delete('APPSIGNAL_APP_ENV')
47
47
  end
48
48
 
49
-
50
49
  its(:env) { should == 'env_test' }
51
50
  end
52
51
  end
@@ -55,7 +54,7 @@ if rails_present?
55
54
  it "should have added the listener middleware" do
56
55
  expect( app.middleware ).to receive(:insert_before).with(
57
56
  ActionDispatch::RemoteIp,
58
- Appsignal::Rack::Listener
57
+ Appsignal::Rack::RailsInstrumentation
59
58
  )
60
59
  end
61
60
 
@@ -76,11 +75,11 @@ if rails_present?
76
75
  it "should have added the listener and JSExceptionCatcher middleware" do
77
76
  expect( app.middleware ).to receive(:insert_before).with(
78
77
  ActionDispatch::RemoteIp,
79
- Appsignal::Rack::Listener
78
+ Appsignal::Rack::RailsInstrumentation
80
79
  )
81
80
 
82
81
  expect( app.middleware ).to receive(:insert_before).with(
83
- Appsignal::Rack::Listener,
82
+ Appsignal::Rack::RailsInstrumentation,
84
83
  Appsignal::Rack::JSExceptionCatcher
85
84
  )
86
85
  end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+ require 'rake'
3
+ describe "Rack integration" do
4
+ let(:file) { File.expand_path('lib/appsignal/integrations/rake.rb') }
5
+ let(:app) { double(:current_scope => nil) }
6
+ let(:task) { Rake::Task.new('task', app) }
7
+ before do
8
+ load file
9
+ task.stub(
10
+ :name => 'task:name',
11
+ :invoke_without_appsignal => true
12
+ )
13
+ end
14
+
15
+ describe "#invoke" do
16
+ before { Appsignal.stub(:active? => true) }
17
+
18
+ it "should call with appsignal monitoring" do
19
+ expect( task ).to receive(:invoke_with_appsignal).with(['foo'])
20
+ end
21
+
22
+ context "when not active" do
23
+ before { Appsignal.stub(:active? => false) }
24
+
25
+ it "should NOT call with appsignal monitoring" do
26
+ expect( task ).to_not receive(:invoke_with_appsignal).with(['foo'])
27
+ end
28
+
29
+ it "should call the original task" do
30
+ expect( task ).to receive(:invoke_without_appsignal).with(['foo'])
31
+ end
32
+ end
33
+
34
+ after { task.invoke(['foo']) }
35
+ end
36
+
37
+ describe "#invoke_with_appsignal" do
38
+ context "with transaction" do
39
+ let!(:transaction) { regular_transaction }
40
+ let!(:agent) { double('Agent', :send_queue => true) }
41
+ before do
42
+ SecureRandom.stub(:uuid => '123')
43
+ Appsignal::Transaction.stub(:create => transaction)
44
+ Appsignal.stub(:agent => agent, :active? => true)
45
+ end
46
+
47
+ it "should call the original task" do
48
+ expect( task ).to receive(:invoke_without_appsignal).with('foo')
49
+ end
50
+
51
+ context "with an exception" do
52
+ let(:exception) { VerySpecificError.new }
53
+
54
+ before do
55
+ task.stub(:invoke_without_appsignal).and_raise(exception)
56
+ end
57
+
58
+ it "should create a transaction" do
59
+ expect( Appsignal::Transaction ).to receive(:create).with(
60
+ '123',
61
+ Appsignal::Transaction::BACKGROUND_JOB,
62
+ kind_of(Appsignal::Transaction::GenericRequest)
63
+ )
64
+ end
65
+
66
+ it "should set the action on the transaction" do
67
+ expect( transaction ).to receive(:set_action).with('task:name')
68
+ end
69
+
70
+ it "should add the exception to the transaction" do
71
+ expect( transaction ).to receive(:set_error).with(exception)
72
+ end
73
+ end
74
+ end
75
+
76
+ after { task.invoke_with_appsignal('foo') rescue VerySpecificError }
77
+ end
78
+ end
@@ -25,7 +25,7 @@ describe "Resque integration" do
25
25
  end
26
26
 
27
27
  describe :around_perform_resque_plugin do
28
- let(:transaction) { Appsignal::Transaction.new('1', {}) }
28
+ let(:transaction) { background_job_transaction }
29
29
  let(:job) { Resque::Job }
30
30
  let(:invoked_job) { nil }
31
31
  before do
@@ -55,7 +55,7 @@ describe "Resque integration" do
55
55
 
56
56
  context "with exception" do
57
57
  it "should set the exception" do
58
- transaction.should_receive(:set_error)
58
+ Appsignal::Transaction.any_instance.should_receive(:set_error)
59
59
  end
60
60
 
61
61
  after do
@@ -10,15 +10,14 @@ describe "Sequel integration", if: sequel_present? do
10
10
  end
11
11
 
12
12
  context "with Sequel" do
13
- before { Appsignal::Transaction.create('uuid', 'test') }
13
+ before { Appsignal::Transaction.create('uuid', Appsignal::Transaction::HTTP_REQUEST, 'test') }
14
14
 
15
15
  it "should instrument queries" do
16
16
  expect( Appsignal::Extension ).to receive(:start_event)
17
17
  .at_least(:once)
18
- .with('uuid')
19
18
  expect( Appsignal::Extension ).to receive(:finish_event)
20
19
  .at_least(:once)
21
- .with('uuid', "sql.sequel", "", "")
20
+ .with(kind_of(Integer), "sql.sequel", "", "")
22
21
 
23
22
  db['SELECT 1'].all
24
23
  end
@@ -15,7 +15,7 @@ describe "Sidekiq integration" do
15
15
 
16
16
  let(:worker) { double }
17
17
  let(:queue) { double }
18
- let(:current_transaction) { Appsignal::Transaction.create(SecureRandom.uuid, {}) }
18
+ let(:current_transaction) { background_job_transaction }
19
19
  let(:item) {{
20
20
  'class' => 'TestClass',
21
21
  'retry_count' => 0,
@@ -47,6 +47,23 @@ 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
+
50
67
  after do
51
68
  Timecop.freeze(Time.parse('01-01-2001 10:01:00UTC')) do
52
69
  Appsignal::Integrations::SidekiqPlugin.new.call(worker, item, queue) do
@@ -59,7 +76,7 @@ describe "Sidekiq integration" do
59
76
  context "with an erroring call" do
60
77
  let(:error) { VerySpecificError.new('the roof') }
61
78
  it "should add the exception to appsignal" do
62
- current_transaction.should_receive(:set_error).with(error)
79
+ Appsignal::Transaction.any_instance.should_receive(:set_error).with(error)
63
80
  end
64
81
 
65
82
  after do
@@ -104,11 +121,11 @@ describe "Sidekiq integration" do
104
121
 
105
122
  describe "#truncate" do
106
123
  let(:very_long_text) do
107
- "a" * 200
124
+ "a" * 400
108
125
  end
109
126
 
110
- it "should truncate the text to 100 chars max" do
111
- plugin.truncate(very_long_text).should == "#{'a' * 97}..."
127
+ it "should truncate the text to 200 chars max" do
128
+ plugin.truncate(very_long_text).should == "#{'a' * 197}..."
112
129
  end
113
130
  end
114
131
 
@@ -22,12 +22,6 @@ if defined?(::Sinatra)
22
22
  it { should be_a(Appsignal::Config) }
23
23
  end
24
24
 
25
- it "should have added the listener middleware" do
26
- Sinatra::Application.middleware.to_a.should include(
27
- [Appsignal::Rack::Listener, [], nil]
28
- )
29
- end
30
-
31
25
  it "should have added the instrumentation middleware" do
32
26
  Sinatra::Application.middleware.to_a.should include(
33
27
  [Appsignal::Rack::SinatraInstrumentation, [], nil]
@@ -20,9 +20,9 @@ describe Appsignal::JSExceptionTransaction do
20
20
 
21
21
  describe "#initialize" do
22
22
  it "should call all required methods" do
23
- expect( Appsignal::Extension ).to receive(:start_transaction).with('123abc').and_return(1)
23
+ expect( Appsignal::Extension ).to receive(:start_transaction).with('123abc', 'frontend').and_return(1)
24
24
 
25
- expect( transaction ).to receive(:set_base_data)
25
+ expect( transaction ).to receive(:set_action)
26
26
  expect( transaction ).to receive(:set_metadata)
27
27
  expect( transaction ).to receive(:set_error)
28
28
  expect( transaction ).to receive(:set_error_data)
@@ -35,14 +35,12 @@ describe Appsignal::JSExceptionTransaction do
35
35
 
36
36
  describe "#set_base_data" do
37
37
  it "should call `Appsignal::Extension.set_transaction_basedata`" do
38
- expect( Appsignal::Extension ).to receive(:set_transaction_base_data).with(
38
+ expect( Appsignal::Extension ).to receive(:set_transaction_action).with(
39
39
  kind_of(Integer),
40
- 'frontend',
41
40
  'ExceptionIncidentComponent',
42
- 0
43
41
  )
44
42
 
45
- transaction.set_base_data
43
+ transaction.set_action
46
44
  end
47
45
  end
48
46
 
@@ -6,39 +6,54 @@ class ErrorOnInspect
6
6
  end
7
7
  end
8
8
 
9
+ class ClassWithInspect
10
+ def inspect
11
+ "#<ClassWithInspect foo=\"bar\"/>"
12
+ end
13
+ end
14
+
9
15
  describe Appsignal::ParamsSanitizer do
10
16
  let(:klass) { Appsignal::ParamsSanitizer }
11
17
  let(:file) { uploaded_file }
12
18
  let(:params) do
13
19
  {
14
- :text => 'string',
15
- :file => file,
16
- :hash => {
17
- :nested_text => 'string',
20
+ :text => 'string',
21
+ :file => file,
22
+ :float => 0.0,
23
+ :bool_true => true,
24
+ :bool_false => false,
25
+ :int => 1,
26
+ :hash => {
27
+ :nested_text => 'string',
18
28
  :nested_array => [
19
29
  'something',
20
30
  'else',
21
31
  file,
22
32
  {
23
- :key => 'value',
33
+ :key => 'value',
24
34
  :file => file,
25
35
  },
26
- ErrorOnInspect.new
36
+ ErrorOnInspect.new,
37
+ ClassWithInspect.new
27
38
  ]
28
39
  }
29
40
  }
30
41
  end
31
42
  let(:sanitized_params) { klass.sanitize(params) }
32
- let(:scrubbed_params) { klass.scrub(params) }
43
+ let(:scrubbed_params) { klass.scrub(params) }
33
44
 
34
45
  describe ".sanitize!" do
35
46
  subject { params }
36
47
  before { klass.sanitize!(subject) }
37
48
 
38
49
  it { should be_instance_of Hash }
39
- its([:text]) { should == 'string' }
40
- its([:file]) { should be_instance_of String }
41
- its([:file]) { should include '::UploadedFile' }
50
+ its([:text]) { should == 'string' }
51
+ its([:file]) { should be_instance_of String }
52
+ its([:file]) { should include '::UploadedFile' }
53
+ its([:float]) { should == 0.0 }
54
+ its([:int]) { should == 1 }
55
+ its([:bool_true]) { should == 'true' }
56
+ its([:bool_false]) { should == 'false' }
42
57
 
43
58
  context "hash" do
44
59
  subject { params[:hash] }
@@ -54,7 +69,8 @@ describe Appsignal::ParamsSanitizer do
54
69
  its([1]) { should == 'else' }
55
70
  its([2]) { should be_instance_of String }
56
71
  its([2]) { should include '::UploadedFile' }
57
- its([4]) { should == '#<ErrorOnInspect/>' }
72
+ its([4]) { should == '#<ErrorOnInspect>' }
73
+ its([5]) { should == '#<ClassWithInspect>' }
58
74
 
59
75
  context "nested hash" do
60
76
  subject { params[:hash][:nested_array][3] }