appsignal 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +19 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +30 -0
  4. data/Gemfile +3 -0
  5. data/LICENCE +20 -0
  6. data/README.md +48 -0
  7. data/Rakefile +52 -0
  8. data/appsignal.gemspec +33 -0
  9. data/bin/appsignal +13 -0
  10. data/config/appsignal.yml +8 -0
  11. data/gemfiles/3.0.gemfile +16 -0
  12. data/gemfiles/3.1.gemfile +16 -0
  13. data/gemfiles/3.2.gemfile +16 -0
  14. data/gemfiles/edge.gemfile +16 -0
  15. data/lib/appsignal.rb +45 -0
  16. data/lib/appsignal/agent.rb +104 -0
  17. data/lib/appsignal/auth_check.rb +19 -0
  18. data/lib/appsignal/capistrano.rb +41 -0
  19. data/lib/appsignal/cli.rb +118 -0
  20. data/lib/appsignal/config.rb +30 -0
  21. data/lib/appsignal/exception_notification.rb +25 -0
  22. data/lib/appsignal/marker.rb +35 -0
  23. data/lib/appsignal/middleware.rb +30 -0
  24. data/lib/appsignal/railtie.rb +19 -0
  25. data/lib/appsignal/transaction.rb +77 -0
  26. data/lib/appsignal/transaction/faulty_request_formatter.rb +30 -0
  27. data/lib/appsignal/transaction/params_sanitizer.rb +36 -0
  28. data/lib/appsignal/transaction/regular_request_formatter.rb +11 -0
  29. data/lib/appsignal/transaction/slow_request_formatter.rb +34 -0
  30. data/lib/appsignal/transaction/transaction_formatter.rb +93 -0
  31. data/lib/appsignal/transmitter.rb +53 -0
  32. data/lib/appsignal/version.rb +3 -0
  33. data/lib/generators/appsignal/USAGE +8 -0
  34. data/lib/generators/appsignal/appsignal_generator.rb +70 -0
  35. data/lib/generators/appsignal/templates/appsignal.yml +4 -0
  36. data/log/.gitkeep +0 -0
  37. data/resources/cacert.pem +3849 -0
  38. data/spec/appsignal/agent_spec.rb +259 -0
  39. data/spec/appsignal/auth_check_spec.rb +36 -0
  40. data/spec/appsignal/capistrano_spec.rb +81 -0
  41. data/spec/appsignal/cli_spec.rb +124 -0
  42. data/spec/appsignal/config_spec.rb +40 -0
  43. data/spec/appsignal/exception_notification_spec.rb +12 -0
  44. data/spec/appsignal/inactive_railtie_spec.rb +30 -0
  45. data/spec/appsignal/marker_spec.rb +83 -0
  46. data/spec/appsignal/middleware_spec.rb +73 -0
  47. data/spec/appsignal/railtie_spec.rb +54 -0
  48. data/spec/appsignal/transaction/faulty_request_formatter_spec.rb +49 -0
  49. data/spec/appsignal/transaction/params_sanitizer_spec.rb +68 -0
  50. data/spec/appsignal/transaction/regular_request_formatter_spec.rb +14 -0
  51. data/spec/appsignal/transaction/slow_request_formatter_spec.rb +76 -0
  52. data/spec/appsignal/transaction/transaction_formatter_spec.rb +178 -0
  53. data/spec/appsignal/transaction_spec.rb +191 -0
  54. data/spec/appsignal/transmitter_spec.rb +64 -0
  55. data/spec/appsignal_spec.rb +66 -0
  56. data/spec/generators/appsignal/appsignal_generator_spec.rb +222 -0
  57. data/spec/spec_helper.rb +85 -0
  58. data/spec/support/delegate_matcher.rb +39 -0
  59. metadata +247 -0
@@ -0,0 +1,178 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::TransactionFormatter do
4
+ let(:klass) { Appsignal::TransactionFormatter }
5
+ let(:formatter) { klass.new(transaction) }
6
+ let(:transaction) do
7
+ Appsignal::Transaction.create('1', {
8
+ 'HTTP_USER_AGENT' => 'IE6',
9
+ 'SERVER_NAME' => 'localhost',
10
+ 'action_dispatch.routes' => 'not_available'
11
+ })
12
+ end
13
+ subject { formatter }
14
+
15
+ describe ".regular" do
16
+ subject { klass.regular(transaction) }
17
+ it { should be_a klass::RegularRequestFormatter }
18
+ end
19
+
20
+ describe ".slow" do
21
+ subject { klass.slow(transaction) }
22
+ it { klass.slow(transaction).should be_a klass::SlowRequestFormatter }
23
+ end
24
+
25
+ describe ".faulty" do
26
+ subject { klass.faulty(transaction) }
27
+ it { should be_a klass::FaultyRequestFormatter }
28
+ end
29
+
30
+ context "a new formatter" do
31
+ describe "#to_hash" do
32
+ subject { formatter.to_hash }
33
+ before { formatter.stub(:action => :foo, :formatted_process_action_event => :bar) }
34
+
35
+ it 'returns a formatted hash of the transaction data' do
36
+ should == {
37
+ :request_id => '1',
38
+ :action => :foo,
39
+ :log_entry => :bar,
40
+ :failed => false
41
+ }
42
+ end
43
+ end
44
+ end
45
+
46
+ # protected
47
+
48
+ it { should delegate(:id).to(:transaction) }
49
+ it { should delegate(:events).to(:transaction) }
50
+ it { should delegate(:exception).to(:transaction) }
51
+ it { should delegate(:exception?).to(:transaction) }
52
+ it { should delegate(:env).to(:transaction) }
53
+ it { should delegate(:request).to(:transaction) }
54
+ it { should delegate(:process_action_event).to(:transaction) }
55
+ it { should delegate(:action).to(:transaction) }
56
+
57
+ it { should delegate(:payload).to(:process_action_event) }
58
+
59
+ context "a new formatter" do
60
+ describe "#formatted_process_action_event" do
61
+ subject { formatter.send(:formatted_process_action_event) }
62
+
63
+ it "calls basic_process_action_event" do
64
+ formatter.should_receive(:basic_process_action_event)
65
+ subject
66
+ end
67
+
68
+ context "with actual process action event data" do
69
+ before { transaction.set_process_action_event(create_process_action_event) }
70
+
71
+ it { should be_a Hash }
72
+
73
+ it "merges formatted_payload on the basic_process_action_event" do
74
+ subject[:duration].should == 1000.0
75
+ subject[:action].should == 'BlogPostsController#show'
76
+ end
77
+ end
78
+
79
+ context "without any process action event data" do
80
+ it { should be_a Hash }
81
+
82
+ it "does not merge formatted_payload onto the basic_process_action_event" do
83
+ subject.keys.should_not include :duration
84
+ subject.keys.should_not include :action
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#basic_process_action_event" do
90
+ subject { formatter.send(:basic_process_action_event) }
91
+ before do
92
+ transaction.stub(:request => mock(
93
+ :fullpath => '/blog',
94
+ :session => {:current_user => 1})
95
+ )
96
+ end
97
+
98
+ it { should == {
99
+ :path => '/blog',
100
+ :kind => 'http_request'
101
+ } }
102
+
103
+ it "has no environment key" do
104
+ subject[:environment].should be_nil
105
+ end
106
+
107
+ it "has no session_data key" do
108
+ subject[:session_data].should be_nil
109
+ end
110
+ end
111
+
112
+ describe "#formatted_payload" do
113
+ let(:start_time) { Time.at(2.71828182) }
114
+ let(:end_time) { Time.at(3.141592654) }
115
+ subject { formatter.send(:formatted_payload) }
116
+ before do
117
+ transaction.stub(:sanitized_event_payload => {})
118
+ transaction.set_process_action_event(mock(
119
+ :name => 'name',
120
+ :duration => 2,
121
+ :time => start_time,
122
+ :end => end_time,
123
+ :payload => {
124
+ :controller => 'controller',
125
+ :action => 'action'
126
+ }
127
+ ))
128
+ end
129
+
130
+ it { should == {
131
+ :action => 'controller#action',
132
+ :controller => 'controller', # DEBUG this should no longer be here now
133
+ :duration => 2,
134
+ :time => start_time.to_f,
135
+ :end => end_time.to_f
136
+ } }
137
+ end
138
+
139
+ describe "#sanitized_event_payload" do
140
+ subject do
141
+ formatter.send(:sanitized_event_payload, double(:payload => {:key => :sensitive}))
142
+ end
143
+
144
+ it "calls Appsignal event payload sanitizer" do
145
+ Appsignal.should_receive(:event_payload_sanitizer).and_return(
146
+ proc do |event|
147
+ event.payload[:key] = 'censored'
148
+ event.payload
149
+ end
150
+ )
151
+ subject.should == {:key => 'censored'}
152
+ end
153
+
154
+ it "calls params sanitizer" do
155
+ Appsignal::ParamsSanitizer.should_receive(:sanitize).and_return(
156
+ :key => 'sensitive'
157
+ )
158
+ subject.should == {:key => 'sensitive'}
159
+ end
160
+ end
161
+
162
+ describe "#filtered_environment" do
163
+ subject { formatter.send(:filtered_environment) }
164
+
165
+ it "should have a SERVER_NAME" do
166
+ subject['SERVER_NAME'].should == 'localhost'
167
+ end
168
+
169
+ it "should have a HTTP_USER_AGENT" do
170
+ subject['HTTP_USER_AGENT'].should == 'IE6'
171
+ end
172
+
173
+ it "should not have a action_dispatch.routes" do
174
+ subject.should_not have_key 'action_dispatch.routes'
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Transaction do
4
+ describe '.create' do
5
+ before { Appsignal::Transaction.create('1', {}) }
6
+
7
+ it 'should add the id to the thread' do
8
+ Thread.current[:appsignal_transaction_id].should == '1'
9
+ end
10
+
11
+ it 'should add the transaction to the list' do
12
+ Appsignal.transactions['1'].should be_a Appsignal::Transaction
13
+ end
14
+ end
15
+
16
+ describe '.current' do
17
+ let(:transaction) { Appsignal::Transaction.create('1', {}) }
18
+ before { transaction }
19
+ subject { Appsignal::Transaction.current }
20
+
21
+ it 'should return the correct transaction' do
22
+ should eq transaction
23
+ end
24
+ end
25
+
26
+ describe 'transaction instance' do
27
+ let(:transaction) do
28
+ Appsignal::Transaction.create('1', {
29
+ 'HTTP_USER_AGENT' => 'IE6',
30
+ 'SERVER_NAME' => 'localhost',
31
+ 'action_dispatch.routes' => 'not_available'
32
+ })
33
+ end
34
+
35
+ describe '#request' do
36
+ subject { transaction.request }
37
+
38
+ it { should be_a ActionDispatch::Request }
39
+ end
40
+
41
+ describe '#set_process_action_event' do
42
+ let(:process_action_event) { create_process_action_event }
43
+
44
+ it 'should add a process action event' do
45
+ transaction.set_process_action_event(process_action_event)
46
+
47
+ transaction.process_action_event.should == process_action_event
48
+ transaction.action.should == 'BlogPostsController#show'
49
+ end
50
+ end
51
+
52
+ describe '#add_event' do
53
+ let(:event) {stub(:name => 'test') }
54
+
55
+ it 'should add an event' do
56
+ expect {
57
+ transaction.add_event(event)
58
+ }.to change(transaction, :events).to([event])
59
+ end
60
+ end
61
+
62
+ describe '#add_exception' do
63
+ let(:exception) {stub(:name => 'test') }
64
+
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 '#slow_request?' do
73
+ let(:duration) { 199 }
74
+ subject { transaction.slow_request? }
75
+ before { transaction.set_process_action_event(
76
+ stub(:duration => duration, :payload => {})
77
+ ) }
78
+
79
+ it { should be_false }
80
+
81
+ context "when the request took too long" do
82
+ let(:duration) { 200 }
83
+
84
+ it { should be_true }
85
+ end
86
+
87
+ context "when process action event is empty" do
88
+ before { transaction.set_process_action_event(nil) }
89
+
90
+ it { should be_false }
91
+ end
92
+
93
+ context "when process action event does not have a payload" do
94
+ before { transaction.set_process_action_event(stub(:payload => nil)) }
95
+
96
+ it { should be_false }
97
+ end
98
+ end
99
+
100
+ describe "#clear_payload_and_events!" do
101
+ subject { slow_transaction }
102
+
103
+ it "should clear the process action payload and events" do
104
+ subject.clear_payload_and_events!
105
+
106
+ subject.process_action_event.payload.should be_empty
107
+ subject.events.should be_empty
108
+ end
109
+ end
110
+
111
+ describe '#to_hash' do
112
+ subject { transaction.to_hash }
113
+ before { transaction.stub(:exception? => false) }
114
+
115
+ context "with an exception request" do
116
+ before { transaction.stub(:exception? => true) }
117
+
118
+ it "calls TransactionFormatter.faulty with self" do
119
+ Appsignal::TransactionFormatter.should_receive(:faulty).
120
+ with(transaction).and_return({})
121
+ end
122
+ end
123
+
124
+ context "with a slow request" do
125
+ before { transaction.stub(:slow_request? => true) }
126
+
127
+ it "calls TransactionFormatter.slow with self" do
128
+ Appsignal::TransactionFormatter.should_receive(:slow).
129
+ with(transaction).and_return({})
130
+ end
131
+ end
132
+
133
+ context "with a regular request" do
134
+ before { transaction.stub(:slow_request? => false) }
135
+
136
+ it "calls TransactionFormatter.slow with self" do
137
+ Appsignal::TransactionFormatter.should_receive(:regular).
138
+ with(transaction).and_return({})
139
+ end
140
+ end
141
+
142
+ after { subject }
143
+ end
144
+
145
+ describe '#complete!' do
146
+ before { transaction.set_process_action_event(
147
+ stub(:duration => 199, :time => Time.now, :payload => nil)
148
+ ) }
149
+
150
+ it 'should remove transaction from the list' do
151
+ expect {
152
+ transaction.complete!
153
+ }.to change(Appsignal.transactions, :length).by(-1)
154
+ end
155
+
156
+ context 'calling the appsignal agent' do
157
+ context 'without events and exception' do
158
+ it 'should add transaction to the agent' do
159
+ Appsignal.agent.should_receive(:add_to_queue).with(transaction)
160
+ end
161
+ end
162
+
163
+ context 'with events' do
164
+ before { transaction.add_event(stub) }
165
+
166
+ it 'should add transaction to the agent' do
167
+ Appsignal.agent.should_receive(:add_to_queue).with(transaction)
168
+ end
169
+ end
170
+
171
+ context 'with exception' do
172
+ before { transaction.add_exception(stub) }
173
+
174
+ it 'should add transaction to the agent' do
175
+ Appsignal.agent.should_receive(:add_to_queue).with(transaction)
176
+ end
177
+ end
178
+
179
+ after { transaction.complete! }
180
+ end
181
+
182
+ context 'thread' do
183
+ before { transaction.complete! }
184
+
185
+ it 'should reset the thread transaction id' do
186
+ Thread.current[:appsignal_transaction_id].should be_nil
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Transmitter do
4
+ let(:_80beans_) { 'http://www.80beans.com' }
5
+ let(:action) { 'action' }
6
+ let(:klass) { Appsignal::Transmitter }
7
+ let(:instance) { klass.new(_80beans_, action, :the_api_key) }
8
+ subject { instance }
9
+
10
+ describe "#uri" do
11
+ it "returns the uri" do
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}")
15
+ end
16
+ end
17
+
18
+ describe "#transmit" do
19
+ let(:http_client) { stub(:request => stub(:code => '200')) }
20
+ before { instance.stub(:http_client => http_client) }
21
+
22
+ subject { instance.transmit(:shipit => :payload) }
23
+
24
+ it { should == '200' }
25
+ end
26
+
27
+ describe "#http_post" do
28
+ it "calls Net::HTTP.post_form with the correct params" do
29
+ post = stub
30
+ post.should_receive(:body=).with("{\"the\":\"payload\"}")
31
+ Socket.stub(:gethostname => 'app1.local')
32
+
33
+ Net::HTTP::Post.should_receive(:new).with(
34
+ "/action?api_key=the_api_key&hostname=app1.local&gem_version=#{Appsignal::VERSION}"
35
+ ).and_return(post)
36
+ instance.send(:http_post, :the => :payload)
37
+ end
38
+ end
39
+
40
+ describe "ca_file_path" do
41
+ subject { instance.send(:ca_file_path) }
42
+
43
+ it { should include('resources/cacert.pem') }
44
+ it("should exist") { File.exists?(subject).should be_true }
45
+ end
46
+
47
+ describe "#http_client" do
48
+ subject { instance.send(:http_client) }
49
+
50
+ context "with a http uri" do
51
+ it { should be_instance_of(Net::HTTP) }
52
+
53
+ its(:use_ssl?) { should be_false }
54
+ end
55
+
56
+ context "with a https uri" do
57
+ let(:instance) { klass.new('https://www.80beans.com', action, :the_api_key) }
58
+
59
+ its(:use_ssl?) { should be_true }
60
+ its(:verify_mode) { should == OpenSSL::SSL::VERIFY_PEER }
61
+ its(:ca_file) { include('resources/cacert.pem') }
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal do
4
+ it { should respond_to :subscriber }
5
+
6
+ describe ".transactions" do
7
+ subject { Appsignal.transactions }
8
+
9
+ it { should be_a Hash }
10
+ end
11
+
12
+ describe '.agent' do
13
+ subject { Appsignal.agent }
14
+
15
+ it { should be_a Appsignal::Agent }
16
+ end
17
+
18
+ describe '.logger' do
19
+ subject { Appsignal.logger }
20
+
21
+ it { should be_a Logger }
22
+ its(:level) { should == Logger::INFO }
23
+ end
24
+
25
+ describe '.config' do
26
+ subject { Appsignal.config }
27
+
28
+ it 'should return the endpoint' do
29
+ subject[:endpoint].should eq 'http://localhost:3000/1'
30
+ end
31
+
32
+ it 'should return the api key' do
33
+ subject[:api_key].should eq 'abc'
34
+ end
35
+
36
+ it 'should return ignored exceptions' do
37
+ subject[:ignore_exceptions].should eq []
38
+ end
39
+
40
+ it 'should return the slow request threshold' do
41
+ subject[:slow_request_threshold].should eq 200
42
+ end
43
+ end
44
+
45
+ describe '.active?' do
46
+ subject { Appsignal.active? }
47
+
48
+ context "without config" do
49
+ before { Appsignal.stub(:config => nil) }
50
+
51
+ it { should be_false }
52
+ end
53
+
54
+ context "with config but inactive" do
55
+ before { Appsignal.stub(:config => {:active => false}) }
56
+
57
+ it { should be_false }
58
+ end
59
+
60
+ context "with active config" do
61
+ before { Appsignal.stub(:config => {:active => true}) }
62
+
63
+ it { should be_true }
64
+ end
65
+ end
66
+ end