appsignal 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rvmrc +1 -0
- data/.travis.yml +30 -0
- data/Gemfile +3 -0
- data/LICENCE +20 -0
- data/README.md +48 -0
- data/Rakefile +52 -0
- data/appsignal.gemspec +33 -0
- data/bin/appsignal +13 -0
- data/config/appsignal.yml +8 -0
- data/gemfiles/3.0.gemfile +16 -0
- data/gemfiles/3.1.gemfile +16 -0
- data/gemfiles/3.2.gemfile +16 -0
- data/gemfiles/edge.gemfile +16 -0
- data/lib/appsignal.rb +45 -0
- data/lib/appsignal/agent.rb +104 -0
- data/lib/appsignal/auth_check.rb +19 -0
- data/lib/appsignal/capistrano.rb +41 -0
- data/lib/appsignal/cli.rb +118 -0
- data/lib/appsignal/config.rb +30 -0
- data/lib/appsignal/exception_notification.rb +25 -0
- data/lib/appsignal/marker.rb +35 -0
- data/lib/appsignal/middleware.rb +30 -0
- data/lib/appsignal/railtie.rb +19 -0
- data/lib/appsignal/transaction.rb +77 -0
- data/lib/appsignal/transaction/faulty_request_formatter.rb +30 -0
- data/lib/appsignal/transaction/params_sanitizer.rb +36 -0
- data/lib/appsignal/transaction/regular_request_formatter.rb +11 -0
- data/lib/appsignal/transaction/slow_request_formatter.rb +34 -0
- data/lib/appsignal/transaction/transaction_formatter.rb +93 -0
- data/lib/appsignal/transmitter.rb +53 -0
- data/lib/appsignal/version.rb +3 -0
- data/lib/generators/appsignal/USAGE +8 -0
- data/lib/generators/appsignal/appsignal_generator.rb +70 -0
- data/lib/generators/appsignal/templates/appsignal.yml +4 -0
- data/log/.gitkeep +0 -0
- data/resources/cacert.pem +3849 -0
- data/spec/appsignal/agent_spec.rb +259 -0
- data/spec/appsignal/auth_check_spec.rb +36 -0
- data/spec/appsignal/capistrano_spec.rb +81 -0
- data/spec/appsignal/cli_spec.rb +124 -0
- data/spec/appsignal/config_spec.rb +40 -0
- data/spec/appsignal/exception_notification_spec.rb +12 -0
- data/spec/appsignal/inactive_railtie_spec.rb +30 -0
- data/spec/appsignal/marker_spec.rb +83 -0
- data/spec/appsignal/middleware_spec.rb +73 -0
- data/spec/appsignal/railtie_spec.rb +54 -0
- data/spec/appsignal/transaction/faulty_request_formatter_spec.rb +49 -0
- data/spec/appsignal/transaction/params_sanitizer_spec.rb +68 -0
- data/spec/appsignal/transaction/regular_request_formatter_spec.rb +14 -0
- data/spec/appsignal/transaction/slow_request_formatter_spec.rb +76 -0
- data/spec/appsignal/transaction/transaction_formatter_spec.rb +178 -0
- data/spec/appsignal/transaction_spec.rb +191 -0
- data/spec/appsignal/transmitter_spec.rb +64 -0
- data/spec/appsignal_spec.rb +66 -0
- data/spec/generators/appsignal/appsignal_generator_spec.rb +222 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/support/delegate_matcher.rb +39 -0
- 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
|