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