appsignal 0.10.6 → 0.11.0.beta.1

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