appsignal 0.4.7 → 0.5.0

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 (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