appsignal 0.4.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 (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