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
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Middleware::DeleteBlanks do
4
+ let(:klass) { Appsignal::Middleware::DeleteBlanks }
5
+ let(:delete_blanks) { klass.new }
6
+
7
+ describe "#call" do
8
+ let(:event) do
9
+ notification_event(
10
+ :name => 'something',
11
+ :payload => create_payload(payload)
12
+ )
13
+ end
14
+ let(:payload) do
15
+ {
16
+ :string => 'not empty',
17
+ :array => ['something'],
18
+ :hash => {'something' => 'something'},
19
+ :empty_string => '',
20
+ :empty_array => [],
21
+ :empty_hash => {},
22
+ :nil => nil
23
+ }
24
+ end
25
+ subject { event.payload }
26
+ before { delete_blanks.call(event) { } }
27
+
28
+ it { should have_key(:string) }
29
+ it { should have_key(:array) }
30
+ it { should have_key(:hash) }
31
+
32
+ it { should_not have_key(:empty_string) }
33
+ it { should_not have_key(:empty_array) }
34
+ it { should_not have_key(:empty_hash) }
35
+ it { should_not have_key(:nil) }
36
+ end
37
+ end
@@ -3,52 +3,65 @@ require 'action_controller/railtie'
3
3
  require 'appsignal/railtie'
4
4
 
5
5
  describe Appsignal::Railtie do
6
-
7
- before(:all) { MyApp::Application.initialize! }
8
-
9
- it "should have set the appsignal subscriber" do
10
- Appsignal.subscriber.
11
- should be_a ActiveSupport::Notifications::Fanout::Subscriber
6
+ it "should log to the in memory log before init" do
7
+ Appsignal.logger.error('Log something before init')
8
+ Appsignal.in_memory_log.string.should include('Log something before init')
12
9
  end
13
10
 
14
- it "should have added the middleware for exceptions" do
15
- MyApp::Application.middleware.to_a.should include Appsignal::Middleware
16
- end
11
+ context "after initializing the app" do
12
+ before(:all) { MyApp::Application.initialize! }
17
13
 
18
- context "non action_controller event" do
19
- it "should call add_event for non action_controller event" do
20
- current = stub
21
- current.should_receive(:add_event)
22
- Appsignal::Transaction.should_receive(:current).twice.
23
- and_return(current)
14
+ it "should start logging to file and have written the in memory log" do
15
+ Appsignal.logger.should be_instance_of(Logger)
16
+ File.open(log_file).read.should include(
17
+ 'Log something before init'
18
+ )
24
19
  end
25
20
 
26
- after { ActiveSupport::Notifications.instrument 'query.mongoid' }
27
- end
21
+ it "should have set the appsignal subscriber" do
22
+ Appsignal.subscriber.
23
+ should be_a ActiveSupport::Notifications::Fanout::Subscriber
24
+ end
28
25
 
29
- context "action_controller event" do
30
- it "should call set_process_action_event for action_controller event" do
31
- current = stub
32
- current.should_receive(:set_process_action_event)
33
- current.should_receive(:add_event)
34
- Appsignal::Transaction.should_receive(:current).exactly(3).times.
35
- and_return(current)
26
+ it "should have added the listener middleware for exceptions" do
27
+ MyApp::Application.middleware.to_a.should include Appsignal::Listener
36
28
  end
37
29
 
38
- after do
39
- ActiveSupport::Notifications.
40
- instrument 'process_action.action_controller'
30
+ context "non action_controller event" do
31
+ it "should call add_event for non action_controller event" do
32
+ current = mock(:current)
33
+ current.should_receive(:add_event)
34
+ Appsignal::Transaction.should_receive(:current).twice.
35
+ and_return(current)
36
+ end
37
+
38
+ after { ActiveSupport::Notifications.instrument 'query.mongoid' }
41
39
  end
42
- end
43
40
 
44
- context "event that starts with a bang" do
45
- it "should not be processed" do
46
- Appsignal::Transaction.should_not_receive(:current)
41
+ context "action_controller event" do
42
+ it "should call set_process_action_event for action_controller event" do
43
+ current = mock(:current)
44
+ current.should_receive(:set_process_action_event)
45
+ current.should_receive(:add_event)
46
+ Appsignal::Transaction.should_receive(:current).exactly(3).times.
47
+ and_return(current)
48
+ end
49
+
50
+ after do
51
+ ActiveSupport::Notifications.
52
+ instrument 'process_action.action_controller'
53
+ end
47
54
  end
48
55
 
49
- after do
50
- ActiveSupport::Notifications.
51
- instrument '!render_template'
56
+ context "event that starts with a bang" do
57
+ it "should not be processed" do
58
+ Appsignal::Transaction.should_not_receive(:current)
59
+ end
60
+
61
+ after do
62
+ ActiveSupport::Notifications.
63
+ instrument '!render_template'
64
+ end
52
65
  end
53
66
  end
54
67
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::ToAppsignalHash do
4
+ subject { notification_event }
5
+
6
+ it { should be_instance_of ActiveSupport::Notifications::Event }
7
+ it { should respond_to(:to_appsignal_hash) }
8
+
9
+ describe "#to_appsignal_hash" do
10
+ subject { notification_event.to_appsignal_hash }
11
+
12
+ it { should == {
13
+ :time => 978364860.0,
14
+ :duration => 100.0,
15
+ :end => 978364860.1,
16
+ :name => "process_action.action_controller",
17
+ :payload => {
18
+ :path=>"/blog",
19
+ :action=>"show",
20
+ :controller=>"BlogPostsController",
21
+ :request_format=>"html",
22
+ :request_method=>"GET",
23
+ :status=>"200",
24
+ :view_runtime=>500,
25
+ :db_runtime=>500
26
+ }
27
+ } }
28
+ end
29
+ end
@@ -1,66 +1,171 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Appsignal::ParamsSanitizer do
4
- describe ".sanitize" do
5
- let(:file) { ActionDispatch::Http::UploadedFile.new(:tempfile => '/tmp') }
6
- let(:params) do
7
- {
8
- :text => 'string',
9
- :file => file,
10
- :hash => {
11
- :nested_text => 'string',
12
- :nested_array => [
13
- 'something',
14
- 'else',
15
- file,
16
- {
17
- :key => 'value',
18
- :nested_array_hash_file => file,
19
- }
20
- ]
21
- }
4
+ let(:file) { ActionDispatch::Http::UploadedFile.new(:tempfile => '/tmp') }
5
+ let(:params) do
6
+ {
7
+ :text => 'string',
8
+ :file => file,
9
+ :hash => {
10
+ :nested_text => 'string',
11
+ :nested_array => [
12
+ 'something',
13
+ 'else',
14
+ file,
15
+ {
16
+ :key => 'value',
17
+ :file => file,
18
+ }
19
+ ]
22
20
  }
21
+ }
22
+ end
23
+ let(:sanitized_params) { Appsignal::ParamsSanitizer.sanitize(params) }
24
+ let(:scrubbed_params) { Appsignal::ParamsSanitizer.scrub(params) }
25
+
26
+ describe ".sanitize!" do
27
+ subject { params }
28
+ before { Appsignal::ParamsSanitizer.sanitize!(subject) }
29
+
30
+ it { should be_instance_of Hash }
31
+ its([:text]) { should == 'string' }
32
+ its([:file]) { should be_instance_of String }
33
+ its([:file]) { should include '#<ActionDispatch::Http::UploadedFile:' }
34
+
35
+ context "hash" do
36
+ subject { params[:hash] }
37
+
38
+ it { should be_instance_of Hash }
39
+ its([:nested_text]) { should == 'string' }
40
+
41
+ context "nested_array" do
42
+ subject { params[:hash][:nested_array] }
43
+
44
+ it { should be_instance_of Array }
45
+ its([0]) { should == 'something' }
46
+ its([1]) { should == 'else' }
47
+ its([2]) { should be_instance_of String }
48
+ its([2]) { should include '#<ActionDispatch::Http::UploadedFile:' }
49
+
50
+ context "nested hash" do
51
+ subject { params[:hash][:nested_array][3] }
52
+
53
+ it { should be_instance_of Hash }
54
+ its([:key]) { should == 'value' }
55
+ its([:file]) { should be_instance_of String }
56
+ its([:file]) { should include '#<ActionDispatch::Http::UploadedFile:' }
57
+ end
58
+ end
23
59
  end
24
- let(:sanitized_params) { Appsignal::ParamsSanitizer.sanitize(params) }
60
+ end
25
61
 
62
+ describe ".sanitize" do
26
63
  subject { sanitized_params }
27
64
 
28
65
  it { should be_instance_of Hash }
29
- it('should have a text') { subject[:text].should == 'string' }
30
- it('should have a file') do
31
- subject[:file].should be_instance_of String
32
- subject[:file].should include '#<ActionDispatch::Http::UploadedFile:'
66
+ its([:text]) { should == 'string' }
67
+ its([:file]) { should be_instance_of String }
68
+ its([:file]) { should include '#<ActionDispatch::Http::UploadedFile:' }
69
+
70
+ it "does not change the original params" do
71
+ subject
72
+ params[:file].should == file
73
+ params[:hash][:nested_array][2].should == file
33
74
  end
34
75
 
35
76
  context "hash" do
36
77
  subject { sanitized_params[:hash] }
37
78
 
38
79
  it { should be_instance_of Hash }
39
- it('should have a nested text') { subject[:nested_text].should == 'string' }
80
+ its([:nested_text]) { should == 'string' }
40
81
 
41
82
  context "nested_array" do
42
83
  subject { sanitized_params[:hash][:nested_array] }
43
84
 
44
85
  it { should be_instance_of Array }
86
+ its([0]) { should == 'something' }
87
+ its([1]) { should == 'else' }
88
+ its([2]) { should be_instance_of String }
89
+ its([2]) { should include '#<ActionDispatch::Http::UploadedFile:' }
45
90
 
46
- it("should have two string items") do
47
- subject.first.should == 'something'
48
- subject.second.should == 'else'
91
+ context "nested hash" do
92
+ subject { sanitized_params[:hash][:nested_array][3] }
93
+
94
+ it { should be_instance_of Hash }
95
+ its([:key]) { should == 'value' }
96
+ its([:file]) { should be_instance_of String }
97
+ its([:file]) { should include '#<ActionDispatch::Http::UploadedFile:' }
49
98
  end
50
- it "should have a file" do
51
- subject.third.should be_instance_of String
52
- subject.third.should include '#<ActionDispatch::Http::UploadedFile:'
99
+ end
100
+ end
101
+ end
102
+
103
+ describe ".scrub!" do
104
+ subject { params }
105
+ before { Appsignal::ParamsSanitizer.scrub!(subject) }
106
+
107
+ it { should be_instance_of Hash }
108
+ its([:text]) { should == '?' }
109
+ its([:file]) { should == '?' }
110
+
111
+ context "hash" do
112
+ subject { params[:hash] }
113
+
114
+ it { should be_instance_of Hash }
115
+ its([:nested_text]) { should == '?' }
116
+
117
+ context "nested_array" do
118
+ subject { params[:hash][:nested_array] }
119
+
120
+ it { should be_instance_of Array }
121
+ its([0]) { should == '?' }
122
+ its([1]) { should == '?' }
123
+ its([2]) { should == '?' }
124
+
125
+ context "nested hash" do
126
+ subject { params[:hash][:nested_array][3] }
127
+
128
+ it { should be_instance_of Hash }
129
+ its([:key]) { should == '?' }
130
+ its([:file]) { should == '?' }
53
131
  end
132
+ end
133
+ end
134
+ end
135
+
136
+ describe ".scrub" do
137
+ subject { scrubbed_params }
138
+
139
+ it "does not change the original params" do
140
+ subject
141
+ params[:file].should == file
142
+ params[:hash][:nested_array][2].should == file
143
+ end
144
+
145
+ it { should be_instance_of Hash }
146
+ its([:text]) { should == '?' }
147
+ its([:file]) { should == '?' }
148
+
149
+ context "hash" do
150
+ subject { scrubbed_params[:hash] }
151
+
152
+ it { should be_instance_of Hash }
153
+ its([:nested_text]) { should == '?' }
154
+
155
+ context "nested_array" do
156
+ subject { scrubbed_params[:hash][:nested_array] }
157
+
158
+ it { should be_instance_of Array }
159
+ its([0]) { should == '?' }
160
+ its([1]) { should == '?' }
161
+ its([2]) { should == '?' }
54
162
 
55
163
  context "nested hash" do
56
- subject { sanitized_params[:hash][:nested_array].fourth }
164
+ subject { scrubbed_params[:hash][:nested_array][3] }
57
165
 
58
166
  it { should be_instance_of Hash }
59
- it('should have a text') { subject[:key].should == 'value' }
60
- it('should have a file') do
61
- subject[:nested_array_hash_file].should be_instance_of String
62
- subject[:nested_array_hash_file].should include '#<ActionDispatch::Http::UploadedFile:'
63
- end
167
+ its([:key]) { should == '?' }
168
+ its([:file]) { should == '?' }
64
169
  end
65
170
  end
66
171
  end
@@ -3,175 +3,80 @@ require 'spec_helper'
3
3
  describe Appsignal::TransactionFormatter do
4
4
  let(:klass) { Appsignal::TransactionFormatter }
5
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
6
  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
7
+ before { transaction.stub(:fullpath => '/foo') }
8
+
9
+ describe "#to_hash" do
10
+ before { formatter.to_hash }
11
+ subject { formatter.hash }
12
+
13
+ context "with a regular request" do
14
+ let(:transaction) { regular_transaction }
15
+
16
+ its(:keys) { should == [:request_id, :log_entry, :failed] }
17
+ its([:request_id]) { should == '1' }
18
+ its([:log_entry]) { should == {
19
+ :action => "BlogPostsController#show",
20
+ :db_runtime => 500,
21
+ :duration => 100.0,
22
+ :end => 978364860.1,
23
+ :environment => {},
24
+ :kind => "http_request",
25
+ :path => "/blog",
26
+ :request_format => "html",
27
+ :request_method => "GET",
28
+ :session_data => {},
29
+ :status => "200",
30
+ :time => 978364860.0,
31
+ :view_runtime => 500
32
+ } }
33
+ its([:failed]) { should be_false }
43
34
  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
35
 
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
36
+ context "with an exception request" do
37
+ let(:transaction) { transaction_with_exception }
67
38
 
68
- context "with actual process action event data" do
69
- before { transaction.set_process_action_event(create_process_action_event) }
39
+ its(:keys) { should == [:request_id, :log_entry, :failed, :exception] }
40
+ its([:request_id]) { should == '1' }
41
+ its([:failed]) { should be_true }
70
42
 
71
- it { should be_a Hash }
43
+ context "exception content" do
44
+ subject { formatter.hash[:exception] }
72
45
 
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
46
+ its(:keys) { should == [:backtrace, :exception, :message] }
47
+ its([:backtrace]) { should be_instance_of Array }
48
+ its([:exception]) { should == 'ArgumentError' }
49
+ its([:message]) { should == 'oh no' }
86
50
  end
87
51
  end
88
52
 
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
53
+ context "with a slow request" do
54
+ let(:transaction) { slow_transaction }
97
55
 
98
- it { should == {
99
- :path => '/blog',
100
- :kind => 'http_request'
101
- } }
56
+ its(:keys) { should == [:request_id, :log_entry, :failed, :events] }
57
+ its([:request_id]) { should == '1' }
58
+ its([:failed]) { should be_false }
102
59
 
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
60
+ context "events content" do
61
+ subject { formatter.hash[:events] }
111
62
 
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,
63
+ its(:length) { should == 1 }
64
+ its(:first) { should == {
65
+ :name => "query.mongoid",
66
+ :duration => 100.0,
67
+ :time => 978364860.0,
68
+ :end => 978364860.1,
123
69
  :payload => {
124
- :controller => 'controller',
125
- :action => 'action'
70
+ :path => "/blog",
71
+ :action => "show",
72
+ :controller => "BlogPostsController",
73
+ :request_format => "html",
74
+ :request_method => "GET",
75
+ :status => "200",
76
+ :view_runtime => 500,
77
+ :db_runtime => 500
126
78
  }
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'
79
+ } }
175
80
  end
176
81
  end
177
82
  end