appsignal 1.2.5 → 1.3.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/circle.yml +4 -0
  4. data/ext/agent.yml +11 -11
  5. data/ext/appsignal_extension.c +105 -40
  6. data/lib/appsignal.rb +18 -6
  7. data/lib/appsignal/cli/install.rb +3 -3
  8. data/lib/appsignal/config.rb +19 -5
  9. data/lib/appsignal/event_formatter.rb +3 -2
  10. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +1 -1
  11. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +1 -1
  12. data/lib/appsignal/event_formatter/moped/query_formatter.rb +13 -6
  13. data/lib/appsignal/hooks/mongo_ruby_driver.rb +0 -1
  14. data/lib/appsignal/hooks/net_http.rb +2 -5
  15. data/lib/appsignal/hooks/redis.rb +1 -1
  16. data/lib/appsignal/hooks/sequel.rb +8 -4
  17. data/lib/appsignal/integrations/mongo_ruby_driver.rb +1 -1
  18. data/lib/appsignal/integrations/object.rb +35 -0
  19. data/lib/appsignal/integrations/resque.rb +5 -0
  20. data/lib/appsignal/integrations/sinatra.rb +2 -2
  21. data/lib/appsignal/minutely.rb +41 -0
  22. data/lib/appsignal/params_sanitizer.rb +4 -105
  23. data/lib/appsignal/rack/sinatra_instrumentation.rb +25 -2
  24. data/lib/appsignal/transaction.rb +41 -15
  25. data/lib/appsignal/transmitter.rb +1 -1
  26. data/lib/appsignal/utils.rb +42 -47
  27. data/lib/appsignal/utils/params_sanitizer.rb +58 -0
  28. data/lib/appsignal/utils/query_params_sanitizer.rb +54 -0
  29. data/lib/appsignal/version.rb +1 -1
  30. data/spec/lib/appsignal/config_spec.rb +12 -2
  31. data/spec/lib/appsignal/extension_spec.rb +4 -0
  32. data/spec/lib/appsignal/hooks/net_http_spec.rb +20 -28
  33. data/spec/lib/appsignal/hooks/redis_spec.rb +9 -11
  34. data/spec/lib/appsignal/integrations/object_spec.rb +211 -0
  35. data/spec/lib/appsignal/integrations/sinatra_spec.rb +2 -2
  36. data/spec/lib/appsignal/minutely_spec.rb +54 -0
  37. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +50 -10
  38. data/spec/lib/appsignal/subscriber_spec.rb +5 -6
  39. data/spec/lib/appsignal/transaction_spec.rb +102 -23
  40. data/spec/lib/appsignal/transmitter_spec.rb +1 -1
  41. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +122 -0
  42. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +194 -0
  43. data/spec/lib/appsignal/utils_spec.rb +13 -76
  44. data/spec/lib/appsignal_spec.rb +82 -13
  45. metadata +15 -11
  46. data/lib/appsignal/event_formatter/net_http/request_formatter.rb +0 -13
  47. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +0 -13
  48. data/spec/lib/appsignal/event_formatter/net_http/request_formatter_spec.rb +0 -26
  49. data/spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb +0 -22
  50. data/spec/lib/appsignal/params_sanitizer_spec.rb +0 -200
@@ -6,7 +6,7 @@ describe Appsignal::Transmitter do
6
6
  let(:instance) { Appsignal::Transmitter.new(action, config) }
7
7
 
8
8
  describe "#uri" do
9
- before { Socket.stub(:gethostname => 'app1.local') }
9
+ before { ENV['APPSIGNAL_HOSTNAME'] = 'app1.local' }
10
10
 
11
11
  subject { instance.uri.to_s }
12
12
 
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Utils::ParamsSanitizer do
4
+ let(:file) { uploaded_file }
5
+ let(:params) do
6
+ {
7
+ :text => 'string',
8
+ 'string' => 'string key value',
9
+ :file => file,
10
+ :float => 0.0,
11
+ :bool_true => true,
12
+ :bool_false => false,
13
+ :nil => nil,
14
+ :int => 1,
15
+ :hash => {
16
+ :nested_text => 'string',
17
+ :nested_array => [
18
+ 'something',
19
+ 'else',
20
+ file,
21
+ {
22
+ :key => 'value',
23
+ :file => file
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ end
29
+
30
+ describe ".sanitize" do
31
+ let(:sanitized_params) { described_class.sanitize(params) }
32
+ subject { sanitized_params }
33
+
34
+ it { should be_instance_of Hash }
35
+ its([:text]) { should eq('string') }
36
+ its(['string']) { should eq('string key value') }
37
+ its([:file]) { should be_instance_of String }
38
+ its([:file]) { should include '::UploadedFile' }
39
+ its([:float]) { should eq(0.0) }
40
+ its([:bool_true]) { should be(true) }
41
+ its([:bool_false]) { should be(false) }
42
+ its([:nil]) { should be_nil }
43
+ its([:int]) { should eq(1) }
44
+
45
+ it "does not change the original params" do
46
+ subject
47
+ params[:file].should eq(file)
48
+ params[:hash][:nested_array][2].should eq(file)
49
+ end
50
+
51
+ describe ":hash" do
52
+ subject { sanitized_params[:hash] }
53
+
54
+ it { should be_instance_of Hash }
55
+ its([:nested_text]) { should eq('string') }
56
+
57
+ describe ":nested_array" do
58
+ subject { sanitized_params[:hash][:nested_array] }
59
+
60
+ it { should be_instance_of Array }
61
+ its([0]) { should eq('something') }
62
+ its([1]) { should eq('else') }
63
+ its([2]) { should be_instance_of String }
64
+ its([2]) { should include '::UploadedFile' }
65
+
66
+ describe ":nested_hash" do
67
+ subject { sanitized_params[:hash][:nested_array][3] }
68
+
69
+ it { should be_instance_of Hash }
70
+ its([:key]) { should eq('value') }
71
+ its([:file]) { should be_instance_of String }
72
+ its([:file]) { should include '::UploadedFile' }
73
+ end
74
+ end
75
+ end
76
+
77
+ context "with :filter_parameters option" do
78
+ let(:sanitized_params) do
79
+ described_class.sanitize(params, :filter_parameters => %w(text hash))
80
+ end
81
+ subject { sanitized_params }
82
+
83
+ its([:text]) { should eq(described_class::FILTERED) }
84
+ its([:hash]) { should eq(described_class::FILTERED) }
85
+ its([:file]) { should be_instance_of String }
86
+ its([:file]) { should include '::UploadedFile' }
87
+ its([:float]) { should eq(0.0) }
88
+ its([:bool_true]) { should be(true) }
89
+ its([:bool_false]) { should be(false) }
90
+ its([:nil]) { should be_nil }
91
+ its([:int]) { should eq(1) }
92
+
93
+ context "with strings as key filter values" do
94
+ let(:sanitized_params) do
95
+ described_class.sanitize(params, :filter_parameters => %w(string))
96
+ end
97
+
98
+ its(['string']) { should eq('[FILTERED]') }
99
+ end
100
+
101
+ describe ":hash" do
102
+ let(:sanitized_params) do
103
+ described_class.sanitize(params, :filter_parameters => %w(nested_text))
104
+ end
105
+ subject { sanitized_params[:hash] }
106
+
107
+ its([:nested_text]) { should eq('[FILTERED]') }
108
+
109
+ describe ":nested_array" do
110
+ describe ":nested_hash" do
111
+ let(:sanitized_params) do
112
+ described_class.sanitize(params, :filter_parameters => %w(key))
113
+ end
114
+ subject { sanitized_params[:hash][:nested_array][3] }
115
+
116
+ its([:key]) { should eq('[FILTERED]') }
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,194 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Utils::QueryParamsSanitizer do
4
+ describe ".sanitize" do
5
+ context "when only_top_level = true" do
6
+ subject { described_class.sanitize(value, true) }
7
+
8
+ context "when value is a hash" do
9
+ let(:value) { {'foo' => 'bar'} }
10
+
11
+ it "should only return the first level of the object" do
12
+ expect(subject).to eq('foo' => '?')
13
+ end
14
+
15
+ it "should not modify source value" do
16
+ subject
17
+ expect(value).to eq('foo' => 'bar')
18
+ end
19
+ end
20
+
21
+ context "when value is a nested hash" do
22
+ let(:value) { {'foo' => { 'bar' => 'baz' }} }
23
+
24
+ it "should only return the first level of the object" do
25
+ expect(subject).to eq('foo' => '?')
26
+ end
27
+
28
+ it "should not modify source value" do
29
+ subject
30
+ expect(value).to eq('foo' => { 'bar' => 'baz' })
31
+ end
32
+ end
33
+
34
+ context "when value is an array of hashes" do
35
+ let(:value) { ['foo' => 'bar'] }
36
+
37
+ it "should sanitize all hash values with a questionmark" do
38
+ expect(subject).to eq('foo' => '?')
39
+ end
40
+
41
+ it "should not modify source value" do
42
+ subject
43
+ expect(value).to eq(['foo' => 'bar'])
44
+ end
45
+ end
46
+
47
+ context "when value is an array" do
48
+ let(:value) { ['foo', 'bar'] }
49
+
50
+ it "should only return the first level of the object" do
51
+ expect(subject).to eq('?')
52
+ end
53
+
54
+ it "should not modify source value" do
55
+ subject
56
+ expect(value).to eq(['foo', 'bar'])
57
+ end
58
+ end
59
+
60
+ context "when value is a mixed array" do
61
+ let(:value) { [nil, 'foo', 'bar'] }
62
+
63
+ it "should sanitize all hash values with a single questionmark" do
64
+ expect(subject).to eq('?')
65
+ end
66
+ end
67
+
68
+ context "when value is a string" do
69
+ let(:value) { 'foo' }
70
+
71
+ it "should sanitize all hash values with a questionmark" do
72
+ expect(subject).to eq('?')
73
+ end
74
+ end
75
+ end
76
+
77
+ context "when only_top_level = false" do
78
+ subject { described_class.sanitize(value, false) }
79
+
80
+ context "when value is a hash" do
81
+ let(:value) { {'foo' => 'bar'} }
82
+
83
+ it "should sanitize all hash values with a questionmark" do
84
+ expect(subject).to eq('foo' => '?')
85
+ end
86
+
87
+ it "should not modify source value" do
88
+ subject
89
+ expect(value).to eq('foo' => 'bar')
90
+ end
91
+ end
92
+
93
+ context "when value is a nested hash" do
94
+ let(:value) { {'foo' => { 'bar' => 'baz' }} }
95
+
96
+ it "should replaces values" do
97
+ expect(subject).to eq('foo' => {'bar' => '?'})
98
+ end
99
+
100
+ it "should not modify source value" do
101
+ subject
102
+ expect(value).to eq('foo' => {'bar' => 'baz'})
103
+ end
104
+ end
105
+
106
+ context "when value is an array of hashes" do
107
+ let(:value) { ['foo' => 'bar'] }
108
+
109
+ it "should sanitize all hash values with a questionmark" do
110
+ expect(subject).to eq(['foo' => '?'])
111
+ end
112
+
113
+ it "should not modify source value" do
114
+ subject
115
+ expect(value).to eq(['foo' => 'bar'])
116
+ end
117
+ end
118
+
119
+ context "when value is an array" do
120
+ let(:value) { ['foo', 'bar'] }
121
+
122
+ it "should sanitize all hash values with a single questionmark" do
123
+ expect(subject).to eq(['?'])
124
+ end
125
+ end
126
+
127
+ context "when value is a mixed array" do
128
+ let(:value) { [nil, 'foo', 'bar'] }
129
+
130
+ it "should sanitize all hash values with a single questionmark" do
131
+ expect(subject).to eq(['?'])
132
+ end
133
+ end
134
+
135
+ context "when value is a string" do
136
+ let(:value) { 'bar' }
137
+
138
+ it "should sanitize all hash values with a questionmark" do
139
+ expect(subject).to eq('?')
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "key_sanitizer option" do
146
+ context "without key_sanitizer" do
147
+ subject { described_class.sanitize(value) }
148
+
149
+ context "when dots are in the key" do
150
+ let(:value) { {'foo.bar' => 'bar'} }
151
+
152
+ it "should not sanitize the key" do
153
+ expect(subject).to eql('foo.bar' => '?')
154
+ end
155
+ end
156
+
157
+ context "when key is a symbol" do
158
+ let(:value) { {:ismaster => 'bar'} }
159
+
160
+ it "should sanitize the key" do
161
+ expect(subject).to eql(:ismaster => '?')
162
+ end
163
+ end
164
+ end
165
+
166
+ context "with mongodb key_sanitizer" do
167
+ subject { described_class.sanitize(value, false, :mongodb) }
168
+
169
+ context "when no dots are in the key" do
170
+ let(:value) { {'foo' => 'bar'} }
171
+
172
+ it "should not sanitize the key" do
173
+ expect(subject).to eql('foo' => '?')
174
+ end
175
+ end
176
+
177
+ context "when dots are in the key" do
178
+ let(:value) { {'foo.bar' => 'bar'} }
179
+
180
+ it "should sanitize the key" do
181
+ expect(subject).to eql('foo.?' => '?')
182
+ end
183
+ end
184
+
185
+ context "when key is a symbol" do
186
+ let(:value) { {:ismaster => 'bar'} }
187
+
188
+ it "should sanitize the key" do
189
+ expect(subject).to eql('ismaster' => '?')
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -3,82 +3,29 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Appsignal::Utils do
6
- describe ".sanitize" do
7
- context "when params is a hash" do
8
- let(:params) { {'foo' => 'bar'} }
9
-
10
- it "should sanitize all hash values with a questionmark" do
11
- expect( Appsignal::Utils.sanitize(params) ).to eq('foo' => '?')
12
- end
13
- end
14
-
15
- context "when params is an array of hashes" do
16
- let(:params) { [{'foo' => 'bar'}] }
17
-
18
- it "should sanitize all hash values with a questionmark" do
19
- expect( Appsignal::Utils.sanitize(params) ).to eq([{'foo' => '?'}])
20
- end
21
- end
22
-
23
- context "when params is an array of strings" do
24
- let(:params) { ['foo', 'bar'] }
25
-
26
- it "should sanitize all hash values with a single questionmark" do
27
- expect( Appsignal::Utils.sanitize(params) ).to eq(['?'])
28
- end
29
- end
30
-
31
- context "when params is a mixed array" do
32
- let(:params) { [nil, 'foo', 'bar'] }
33
-
34
- it "should sanitize all hash values with a single questionmark" do
35
- expect( Appsignal::Utils.sanitize(params) ).to eq(['?'])
36
- end
37
- end
38
-
39
- context "when params is a string" do
40
- let(:params) { 'bar'}
41
-
42
- it "should sanitize all hash values with a questionmark" do
43
- expect( Appsignal::Utils.sanitize(params) ).to eq('?')
44
- end
45
- end
46
- end
47
-
48
- describe ".sanitize_key" do
49
- it "should not sanitize key when no key_sanitizer is given" do
50
- expect( Appsignal::Utils.sanitize_key('foo', nil) ).to eql('foo')
51
- end
52
-
53
- context "with mongodb sanitizer" do
54
- it "should not sanitize key when no dots are in the key" do
55
- expect( Appsignal::Utils.sanitize_key('foo', :mongodb) ).to eql('foo')
56
- end
57
-
58
- it "should sanitize key when dots are in the key" do
59
- expect( Appsignal::Utils.sanitize_key('foo.bar', :mongodb) ).to eql('foo.?')
60
- end
61
-
62
- it "should sanitize a symbol" do
63
- expect( Appsignal::Utils.sanitize_key(:ismaster, :mongodb) ).to eql('ismaster')
64
- end
65
- end
66
- end
67
-
68
6
  describe ".json_generate" do
69
7
  subject { Appsignal::Utils.json_generate(body) }
70
8
 
71
9
  context "with a valid body" do
72
- let(:body) { {'the' => 'payload'} }
10
+ let(:body) do
11
+ {
12
+ 'the' => 'payload',
13
+ 1 => true,
14
+ nil => 'test',
15
+ :foo => [1, 2, 'three'],
16
+ 'bar' => nil,
17
+ 'baz' => { 'foo' => 'bar' }
18
+ }
19
+ end
73
20
 
74
- it { should == "{\"the\":\"payload\"}" }
21
+ it { should == %({"the":"payload","1":true,"":"test","foo":[1,2,"three"],"bar":null,"baz":{"foo":"bar"}}) }
75
22
  end
76
23
 
77
24
  context "with a body that contains strings with invalid utf-8 content" do
78
25
  let(:string_with_invalid_utf8) { [0x61, 0x61, 0x85].pack('c*') }
79
26
  let(:body) { {
80
27
  'field_one' => [0x61, 0x61].pack('c*'),
81
- 'field_two' => string_with_invalid_utf8,
28
+ :field_two => string_with_invalid_utf8,
82
29
  'field_three' => [
83
30
  'one', string_with_invalid_utf8
84
31
  ],
@@ -87,17 +34,7 @@ describe Appsignal::Utils do
87
34
  }
88
35
  } }
89
36
 
90
- it { should == "{\"field_one\":\"aa\",\"field_two\":\"aa�\",\"field_three\":[\"one\",\"aa�\"],\"field_four\":{\"one\":\"aa�\"}}" }
91
- end
92
- end
93
-
94
- describe ".encode_utf8" do
95
- subject { Appsignal::Utils.encode_utf8(value) }
96
-
97
- context "value with invalid utf-8 content" do
98
- let(:value) { [0x61, 0x61, 0x85].pack('c*') }
99
-
100
- it { should == "aa�" }
37
+ it { should == %({"field_one":"aa","field_two":"aa","field_three":["one","aa"],"field_four":{"one":"aa"}}) }
101
38
  end
102
39
  end
103
40
  end
@@ -101,6 +101,11 @@ describe Appsignal do
101
101
  Appsignal::Extension.should_receive(:install_gc_event_hooks)
102
102
  Appsignal.start
103
103
  end
104
+
105
+ it "should add the gc probe to minutely" do
106
+ Appsignal::Minutely.should_receive(:add_gc_probe)
107
+ Appsignal.start
108
+ end
104
109
  end
105
110
 
106
111
  context "when allocation tracking and gc instrumentation have been disabled" do
@@ -114,6 +119,33 @@ describe Appsignal do
114
119
  Appsignal::Extension.should_not_receive(:install_gc_event_hooks)
115
120
  Appsignal.start
116
121
  end
122
+
123
+ it "should not add the gc probe to minutely" do
124
+ Appsignal::Minutely.should_not_receive(:add_gc_probe)
125
+ Appsignal.start
126
+ end
127
+ end
128
+
129
+ context "when minutely metrics has been enabled" do
130
+ before do
131
+ Appsignal.config.config_hash[:enable_minutely_probes] = true
132
+ end
133
+
134
+ it "should start minutely" do
135
+ Appsignal::Minutely.should_receive(:start)
136
+ Appsignal.start
137
+ end
138
+ end
139
+
140
+ context "when minutely metrics has been disabled" do
141
+ before do
142
+ Appsignal.config.config_hash[:enable_minutely_probes] = false
143
+ end
144
+
145
+ it "should not start minutely" do
146
+ Appsignal::Minutely.should_not_receive(:start)
147
+ Appsignal.start
148
+ end
117
149
  end
118
150
  end
119
151
 
@@ -266,6 +298,19 @@ describe Appsignal do
266
298
  }.should_not raise_error
267
299
  end
268
300
  end
301
+
302
+ describe ".instrument" do
303
+ it "should not instrument, but still call the block" do
304
+ stub = double
305
+ stub.should_receive(:method_call).and_return('return value')
306
+
307
+ lambda {
308
+ Appsignal.instrument 'name' do
309
+ stub.method_call
310
+ end.should eq 'return value'
311
+ }.should_not raise_error
312
+ end
313
+ end
269
314
  end
270
315
 
271
316
  context "with config and started" do
@@ -393,14 +438,14 @@ describe Appsignal do
393
438
 
394
439
  describe "custom stats" do
395
440
  describe ".set_gauge" do
396
- it "should call set_gauge on the extension with a float" do
441
+ it "should call set_gauge on the extension with a string key and float" do
397
442
  Appsignal::Extension.should_receive(:set_gauge).with('key', 0.1)
398
443
  Appsignal.set_gauge('key', 0.1)
399
444
  end
400
445
 
401
- it "should call set_gauge on the extension with an int" do
446
+ it "should call set_gauge on the extension with a symbol key and int" do
402
447
  Appsignal::Extension.should_receive(:set_gauge).with('key', 1.0)
403
- Appsignal.set_gauge('key', 1)
448
+ Appsignal.set_gauge(:key, 1)
404
449
  end
405
450
 
406
451
  it "should not raise an exception when out of range" do
@@ -413,14 +458,14 @@ describe Appsignal do
413
458
  end
414
459
 
415
460
  describe ".set_host_gauge" do
416
- it "should call set_host_gauge on the extension with a float" do
461
+ it "should call set_host_gauge on the extension with a string key and float" do
417
462
  Appsignal::Extension.should_receive(:set_host_gauge).with('key', 0.1)
418
463
  Appsignal.set_host_gauge('key', 0.1)
419
464
  end
420
465
 
421
- it "should call set_host_gauge on the extension with an int" do
466
+ it "should call set_host_gauge on the extension with a symbol key and int" do
422
467
  Appsignal::Extension.should_receive(:set_host_gauge).with('key', 1.0)
423
- Appsignal.set_host_gauge('key', 1)
468
+ Appsignal.set_host_gauge(:key, 1)
424
469
  end
425
470
 
426
471
  it "should not raise an exception when out of range" do
@@ -433,14 +478,14 @@ describe Appsignal do
433
478
  end
434
479
 
435
480
  describe ".set_process_gauge" do
436
- it "should call set_process_gauge on the extension with a float" do
481
+ it "should call set_process_gauge on the extension with a string key and float" do
437
482
  Appsignal::Extension.should_receive(:set_process_gauge).with('key', 0.1)
438
483
  Appsignal.set_process_gauge('key', 0.1)
439
484
  end
440
485
 
441
- it "should call set_process_gauge on the extension with an int" do
486
+ it "should call set_process_gauge on the extension with a symbol key and int" do
442
487
  Appsignal::Extension.should_receive(:set_process_gauge).with('key', 1.0)
443
- Appsignal.set_process_gauge('key', 1)
488
+ Appsignal.set_process_gauge(:key, 1)
444
489
  end
445
490
 
446
491
  it "should not raise an exception when out of range" do
@@ -453,11 +498,16 @@ describe Appsignal do
453
498
  end
454
499
 
455
500
  describe ".increment_counter" do
456
- it "should call increment_counter on the extension" do
501
+ it "should call increment_counter on the extension with a string key" do
457
502
  Appsignal::Extension.should_receive(:increment_counter).with('key', 1)
458
503
  Appsignal.increment_counter('key')
459
504
  end
460
505
 
506
+ it "should call increment_counter on the extension with a symbol key" do
507
+ Appsignal::Extension.should_receive(:increment_counter).with('key', 1)
508
+ Appsignal.increment_counter(:key)
509
+ end
510
+
461
511
  it "should call increment_counter on the extension with a count" do
462
512
  Appsignal::Extension.should_receive(:increment_counter).with('key', 5)
463
513
  Appsignal.increment_counter('key', 5)
@@ -473,14 +523,14 @@ describe Appsignal do
473
523
  end
474
524
 
475
525
  describe ".add_distribution_value" do
476
- it "should call add_distribution_value on the extension with a float" do
526
+ it "should call add_distribution_value on the extension with a string key and float" do
477
527
  Appsignal::Extension.should_receive(:add_distribution_value).with('key', 0.1)
478
528
  Appsignal.add_distribution_value('key', 0.1)
479
529
  end
480
530
 
481
- it "should call add_distribution_value on the extension with an int" do
531
+ it "should call add_distribution_value on the extension with a symbol key and int" do
482
532
  Appsignal::Extension.should_receive(:add_distribution_value).with('key', 1.0)
483
- Appsignal.add_distribution_value('key', 1)
533
+ Appsignal.add_distribution_value(:key, 1)
484
534
  end
485
535
 
486
536
  it "should not raise an exception when out of range" do
@@ -676,6 +726,25 @@ describe Appsignal do
676
726
  end
677
727
  end
678
728
 
729
+ describe ".instrument" do
730
+ it "should instrument through the transaction" do
731
+ stub = double
732
+ stub.should_receive(:method_call).and_return('return value')
733
+
734
+ transaction.should_receive(:start_event)
735
+ transaction.should_receive(:finish_event).with(
736
+ 'name',
737
+ 'title',
738
+ 'body',
739
+ 0
740
+ )
741
+
742
+ Appsignal.instrument 'name', 'title', 'body' do
743
+ stub.method_call
744
+ end.should eq 'return value'
745
+ end
746
+ end
747
+
679
748
  describe ".without_instrumentation" do
680
749
  let(:transaction) { double }
681
750
  before { Appsignal::Transaction.stub(:current => transaction) }