appsignal 0.10.6 → 0.11.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- module Appsignal
1
+ module Appsignal
2
2
  class Transaction
3
3
  # Based on what Rails uses + some variables we'd like to show
4
4
  ENV_METHODS = %w(CONTENT_LENGTH AUTH_TYPE GATEWAY_INTERFACE
@@ -97,9 +97,13 @@
97
97
  process_action_event.duration > transaction.process_action_event.duration
98
98
  end
99
99
 
100
+ def clear_events!
101
+ events.clear
102
+ end
103
+
100
104
  def truncate!
101
105
  return if truncated?
102
- process_action_event.payload.clear
106
+ process_action_event.truncate!
103
107
  events.clear
104
108
  tags.clear
105
109
  sanitized_environment.clear
@@ -114,12 +118,8 @@
114
118
 
115
119
  def convert_values_to_primitives!
116
120
  return if have_values_been_converted_to_primitives?
117
- Appsignal::Transaction::ParamsSanitizer.sanitize!(@process_action_event.payload) if @process_action_event
118
- @events.map do |o|
119
- o.payload.each do |key, value|
120
- o.payload[key] = Appsignal::Transaction::ParamsSanitizer.sanitize(value)
121
- end
122
- end
121
+ @process_action_event.sanitize! if @process_action_event
122
+ @events.each { |event| event.sanitize! }
123
123
  add_sanitized_context!
124
124
  @have_values_been_converted_to_primitives = true
125
125
  end
@@ -142,10 +142,9 @@
142
142
  Thread.current[:appsignal_transaction_id] = nil
143
143
  Appsignal.transactions.delete(@request_id)
144
144
  if process_action_event || exception?
145
- if Appsignal::Pipe.current
145
+ if Appsignal::IPC::Client.active?
146
146
  convert_values_to_primitives!
147
- Appsignal.logger.debug("Writing transaction to pipe: #{@request_id}")
148
- Appsignal::Pipe.current.write(self)
147
+ Appsignal::IPC::Client.enqueue(self)
149
148
  else
150
149
  Appsignal.logger.debug("Enqueueing transaction: #{@request_id}")
151
150
  Appsignal.enqueue(self)
@@ -204,7 +203,7 @@
204
203
  end
205
204
 
206
205
  def sanitize_session_data!
207
- @sanitized_session_data = Appsignal::Transaction::ParamsSanitizer.sanitize(
206
+ @sanitized_session_data = Appsignal::ParamsSanitizer.sanitize(
208
207
  request.session.to_hash
209
208
  ) if Appsignal.config[:skip_session_data] == false
210
209
  @fullpath = request.fullpath
@@ -1,119 +1,4 @@
1
- module Appsignal
2
- class Transaction
3
- class ParamsSanitizer
4
- class << self
5
- def sanitize(params)
6
- ParamsSanitizerCopy.sanitize_value(params)
7
- end
1
+ # The Appsignal-moped gem depends on this class
2
+ class Appsignal::Transaction::ParamsSanitizer < Appsignal::ParamsSanitizer
8
3
 
9
- def sanitize!(params)
10
- ParamsSanitizerDestructive.sanitize_value(params)
11
- end
12
-
13
- def scrub(params)
14
- ParamsSanitizerCopyScrub.sanitize_value(params)
15
- end
16
-
17
- def scrub!(params)
18
- ParamsSanitizerDestructiveScrub.sanitize_value(params)
19
- end
20
-
21
- protected
22
-
23
- def sanitize_value(value)
24
- case value
25
- when Hash
26
- sanitize_hash(value)
27
- when Array
28
- sanitize_array(value)
29
- when Fixnum, String, Symbol
30
- unmodified(value)
31
- else
32
- inspected(value)
33
- end
34
- end
35
-
36
- def sanitize_hash_with_target(source_hash, target_hash)
37
- source_hash.each_pair do |key, value|
38
- target_hash[key] = sanitize_value(value)
39
- end
40
- target_hash
41
- end
42
-
43
- def sanitize_array_with_target(source_array, target_array)
44
- source_array.each_with_index do |item, index|
45
- target_array[index] = sanitize_value(item)
46
- end
47
- target_array
48
- end
49
-
50
- def unmodified(value)
51
- value
52
- end
53
-
54
- def inspected(value)
55
- value.inspect
56
- rescue
57
- # It turns out that sometimes inspect can fail
58
- "#<#{value.class.to_s}/>"
59
- end
60
- end
61
- end
62
-
63
- class ParamsSanitizerCopy < ParamsSanitizer
64
- class << self
65
- protected
66
-
67
- def sanitize_hash(hash)
68
- sanitize_hash_with_target(hash, {})
69
- end
70
-
71
- def sanitize_array(array)
72
- sanitize_array_with_target(array, [])
73
- end
74
- end
75
- end
76
-
77
- class ParamsSanitizerDestructive < ParamsSanitizer
78
- class << self
79
- protected
80
-
81
- def sanitize_hash(hash)
82
- sanitize_hash_with_target(hash, hash)
83
- end
84
-
85
- def sanitize_array(array)
86
- sanitize_array_with_target(array, array)
87
- end
88
- end
89
- end
90
-
91
- class ParamsSanitizerCopyScrub < ParamsSanitizerCopy
92
- class << self
93
- protected
94
-
95
- def unmodified(value)
96
- '?'
97
- end
98
-
99
- def inspected(value)
100
- '?'
101
- end
102
- end
103
- end
104
-
105
- class ParamsSanitizerDestructiveScrub < ParamsSanitizerDestructive
106
- class << self
107
- protected
108
-
109
- def unmodified(value)
110
- '?'
111
- end
112
-
113
- def inspected(value)
114
- '?'
115
- end
116
- end
117
- end
118
- end
119
4
  end
@@ -10,6 +10,17 @@ module Appsignal
10
10
  CONTENT_ENCODING = 'gzip'.freeze
11
11
  CA_FILE_PATH = File.expand_path(File.join(__FILE__, '../../../resources/cacert.pem'))
12
12
 
13
+ HTTP_ERRORS = [
14
+ EOFError,
15
+ Errno::ECONNRESET,
16
+ Errno::EINVAL,
17
+ Net::HTTPBadResponse,
18
+ Net::HTTPHeaderSyntaxError,
19
+ Net::ProtocolError,
20
+ Timeout::Error,
21
+ OpenSSL::SSL::SSLError
22
+ ]
23
+
13
24
  attr_reader :config, :action
14
25
 
15
26
  def initialize(action, config=Appsignal.config)
@@ -1,3 +1,3 @@
1
1
  module Appsignal
2
- VERSION = '0.10.6'
2
+ VERSION = '0.11.0.beta.1'
3
3
  end
@@ -32,7 +32,16 @@ describe Appsignal::Agent do
32
32
  end
33
33
 
34
34
  describe "#start_thread" do
35
- before { subject.thread = nil }
35
+ before do
36
+ subject.thread = nil
37
+ subject.stub(:sleep_time => 0.1)
38
+ end
39
+
40
+ it "should truncate the aggregator queue" do
41
+ subject.should_receive(:truncate_aggregator_queue).at_least(1).times
42
+ subject.start_thread
43
+ sleep 1
44
+ end
36
45
 
37
46
  context "without transactions" do
38
47
  it "should start and run a background thread" do
@@ -47,10 +56,7 @@ describe Appsignal::Agent do
47
56
 
48
57
  context "with transactions" do
49
58
  before do
50
- subject.stub(
51
- :aggregator => double(:has_transactions? => true),
52
- :sleep_time => 0.01
53
- )
59
+ subject.stub(:aggregator => double(:has_transactions? => true))
54
60
  end
55
61
 
56
62
  it "should send the queue and sleep" do
@@ -67,10 +73,7 @@ describe Appsignal::Agent do
67
73
  aggregator.stub(:has_transactions?).and_raise(
68
74
  RuntimeError.new('error')
69
75
  )
70
- subject.stub(
71
- :aggregator => aggregator,
72
- :sleep_time => 0.1
73
- )
76
+ subject.stub(:aggregator => aggregator)
74
77
  end
75
78
 
76
79
  it "should log the error" do
@@ -250,20 +253,51 @@ describe Appsignal::Agent do
250
253
  end
251
254
 
252
255
  describe "#send_queue" do
253
- context "without transactions" do
254
- it "does not transmit" do
255
- subject.should_not_receive(:handle_result)
256
- end
256
+ it "adds aggregator to queue" do
257
+ subject.aggregator.stub(:post_processed_queue! => :foo)
258
+ subject.should_receive(:add_to_aggregator_queue).with(:foo)
257
259
  end
258
260
 
259
- context "with transactions" do
260
- before do
261
- subject.aggregator.stub(:has_transactions? => true)
262
- end
261
+ it "sends aggregators" do
262
+ subject.should_receive(:send_aggregators)
263
+ end
264
+
265
+ it "handle exceptions in post processing" do
266
+ subject.aggregator.stub(:post_processed_queue!).and_raise(
267
+ PostProcessingException.new('Message')
268
+ )
269
+
270
+ Appsignal.logger.should_receive(:error).
271
+ with('PostProcessingException while sending queue: Message').
272
+ once
273
+ Appsignal.logger.should_receive(:error).
274
+ with(kind_of(String)).
275
+ once
276
+ end
263
277
 
264
- it "transmits" do
265
- subject.aggregator.stub(:post_processed_queue! => :foo)
266
- subject.transmitter.should_receive(:transmit).with(:foo)
278
+ it "handles exceptions in transmit" do
279
+ subject.stub(:send_aggregators).and_raise(
280
+ Exception.new('Message')
281
+ )
282
+
283
+ Appsignal.logger.should_receive(:error).
284
+ with('Exception while sending queue: Message').
285
+ once
286
+ Appsignal.logger.should_receive(:error).
287
+ with(kind_of(String)).
288
+ once
289
+ end
290
+
291
+ after { subject.send_queue }
292
+ end
293
+
294
+ describe "#send_aggregators" do
295
+ let(:aggregator_hash) { double }
296
+ before { subject.add_to_aggregator_queue(aggregator_hash) }
297
+
298
+ context "sending aggreagotor hashes" do
299
+ it "sends each item in the aggregators_to_be_sent array" do
300
+ subject.transmitter.should_receive(:transmit).with(aggregator_hash)
267
301
  end
268
302
 
269
303
  it "handles the return code" do
@@ -271,43 +305,63 @@ describe Appsignal::Agent do
271
305
  subject.should_receive(:handle_result).with('200')
272
306
  end
273
307
 
274
- it "handle exceptions in post processing" do
275
- subject.aggregator.stub(:post_processed_queue!).and_raise(
276
- PostProcessingException.new('Message')
277
- )
308
+ after { subject.send_aggregators }
309
+ end
278
310
 
279
- Appsignal.logger.should_receive(:error).
280
- with('PostProcessingException while sending queue: Message').
281
- once
282
- Appsignal.logger.should_receive(:error).
283
- with(kind_of(String)).
284
- once
311
+ context "managing the queue" do
312
+ before { subject.transmitter.stub(:transmit => '200') }
313
+
314
+ context "when successfully sent" do
315
+ before { subject.stub(:handle_result => true) }
316
+
317
+ it "should remove only successfully sent item from the queue" do
318
+ expect {
319
+ subject.send_aggregators
320
+ }.to change(subject, :aggregator_queue).from([aggregator_hash]).to([])
321
+ end
285
322
  end
286
323
 
287
- it "handles exceptions in transmit" do
288
- subject.transmitter.stub(:transmit).and_raise(
289
- Exception.new('Message')
290
- )
324
+ context "when failed to sent" do
325
+ before { subject.stub(:handle_result => false) }
291
326
 
292
- Appsignal.logger.should_receive(:error).
293
- with('Exception while sending queue: Message').
294
- once
295
- Appsignal.logger.should_receive(:error).
296
- with(kind_of(String)).
297
- once
327
+ it "should remove only successfully sent item from the queue" do
328
+ expect {
329
+ subject.send_aggregators
330
+ }.to_not change(subject, :aggregator_queue)
331
+ end
298
332
  end
299
333
 
300
- it "handles an OpenSSL error in transmit" do
301
- subject.transmitter.stub(:transmit).and_raise(
302
- OpenSSL::SSL::SSLError.new('Message')
303
- )
334
+ context "when an exception occurred during sending" do
335
+ before { subject.stub(:transmitter).and_raise(OpenSSL::SSL::SSLError.new) }
336
+
337
+ it "should remove only successfully sent item from the queue" do
338
+ expect {
339
+ subject.send_aggregators
340
+ }.to_not change(subject, :aggregator_queue)
341
+ end
304
342
 
305
- Appsignal.logger.should_receive(:error).
306
- with('OpenSSL::SSL::SSLError: Message').once
307
343
  end
308
344
  end
345
+ end
309
346
 
310
- after { subject.send_queue }
347
+ describe "#truncate_aggregator_queue" do
348
+ before do
349
+ 5.times { |i| subject.add_to_aggregator_queue(i) }
350
+ end
351
+
352
+ it "should truncate the queue to the given limit" do
353
+ expect {
354
+ subject.truncate_aggregator_queue(2)
355
+ }.to change(subject, :aggregator_queue).from([4, 3, 2, 1, 0]).to([4,3])
356
+ end
357
+
358
+ it "should log this event as an error" do
359
+ Appsignal.logger.should_receive(:error).
360
+ with('Aggregator queue to large, removing items').
361
+ once
362
+
363
+ subject.truncate_aggregator_queue(2)
364
+ end
311
365
  end
312
366
 
313
367
  describe "#clear_queue" do
@@ -441,13 +495,23 @@ describe Appsignal::Agent do
441
495
  it "logs the event" do
442
496
  Appsignal.logger.should_receive(:error)
443
497
  end
498
+ end
444
499
 
445
- it "clears the queue" do
446
- subject.should_receive(:clear_queue)
500
+ after { subject.send(:handle_result, code) }
501
+ end
502
+
503
+ context "return values" do
504
+ %w( 200 420 413 429 406 402 401 ).each do |code|
505
+ it "should return true for '#{code}'" do
506
+ subject.send(:handle_result, code).should be_true
447
507
  end
448
508
  end
449
509
 
450
- after { subject.send(:handle_result, code) }
510
+ %w( 500 502 ).each do |code|
511
+ it "should return false for '#{code}'" do
512
+ subject.send(:handle_result, code).should be_false
513
+ end
514
+ end
451
515
  end
452
516
  end
453
517
 
@@ -27,6 +27,7 @@ describe Appsignal::Aggregator do
27
27
  context "adding a transaction with an exception" do
28
28
  let(:transaction) { transaction_with_exception }
29
29
 
30
+ specify { transaction.should_receive(:clear_events!) }
30
31
  specify { transaction.should_receive(:convert_values_to_primitives!) }
31
32
  end
32
33
 
@@ -0,0 +1,190 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Event::MopedEvent do
4
+ let(:event) do
5
+ Appsignal::Event::MopedEvent.new('query.moped', 1, 2, 123, {})
6
+ end
7
+
8
+ describe "#transform_payload" do
9
+ before { event.stub(:payload_from_op => {'foo' => 'bar'}) }
10
+
11
+ it "should map the operations to a normalized payload" do
12
+ expect( event.transform_payload(:ops => [{}]) ).to eq(
13
+ :ops => [{'foo' => 'bar'}]
14
+ )
15
+ end
16
+ end
17
+
18
+ describe "#payload_from_op" do
19
+ context "Moped::Protocol::Query" do
20
+ let(:payload) do
21
+ double(
22
+ :full_collection_name => 'database.collection',
23
+ :selector => {'_id' => 'abc'},
24
+ :class => double(:to_s => 'Moped::Protocol::Command')
25
+ )
26
+ end
27
+
28
+ it "should transform the payload" do
29
+ expect( event.payload_from_op(payload) ).to eq(
30
+ :type => "Command",
31
+ :database => "database.collection",
32
+ :selector => {"_id" => "?"}
33
+ )
34
+ end
35
+ end
36
+
37
+ context "Moped::Protocol::Query" do
38
+ let(:payload) do
39
+ double(
40
+ :full_collection_name => 'database.collection',
41
+ :selector => {'_id' => 'abc'},
42
+ :flags => [],
43
+ :limit => 0,
44
+ :skip => 0,
45
+ :fields => nil,
46
+ :class => double(:to_s => 'Moped::Protocol::Query')
47
+ )
48
+ end
49
+
50
+ it "should transform the payload" do
51
+ expect( event.payload_from_op(payload) ).to eq(
52
+ :type => "Query",
53
+ :database => "database.collection",
54
+ :selector => {"_id" => "?"},
55
+ :flags => [],
56
+ :limit => 0,
57
+ :skip => 0,
58
+ :fields => nil,
59
+ )
60
+ end
61
+ end
62
+
63
+ context "Moped::Protocol::Delete" do
64
+ let(:payload) do
65
+ double(
66
+ :full_collection_name => 'database.collection',
67
+ :selector => {'_id' => 'abc'},
68
+ :flags => [],
69
+ :class => double(:to_s => 'Moped::Protocol::Delete')
70
+ )
71
+ end
72
+
73
+ it "should transform the payload" do
74
+ expect( event.payload_from_op(payload) ).to eq(
75
+ :type => "Delete",
76
+ :database => "database.collection",
77
+ :selector => {"_id" => "?"},
78
+ :flags => []
79
+ )
80
+ end
81
+ end
82
+
83
+ context "Moped::Protocol::Insert" do
84
+ let(:payload) do
85
+ double(
86
+ :full_collection_name => 'database.collection',
87
+ :flags => [],
88
+ :documents => [{'_id' => 'abc'}, {'_id' => 'def'}],
89
+ :class => double(:to_s => 'Moped::Protocol::Insert')
90
+ )
91
+ end
92
+
93
+ it "should transform the payload" do
94
+ expect( event.payload_from_op(payload) ).to eq(
95
+ :type => "Insert",
96
+ :database => "database.collection",
97
+ :flags => [],
98
+ :documents => [{"_id" => "?"}, {"_id" => "?"}]
99
+ )
100
+ end
101
+ end
102
+
103
+ context "Moped::Protocol::Update" do
104
+ let(:payload) do
105
+ double(
106
+ :full_collection_name => 'database.collection',
107
+ :selector => {'_id' => 'abc'},
108
+ :update => {'name' => 'James Bond'},
109
+ :flags => [],
110
+ :class => double(:to_s => 'Moped::Protocol::Update')
111
+ )
112
+ end
113
+
114
+ it "should transform the payload" do
115
+ expect( event.payload_from_op(payload) ).to eq(
116
+ :type => "Update",
117
+ :database => "database.collection",
118
+ :selector => {"_id" => "?"},
119
+ :update => {"name" => "?"},
120
+ :flags => []
121
+ )
122
+ end
123
+ end
124
+
125
+ context "Moped::Protocol::Other" do
126
+ let(:payload) do
127
+ double(
128
+ :full_collection_name => 'database.collection',
129
+ :class => double(:to_s => 'Moped::Protocol::Other')
130
+ )
131
+ end
132
+
133
+ it "should transform the payload" do
134
+ expect( event.payload_from_op(payload) ).to eq(
135
+ :type => "Other",
136
+ :database => "database.collection"
137
+ )
138
+ end
139
+ end
140
+
141
+ context "Moped::Protocol::KillCursors" do
142
+ let(:payload) do
143
+ double(
144
+ :full_collection_name => 'database.collection',
145
+ :class => double(:to_s => 'Moped::Protocol::KillCursors')
146
+ )
147
+ end
148
+
149
+ it "should transform the payload" do
150
+ expect( event.payload_from_op(payload) ).to eq(
151
+ :type => "KillCursors",
152
+ :database => "database.collection"
153
+ )
154
+ end
155
+ end
156
+ end
157
+
158
+ describe "#sanitize" do
159
+ context "when params is a hash" do
160
+ let(:params) { {'foo' => 'bar'} }
161
+
162
+ it "should sanitize all hash values with a questionmark" do
163
+ expect( event.sanitize(params) ).to eq('foo' => '?')
164
+ end
165
+ end
166
+
167
+ context "when params is an array of hashes" do
168
+ let(:params) { [{'foo' => 'bar'}] }
169
+
170
+ it "should sanitize all hash values with a questionmark" do
171
+ expect( event.sanitize(params) ).to eq([{'foo' => '?'}])
172
+ end
173
+ end
174
+
175
+ context "when params is an array of strings " do
176
+ let(:params) { ['foo', 'bar'] }
177
+
178
+ it "should sanitize all hash values with a single questionmark" do
179
+ expect( event.sanitize(params) ).to eq(['?'])
180
+ end
181
+ end
182
+ context "when params is a string" do
183
+ let(:params) { 'bar'}
184
+
185
+ it "should sanitize all hash values with a questionmark" do
186
+ expect( event.sanitize(params) ).to eq('?')
187
+ end
188
+ end
189
+ end
190
+ end