appsignal 0.4.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.ruby-version +1 -0
  2. data/README.md +20 -19
  3. data/appsignal.gemspec +2 -2
  4. data/lib/appsignal.rb +41 -18
  5. data/lib/appsignal/agent.rb +28 -54
  6. data/lib/appsignal/aggregator.rb +65 -0
  7. data/lib/appsignal/aggregator/post_processor.rb +27 -0
  8. data/lib/appsignal/config.rb +9 -4
  9. data/lib/appsignal/listener.rb +30 -0
  10. data/lib/appsignal/middleware.rb +4 -30
  11. data/lib/appsignal/middleware/action_view_sanitizer.rb +21 -0
  12. data/lib/appsignal/middleware/active_record_sanitizer.rb +60 -0
  13. data/lib/appsignal/middleware/chain.rb +99 -0
  14. data/lib/appsignal/middleware/delete_blanks.rb +12 -0
  15. data/lib/appsignal/railtie.rb +9 -1
  16. data/lib/appsignal/to_appsignal_hash.rb +23 -0
  17. data/lib/appsignal/transaction.rb +72 -16
  18. data/lib/appsignal/transaction/params_sanitizer.rb +91 -13
  19. data/lib/appsignal/transaction/transaction_formatter.rb +32 -68
  20. data/lib/appsignal/version.rb +1 -1
  21. data/spec/appsignal/agent_spec.rb +46 -156
  22. data/spec/appsignal/aggregator/post_processor_spec.rb +84 -0
  23. data/spec/appsignal/aggregator_spec.rb +182 -0
  24. data/spec/appsignal/inactive_railtie_spec.rb +2 -1
  25. data/spec/appsignal/{middleware_spec.rb → listener_spec.rb} +2 -2
  26. data/spec/appsignal/middleware/action_view_sanitizer_spec.rb +27 -0
  27. data/spec/appsignal/middleware/active_record_sanitizer_spec.rb +201 -0
  28. data/spec/appsignal/middleware/chain_spec.rb +168 -0
  29. data/spec/appsignal/middleware/delete_blanks_spec.rb +37 -0
  30. data/spec/appsignal/railtie_spec.rb +47 -34
  31. data/spec/appsignal/to_appsignal_hash_spec.rb +29 -0
  32. data/spec/appsignal/transaction/params_sanitizer_spec.rb +141 -36
  33. data/spec/appsignal/transaction/transaction_formatter_spec.rb +60 -155
  34. data/spec/appsignal/transaction_spec.rb +186 -53
  35. data/spec/appsignal/transmitter_spec.rb +11 -6
  36. data/spec/appsignal_spec.rb +33 -0
  37. data/spec/spec_helper.rb +9 -62
  38. data/spec/support/helpers/notification_helpers.rb +30 -0
  39. data/spec/support/helpers/transaction_helpers.rb +64 -0
  40. metadata +74 -63
  41. data/.rvmrc +0 -1
  42. data/lib/appsignal/transaction/faulty_request_formatter.rb +0 -30
  43. data/lib/appsignal/transaction/regular_request_formatter.rb +0 -11
  44. data/lib/appsignal/transaction/slow_request_formatter.rb +0 -34
  45. data/spec/appsignal/transaction/faulty_request_formatter_spec.rb +0 -49
  46. data/spec/appsignal/transaction/regular_request_formatter_spec.rb +0 -14
  47. data/spec/appsignal/transaction/slow_request_formatter_spec.rb +0 -76
@@ -39,18 +39,17 @@ describe Appsignal::Transaction do
39
39
  end
40
40
 
41
41
  describe '#set_process_action_event' do
42
- let(:process_action_event) { create_process_action_event }
42
+ let(:process_action_event) { notification_event }
43
43
 
44
44
  it 'should add a process action event' do
45
45
  transaction.set_process_action_event(process_action_event)
46
46
 
47
47
  transaction.process_action_event.should == process_action_event
48
- transaction.action.should == 'BlogPostsController#show'
49
48
  end
50
49
  end
51
50
 
52
51
  describe '#add_event' do
53
- let(:event) {stub(:name => 'test') }
52
+ let(:event) { mock(:event, :name => 'test') }
54
53
 
55
54
  it 'should add an event' do
56
55
  expect {
@@ -59,29 +58,54 @@ describe Appsignal::Transaction do
59
58
  end
60
59
  end
61
60
 
62
- describe '#add_exception' do
63
- let(:exception) {stub(:name => 'test') }
61
+ context "using exceptions" do
62
+ let(:exception) { mock(:exception, :name => 'test') }
64
63
 
65
- it 'should add an exception' do
66
- expect {
67
- transaction.add_exception(exception)
68
- }.to change(transaction, :exception).to(exception)
64
+ describe '#add_exception' do
65
+ it 'should add an exception' do
66
+ expect {
67
+ transaction.add_exception(exception)
68
+ }.to change(transaction, :exception).to(exception)
69
+ end
70
+ end
71
+
72
+ describe "#exception?" do
73
+ subject { transaction.exception? }
74
+
75
+ context "without an exception" do
76
+ it { should be_false }
77
+ end
78
+
79
+ context "without an exception" do
80
+ before { transaction.add_exception(exception) }
81
+
82
+ it { should be_true }
83
+ end
69
84
  end
70
85
  end
71
86
 
72
87
  describe '#slow_request?' do
73
- let(:duration) { 199 }
88
+ let(:start) { Time.now }
74
89
  subject { transaction.slow_request? }
75
- before { transaction.set_process_action_event(
76
- stub(:duration => duration, :payload => {})
77
- ) }
78
90
 
79
- it { should be_false }
91
+ context "duration" do
92
+ before do
93
+ transaction.set_process_action_event(
94
+ notification_event(:start => start, :ending => start + duration)
95
+ )
96
+ end
80
97
 
81
- context "when the request took too long" do
82
- let(:duration) { 200 }
98
+ context "when it reasonably fast" do
99
+ let(:duration) { 0.199 } # in seconds
83
100
 
84
- it { should be_true }
101
+ it { should be_false }
102
+ end
103
+
104
+ context "when the request took too long" do
105
+ let(:duration) { 0.200 } # in seconds
106
+
107
+ it { should be_true }
108
+ end
85
109
  end
86
110
 
87
111
  context "when process action event is empty" do
@@ -91,88 +115,148 @@ describe Appsignal::Transaction do
91
115
  end
92
116
 
93
117
  context "when process action event does not have a payload" do
94
- before { transaction.set_process_action_event(stub(:payload => nil)) }
118
+ let(:event) { notification_event }
119
+ before do
120
+ event.instance_variable_set(:@payload, nil)
121
+ transaction.set_process_action_event(event)
122
+ end
95
123
 
96
124
  it { should be_false }
97
125
  end
98
126
  end
99
127
 
100
- describe "#clear_payload_and_events!" do
128
+ describe "#slower?" do
129
+ context "comparing to a slower transaction" do
130
+ subject { regular_transaction.slower?(slow_transaction) }
131
+
132
+ it { should be_false }
133
+ end
134
+
135
+ context "comparing to a faster transaction" do
136
+ subject { slow_transaction.slower?(regular_transaction) }
137
+
138
+ it { should be_true }
139
+ end
140
+ end
141
+
142
+ describe "#truncate!" do
101
143
  subject { slow_transaction }
102
144
 
103
145
  it "should clear the process action payload and events" do
104
- subject.clear_payload_and_events!
146
+ subject.truncate!
105
147
 
106
148
  subject.process_action_event.payload.should be_empty
107
149
  subject.events.should be_empty
108
150
  end
109
151
  end
110
152
 
111
- describe '#to_hash' do
112
- subject { transaction.to_hash }
113
- before { transaction.stub(:exception? => false) }
153
+ describe "#convert_values_to_primitives!" do
154
+ let(:transaction) { slow_transaction }
155
+ let(:action_event_payload) { transaction.process_action_event.payload }
156
+ let(:event_payload) { transaction.events.first.payload }
157
+ let(:weird_class) { Class.new }
158
+
159
+ context "with values that need to be converted" do
160
+ context "process action event payload" do
161
+ subject { action_event_payload }
162
+ before do
163
+ action_event_payload.clear
164
+ action_event_payload.
165
+ merge!(:model => {:with => [:weird, weird_class]})
166
+ transaction.convert_values_to_primitives!
167
+ end
168
+
169
+ it { should == {:model => {:with => [:weird, weird_class.inspect]}} }
170
+ end
114
171
 
115
- context "with an exception request" do
116
- before { transaction.stub(:exception? => true) }
172
+ context "payload of events" do
173
+ subject { event_payload }
174
+ before do
175
+ event_payload.clear
176
+ event_payload.merge!(:weird => weird_class)
177
+ transaction.convert_values_to_primitives!
178
+ end
117
179
 
118
- it "calls TransactionFormatter.faulty with self" do
119
- Appsignal::TransactionFormatter.should_receive(:faulty).
120
- with(transaction).and_return({})
180
+ it { should == {:weird => weird_class.inspect} }
121
181
  end
122
182
  end
123
183
 
124
- context "with a slow request" do
125
- before { transaction.stub(:slow_request? => true) }
184
+ context "without values that need to be converted" do
185
+ subject { transaction.convert_values_to_primitives! }
186
+
187
+ it "doesn't change the action event payload" do
188
+ before = action_event_payload.dup
189
+ subject
190
+ action_event_payload.should == before
191
+ end
126
192
 
127
- it "calls TransactionFormatter.slow with self" do
128
- Appsignal::TransactionFormatter.should_receive(:slow).
129
- with(transaction).and_return({})
193
+ it " doesn't change the event payloads" do
194
+ before = event_payload.dup
195
+ subject
196
+ event_payload.should == before
130
197
  end
131
198
  end
199
+ end
132
200
 
133
- context "with a regular request" do
134
- before { transaction.stub(:slow_request? => false) }
201
+ describe "#type" do
202
+ context "with a regular transaction" do
203
+ subject { regular_transaction.type }
135
204
 
136
- it "calls TransactionFormatter.slow with self" do
137
- Appsignal::TransactionFormatter.should_receive(:regular).
138
- with(transaction).and_return({})
139
- end
205
+ it { should == :regular_request }
206
+ end
207
+
208
+ context "with a slow transaction" do
209
+ subject { slow_transaction.type }
210
+
211
+ it { should == :slow_request }
140
212
  end
141
213
 
142
- after { subject }
214
+ context "with an exception transaction" do
215
+ subject { transaction_with_exception.type }
216
+
217
+ it { should == :exception }
218
+ end
219
+ end
220
+
221
+ describe '#to_hash' do
222
+ subject { transaction.to_hash }
223
+
224
+ it { should be_instance_of Hash }
143
225
  end
144
226
 
145
227
  describe '#complete!' do
146
- before { transaction.set_process_action_event(
147
- stub(:duration => 199, :time => Time.now, :payload => nil)
148
- ) }
228
+ let(:event) { mock(:event) }
229
+ before { transaction.set_process_action_event(notification_event) }
149
230
 
150
231
  it 'should remove transaction from the list' do
151
- expect {
152
- transaction.complete!
153
- }.to change(Appsignal.transactions, :length).by(-1)
232
+ expect { transaction.complete! }.
233
+ to change(Appsignal.transactions, :length).by(-1)
154
234
  end
155
235
 
156
- context 'calling the appsignal agent' do
157
- context 'without events and exception' do
236
+ context 'enqueueing' do
237
+ context 'sanity check' do
238
+ specify { Appsignal.should respond_to(:enqueue) }
239
+ end
240
+
241
+ context 'without events and without exception' do
158
242
  it 'should add transaction to the agent' do
159
- Appsignal.agent.should_receive(:add_to_queue).with(transaction)
243
+ Appsignal.should_receive(:enqueue).with(transaction)
160
244
  end
161
245
  end
162
246
 
163
247
  context 'with events' do
164
- before { transaction.add_event(stub) }
248
+ before { transaction.add_event(event) }
165
249
 
166
250
  it 'should add transaction to the agent' do
167
- Appsignal.agent.should_receive(:add_to_queue).with(transaction)
251
+ Appsignal.should_receive(:enqueue).with(transaction)
168
252
  end
169
253
  end
170
254
 
171
255
  context 'with exception' do
172
- before { transaction.add_exception(stub) }
256
+ before { transaction.add_exception(event) }
173
257
 
174
258
  it 'should add transaction to the agent' do
175
- Appsignal.agent.should_receive(:add_to_queue).with(transaction)
259
+ Appsignal.should_receive(:enqueue).with(transaction)
176
260
  end
177
261
  end
178
262
 
@@ -187,5 +271,54 @@ describe Appsignal::Transaction do
187
271
  end
188
272
  end
189
273
  end
274
+
275
+ # protected
276
+
277
+ describe '#add_sanitized_context!' do
278
+ subject { transaction.send(:add_sanitized_context!) }
279
+
280
+ it "delegates to sanitize_environment! and sanitize_session_data!" do
281
+ transaction.should_receive(:sanitize_environment!)
282
+ transaction.should_receive(:sanitize_session_data!)
283
+ subject
284
+ end
285
+
286
+ specify { expect { subject }.to change(transaction, :env).to(nil) }
287
+ end
288
+
289
+ describe '#sanitize_environment!' do
290
+ let(:whitelisted_keys) { Appsignal::Transaction::ENV_METHODS }
291
+ let(:transaction) { Appsignal::Transaction.create('1', env) }
292
+ let(:env) do
293
+ Hash.new.tap do |hash|
294
+ whitelisted_keys.each { |o| hash[o] = 1 } # use all whitelisted keys
295
+ hash[:not_whitelisted] = 'I will be sanitized'
296
+ end
297
+ end
298
+ subject { transaction.sanitized_environment }
299
+ before { transaction.send(:sanitize_environment!) }
300
+
301
+ its(:keys) { should == whitelisted_keys }
302
+ end
303
+
304
+ describe '#sanitize_session_data!' do
305
+ subject { transaction.send(:sanitize_session_data!) }
306
+ before do
307
+ transaction.should respond_to(:request)
308
+ transaction.stub_chain(:request, :session => :foo)
309
+ transaction.stub_chain(:request, :fullpath => :bar)
310
+ end
311
+
312
+ it "passes the session data into the params sanitizer" do
313
+ Appsignal::ParamsSanitizer.should_receive(:sanitize).with(:foo).
314
+ and_return(:sanitized_foo)
315
+ subject
316
+ transaction.sanitized_session_data.should == :sanitized_foo
317
+ end
318
+
319
+ it "sets the fullpath of the request" do
320
+ expect { subject }.to change(transaction, :fullpath).to(:bar)
321
+ end
322
+ end
190
323
  end
191
324
  end
@@ -10,13 +10,16 @@ describe Appsignal::Transmitter do
10
10
  describe "#uri" do
11
11
  it "returns the uri" do
12
12
  Socket.stub(:gethostname => 'app1.local')
13
- subject.uri.should ==
14
- URI("http://www.80beans.com/action?api_key=the_api_key&hostname=app1.local&gem_version=#{Appsignal::VERSION}")
13
+ subject.uri.should == URI(
14
+ "http://www.80beans.com/action?api_key=the_api_key&"\
15
+ "hostname=app1.local&gem_version=#{Appsignal::VERSION}"
16
+ )
15
17
  end
16
18
  end
17
19
 
18
20
  describe "#transmit" do
19
- let(:http_client) { stub(:request => stub(:code => '200')) }
21
+ let(:response) { mock(:response, :code => '200') }
22
+ let(:http_client) { mock(:request, :request => response) }
20
23
  before { instance.stub(:http_client => http_client) }
21
24
 
22
25
  subject { instance.transmit(:shipit => :payload) }
@@ -26,13 +29,15 @@ describe Appsignal::Transmitter do
26
29
 
27
30
  describe "#http_post" do
28
31
  it "calls Net::HTTP.post_form with the correct params" do
29
- post = stub
30
- post.should_receive(:[]=).with('Content-Type', 'application/json; charset=UTF-8')
32
+ post = mock(:post)
33
+ post.should_receive(:[]=).
34
+ with('Content-Type', 'application/json; charset=UTF-8')
31
35
  post.should_receive(:body=).with("{\"the\":\"payload\"}")
32
36
  Socket.stub(:gethostname => 'app1.local')
33
37
 
34
38
  Net::HTTP::Post.should_receive(:new).with(
35
- "/action?api_key=the_api_key&hostname=app1.local&gem_version=#{Appsignal::VERSION}"
39
+ "/action?api_key=the_api_key&hostname=app1.local&"\
40
+ "gem_version=#{Appsignal::VERSION}"
36
41
  ).and_return(post)
37
42
  instance.send(:http_post, :the => :payload)
38
43
  end
@@ -3,6 +3,17 @@ require 'spec_helper'
3
3
  describe Appsignal do
4
4
  it { should respond_to :subscriber }
5
5
 
6
+ describe ".enqueue" do
7
+ let(:transaction) { regular_transaction }
8
+ subject { Appsignal.enqueue(transaction) }
9
+
10
+ it "forwards the call to the agent" do
11
+ Appsignal.agent.should respond_to(:enqueue)
12
+ Appsignal.agent.should_receive(:enqueue).with(transaction)
13
+ subject
14
+ end
15
+ end
16
+
6
17
  describe ".transactions" do
7
18
  subject { Appsignal.transactions }
8
19
 
@@ -42,6 +53,28 @@ describe Appsignal do
42
53
  end
43
54
  end
44
55
 
56
+ describe ".post_processing_middleware" do
57
+ before { Appsignal.instance_variable_set(:@post_processing_chain, nil) }
58
+
59
+ it "returns the default middleware stack" do
60
+ Appsignal::PostProcessor.should_receive(:default_middleware)
61
+ Appsignal.post_processing_middleware
62
+ end
63
+
64
+ it "returns a chain when called without a block" do
65
+ instance = Appsignal.post_processing_middleware
66
+ instance.should be_an_instance_of Appsignal::Middleware::Chain
67
+ end
68
+
69
+ context "when passing a block" do
70
+ it "yields an appsignal middleware chain" do
71
+ Appsignal.post_processing_middleware do |o|
72
+ o.should be_an_instance_of Appsignal::Middleware::Chain
73
+ end
74
+ end
75
+ end
76
+ end
77
+
45
78
  describe '.active?' do
46
79
  subject { Appsignal.active? }
47
80
 
@@ -1,4 +1,5 @@
1
1
  require 'rspec'
2
+ require 'pry'
2
3
  require 'rails'
3
4
  require 'action_controller/railtie'
4
5
 
@@ -15,71 +16,17 @@ module MyApp
15
16
  end
16
17
  end
17
18
 
18
- require 'appsignal'
19
-
20
- RSpec.configure do |config|
19
+ def log_file
20
+ File.join(File.dirname(__FILE__), '../log/appsignal.log')
21
21
  end
22
22
 
23
- def transaction_with_exception
24
- appsignal_transaction.tap do |o|
25
- begin
26
- raise ArgumentError, 'oh no'
27
- rescue ArgumentError => exception
28
- env = {}
29
- o.add_exception(
30
- Appsignal::ExceptionNotification.new(env, exception)
31
- )
32
- end
33
- end
34
- end
35
-
36
- def regular_transaction
37
- appsignal_transaction(:process_action_event => create_process_action_event)
38
- end
23
+ require 'appsignal'
39
24
 
40
- def slow_transaction
41
- appsignal_transaction(
42
- :process_action_event => create_process_action_event(nil, nil, Time.parse('01-01-2001 10:01:00'))
43
- )
44
- end
25
+ RSpec.configure do |config|
26
+ config.include TransactionHelpers
27
+ config.include NotificationHelpers
45
28
 
46
- def appsignal_transaction(args = {})
47
- process_action_event = args.delete(:process_action_event)
48
- events = args.delete(:events) || [create_process_action_event(name='query.mongoid')]
49
- exception = args.delete(:exception)
50
- Appsignal::Transaction.create(
51
- '1',
52
- {
53
- 'HTTP_USER_AGENT' => 'IE6',
54
- 'SERVER_NAME' => 'localhost',
55
- 'action_dispatch.routes' => 'not_available'
56
- }.merge(args)
57
- ).tap do |o|
58
- o.set_process_action_event(process_action_event)
59
- o.add_exception(exception)
60
- events.each { |event| o.add_event(event) }
29
+ config.before :all do
30
+ FileUtils.rm(log_file) if File.exists?(log_file)
61
31
  end
62
32
  end
63
-
64
- def create_process_action_event(name=nil, start=nil, ending=nil, tid=nil, payload=nil)
65
- ActiveSupport::Notifications::Event.new(
66
- name || 'process_action.action_controller',
67
- start || Time.parse("01-01-2001 10:00:00"),
68
- ending || Time.parse("01-01-2001 10:00:01"),
69
- tid || '1',
70
- payload || create_payload
71
- )
72
- end
73
-
74
- def create_payload(args = {})
75
- {
76
- :path => '/blog',
77
- :action => 'show',
78
- :controller => 'BlogPostsController',
79
- :request_format => 'html',
80
- :request_method => "GET",
81
- :status => '200',
82
- :view_runtime => 500,
83
- :db_runtime => 500
84
- }.merge(args)
85
- end