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
@@ -1,95 +1,59 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module Appsignal
4
- class TransactionFormatter
5
- extend Forwardable
4
+ class TransactionFormatter < SimpleDelegator
6
5
 
7
6
  def initialize(transaction)
8
- @transaction = transaction
7
+ super(transaction)
9
8
  end
10
9
 
11
- def self.regular(transaction)
12
- TransactionFormatter::RegularRequestFormatter.new(transaction)
10
+ def hash
11
+ @hash ||= default_hash
13
12
  end
14
13
 
15
- def self.slow(transaction)
16
- TransactionFormatter::SlowRequestFormatter.new(transaction)
14
+ def to_hash
15
+ merge_process_action_event_with_log_entry! if process_action_event
16
+ add_exception_to_hash! if exception?
17
+ add_events_to_hash! if slow_request?
18
+ hash
17
19
  end
18
20
 
19
- def self.faulty(transaction)
20
- TransactionFormatter::FaultyRequestFormatter.new(transaction)
21
- end
21
+ protected
22
22
 
23
- def to_hash
23
+ def default_hash
24
24
  {
25
25
  :request_id => id,
26
- :action => action,
27
- :log_entry => formatted_process_action_event,
26
+ :log_entry => {
27
+ :path => fullpath,
28
+ :kind => 'http_request',
29
+ :time => time,
30
+ :environment => sanitized_environment,
31
+ :session_data => sanitized_session_data
32
+ },
28
33
  :failed => exception?
29
34
  }
30
35
  end
31
36
 
32
- protected
33
-
34
- def_delegators :transaction, :id, :events, :exception, :exception?, :env,
35
- :request, :process_action_event, :action
36
- def_delegators :process_action_event, :payload
37
-
38
- attr_reader :transaction
39
-
40
- def formatted_process_action_event
41
- basic_process_action_event.tap { |hsh| hsh.merge!(formatted_payload) if process_action_event }
37
+ def merge_process_action_event_with_log_entry!
38
+ hash[:log_entry].merge!(process_action_event.to_appsignal_hash)
39
+ hash[:log_entry].tap do |o|
40
+ o.merge!(o.delete(:payload))
41
+ o[:action] = "#{o.delete(:controller)}##{o.delete(:action)}"
42
+ o.delete(:name)
43
+ end
42
44
  end
43
45
 
44
- def basic_process_action_event
45
- {
46
- :path => request.fullpath,
47
- :kind => 'http_request'
46
+ def add_exception_to_hash!
47
+ hash[:exception] = {
48
+ :backtrace => exception.backtrace,
49
+ :exception => exception.name,
50
+ :message => exception.message
48
51
  }
49
52
  end
50
53
 
51
- def formatted_payload
52
- sanitized_event_payload(process_action_event).merge(
53
- {
54
- :duration => process_action_event.duration,
55
- :time => process_action_event.time.to_f,
56
- :end => process_action_event.end.to_f,
57
- :action => action
58
- }
59
- )
60
- end
61
-
62
- def sanitized_event_payload(event)
63
- Appsignal::ParamsSanitizer.sanitize(
64
- Appsignal.event_payload_sanitizer.call(event)
65
- )
54
+ def add_events_to_hash!
55
+ hash[:events] = events.map(&:to_appsignal_hash)
66
56
  end
67
57
 
68
- def filtered_environment
69
- {}.tap do |out|
70
- env.each_pair do |key, value|
71
- out[key] = value if ENV_METHODS.include?(key)
72
- end
73
- end
74
- end
75
-
76
- # Based on what Rails uses + some variables we'd like to show
77
- ENV_METHODS = %w{ CONTENT_LENGTH AUTH_TYPE GATEWAY_INTERFACE
78
- PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER
79
- REMOTE_ADDR REQUEST_METHOD SERVER_NAME SERVER_PORT
80
- SERVER_PROTOCOL
81
-
82
- HTTP_X_REQUEST_START HTTP_X_MIDDLEWARE_START HTTP_X_QUEUE_START
83
- HTTP_X_QUEUE_TIME HTTP_X_HEROKU_QUEUE_WAIT_TIME
84
- HTTP_X_APPLICATION_START HTTP_ACCEPT HTTP_ACCEPT_CHARSET
85
- HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL
86
- HTTP_CONNECTION HTTP_USER_AGENT HTTP_FROM HTTP_NEGOTIATE
87
- HTTP_PRAGMA HTTP_REFERER }.freeze
88
-
89
58
  end
90
59
  end
91
-
92
- require 'appsignal/transaction/regular_request_formatter'
93
- require 'appsignal/transaction/slow_request_formatter'
94
- require 'appsignal/transaction/faulty_request_formatter'
95
- require 'appsignal/transaction/params_sanitizer'
@@ -1,3 +1,3 @@
1
1
  module Appsignal
2
- VERSION = '0.4.7'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -1,119 +1,24 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Appsignal::Agent do
4
- let(:transaction) { stub(
5
- :name => 'transaction',
6
- :exception? => false,
7
- :action => 'something#else'
8
- ) }
9
-
10
- describe '#add_to_queue' do
11
- before do
12
- @agent = Appsignal::Agent.new
13
- @exception_transaction = stub(
14
- :name => 'exception',
15
- :exception? => true,
16
- :action => 'controller#action1'
17
- )
18
- @slow_transaction = stub(
19
- :name => 'slow',
20
- :action => 'controller#action1',
21
- :exception? => false,
22
- :process_action_event => stub(
23
- :duration => 250.0
24
- )
25
- )
26
- @slower_transaction = stub(
27
- :name => 'slow',
28
- :action => 'controller#action1',
29
- :exception? => false,
30
- :process_action_event => stub(
31
- :duration => 300.0
32
- )
33
- )
34
- @other_slow_transaction = stub(
35
- :name => 'slow',
36
- :action => 'controller#action1',
37
- :exception? => false,
38
- :process_action_event => stub(
39
- :duration => 260.0
40
- )
41
- )
42
- @slow_transaction_in_other_action = stub(
43
- :name => 'slow',
44
- :action => 'controller#action2',
45
- :exception? => false,
46
- :process_action_event => stub(
47
- :duration => 400.0
48
- )
49
- )
50
- end
51
- subject { @agent }
52
-
53
- context "an exception transaction" do
54
- before do
55
- @exception_transaction.should_not_receive(:clear_payload_and_events!)
56
- subject.add_to_queue(@exception_transaction)
57
- end
58
-
59
- its(:queue) { should include(@exception_transaction) }
60
- its(:slowest_transactions) { should be_empty }
3
+ class PostProcessingException < Exception
4
+ end
61
5
 
62
- context "a slow transaction" do
63
- before do
64
- subject.add_to_queue(@slow_transaction)
65
- end
6
+ describe Appsignal::Agent do
7
+ let(:transaction) { regular_transaction }
66
8
 
67
- its(:queue) { should include(@slow_transaction) }
68
- its(:slowest_transactions) { should == {
69
- 'controller#action1' => @slow_transaction
70
- } }
71
-
72
- context "a slower transaction in the same action" do
73
- before do
74
- @slow_transaction.should_receive(:clear_payload_and_events!)
75
- @slower_transaction.should_not_receive(:clear_payload_and_events!)
76
- subject.add_to_queue(@slower_transaction)
77
- end
78
-
79
- its(:queue) { should include(@slower_transaction) }
80
- its(:slowest_transactions) { should == {
81
- 'controller#action1' => @slower_transaction
82
- } }
83
-
84
- context "a slow but not the slowest transaction in the same action" do
85
- before do
86
- @other_slow_transaction.should_receive(:clear_payload_and_events!)
87
- subject.add_to_queue(@other_slow_transaction)
88
- end
89
-
90
- its(:queue) { should include(@other_slow_transaction) }
91
- its(:slowest_transactions) { should == {
92
- 'controller#action1' => @slower_transaction
93
- } }
94
- end
95
-
96
- context "an even slower transaction in a different action" do
97
- before do
98
- @slow_transaction_in_other_action.should_not_receive(:clear_payload_and_events!)
99
- subject.add_to_queue(@slow_transaction_in_other_action)
100
- end
101
-
102
- its(:queue) { should include(@slow_transaction_in_other_action) }
103
- its(:slowest_transactions) { should == {
104
- 'controller#action1' => @slower_transaction,
105
- 'controller#action2' => @slow_transaction_in_other_action
106
- } }
107
- end
108
- end
109
- end
9
+ describe "#enqueue" do
10
+ it "forwards to the aggregator" do
11
+ subject.aggregator.should respond_to(:add)
12
+ subject.aggregator.should_receive(:add).with(:foo)
110
13
  end
14
+
15
+ after { subject.enqueue(:foo) }
111
16
  end
112
17
 
113
18
  describe "#send_queue" do
114
19
  it "transmits" do
115
- subject.stub(:queue => [stub(:to_hash => 'foo')])
116
- subject.transmitter.should_receive(:transmit).with(['foo'])
20
+ subject.aggregator.stub(:post_processed_queue! => :foo)
21
+ subject.transmitter.should_receive(:transmit).with(:foo)
117
22
  end
118
23
 
119
24
  it "handles the return code" do
@@ -121,42 +26,62 @@ describe Appsignal::Agent do
121
26
  subject.should_receive(:handle_result).with('200')
122
27
  end
123
28
 
29
+ it "handle exceptions in post processing" do
30
+ subject.aggregator.stub(:post_processed_queue!).and_raise(
31
+ PostProcessingException.new('Message')
32
+ )
33
+
34
+ subject.should_receive(:stop_logging)
35
+ Appsignal.logger.should_receive(:error).
36
+ with('PostProcessingException while sending queue: Message').
37
+ once
38
+ Appsignal.logger.should_receive(:error).
39
+ with(kind_of(String)).
40
+ once
41
+ end
42
+
124
43
  it "handles exceptions in transmit" do
125
- subject.transmitter.stub(:transmit).and_raise(Exception.new)
126
- subject.should_receive(:handle_result).with(nil)
127
- Appsignal.logger.should_receive(:error).with('Exception while communicating with AppSignal: Exception')
44
+ subject.transmitter.stub(:transmit).and_raise(
45
+ Exception.new('Message')
46
+ )
47
+ subject.should_receive(:stop_logging)
48
+ Appsignal.logger.should_receive(:error).
49
+ with('Exception while sending queue: Message').
50
+ once
51
+ Appsignal.logger.should_receive(:error).
52
+ with(kind_of(String)).
53
+ once
128
54
  end
129
55
 
130
56
  after { subject.send_queue }
131
57
  end
132
58
 
133
59
  describe '#handle_result' do
134
- before { subject.add_to_queue(transaction) }
60
+ before { subject.aggregator.add(transaction) }
135
61
  before { subject.instance_variable_set(:@sleep_time, 3.0) }
136
62
 
137
63
  context "good responses" do
138
- before { subject.handle_result(code) }
64
+ before { subject.send(:handle_result, code) }
139
65
 
140
66
  context "with 200" do
141
67
  let(:code) { '200' }
142
68
 
143
- its(:queue) { should be_empty }
144
- its(:slowest_transactions) { should be_empty }
69
+ its(:sleep_time) { should == 3.0 }
70
+
71
+ it "does not log the event" do
72
+ Appsignal.logger.should_not_receive(:error)
73
+ end
145
74
  end
146
75
 
147
76
  context "with 420" do
148
77
  let(:code) { '420' }
149
78
 
150
- its(:queue) { should be_empty }
151
- its(:slowest_transactions) { should be_empty }
152
79
  its(:sleep_time) { should == 4.5 }
153
80
  end
154
81
 
155
82
  context "with 413" do
156
83
  let(:code) { '413' }
157
84
 
158
- its(:queue) { should be_empty }
159
- its(:slowest_transactions) { should be_empty }
160
85
  its(:sleep_time) { should == 2.0 }
161
86
  end
162
87
  end
@@ -197,47 +122,12 @@ describe Appsignal::Agent do
197
122
  context "any other response" do
198
123
  let(:code) { 'any other response' }
199
124
 
200
- it "calls retry_once" do
201
- subject.should_receive :retry_once
125
+ it "logs the event" do
126
+ Appsignal.logger.should_receive(:error)
202
127
  end
203
128
  end
204
129
 
205
- after { subject.handle_result(code) }
206
- end
207
- end
208
-
209
- describe "#good_response" do
210
- before do
211
- subject.instance_variable_set(:@retry_once, false)
212
- subject.add_to_queue(transaction)
213
- subject.send :good_response
214
- end
215
-
216
- its(:queue) { should be_empty }
217
- its(:slowest_transactions) { should be_empty }
218
-
219
- it "allows the next request to be retried" do
220
- subject.instance_variable_get(:@retry_request).should be_true
221
- end
222
- end
223
-
224
- describe "#retry_once" do
225
- before do
226
- subject.add_to_queue(transaction)
227
- subject.send :retry_once
228
- end
229
-
230
- context "on time," do
231
- its(:queue) { should == [transaction] }
232
- its(:slowest_transactions) { should == {
233
- 'something#else' => transaction
234
- } }
235
-
236
- context "two times" do
237
- before { subject.send :retry_once }
238
-
239
- its(:queue) { should be_empty }
240
- end
130
+ after { subject.send(:handle_result, code) }
241
131
  end
242
132
  end
243
133
 
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::PostProcessor do
4
+ let(:klass) { Appsignal::PostProcessor }
5
+ let(:post_processor) { klass.new(transactions) }
6
+
7
+ describe "#initialize" do
8
+ subject { klass.new(:foo) }
9
+
10
+ its(:transactions) { should == :foo }
11
+ end
12
+
13
+ describe "#post_processed_queue!" do
14
+ let(:first_transaction) { regular_transaction }
15
+ let(:second_transaction) do
16
+ slow_transaction(
17
+ :events => [
18
+ notification_event(
19
+ :payload => {
20
+ :set_value => 'set value',
21
+ :nil_value => nil
22
+ }
23
+ )
24
+ ]
25
+ )
26
+ end
27
+ let(:transactions) { [first_transaction, second_transaction] }
28
+ let(:processed_queue) { post_processor.post_processed_queue! }
29
+ subject { processed_queue }
30
+
31
+ it { should have(2).items }
32
+
33
+ context "the first transaction" do
34
+ subject { processed_queue[0] }
35
+
36
+ it { should_not have_key(:events) }
37
+
38
+ context "log entry" do
39
+ subject { processed_queue[0][:log_entry] }
40
+
41
+ it { should have_key(:duration) }
42
+ it { should have_key(:end) }
43
+ it { should_not have_key(:events) }
44
+ end
45
+ end
46
+
47
+ context "the second transaction" do
48
+ context "log entry" do
49
+ subject { processed_queue[1][:log_entry] }
50
+
51
+ it { should have_key(:duration) }
52
+ it { should have_key(:end) }
53
+ end
54
+
55
+ context "first event" do
56
+ subject { processed_queue[1][:events][0] }
57
+
58
+ it { should_not be_nil }
59
+ it { should have_key(:name) }
60
+
61
+ it "should have the set value in the payload" do
62
+ subject[:payload][:set_value].should == 'set value'
63
+ end
64
+
65
+ it "should not have the nil value in the payload"\
66
+ "since delete blanks is in the middle ware stack" do
67
+ subject[:payload].should_not have_key(:nil_value)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ describe ".default_middleware" do
74
+ subject { klass.default_middleware }
75
+
76
+ it { should be_instance_of Appsignal::Middleware::Chain }
77
+
78
+ it "should include the default middleware stack" do
79
+ subject.exists?(Appsignal::Middleware::DeleteBlanks).should be_true
80
+ subject.exists?(Appsignal::Middleware::ActionViewSanitizer).should be_true
81
+ subject.exists?(Appsignal::Middleware::ActiveRecordSanitizer).should be_true
82
+ end
83
+ end
84
+ end