appsignal 2.11.0 → 2.11.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.semaphore/semaphore.yml +197 -0
  4. data/CHANGELOG.md +19 -0
  5. data/README.md +16 -1
  6. data/Rakefile +20 -11
  7. data/build_matrix.yml +13 -0
  8. data/ext/agent.yml +17 -25
  9. data/ext/appsignal_extension.c +1 -1
  10. data/ext/base.rb +12 -9
  11. data/gemfiles/no_dependencies.gemfile +7 -0
  12. data/gemfiles/resque-2.gemfile +0 -1
  13. data/gemfiles/webmachine.gemfile +1 -0
  14. data/lib/appsignal/cli/diagnose/utils.rb +8 -11
  15. data/lib/appsignal/cli/install.rb +5 -8
  16. data/lib/appsignal/helpers/instrumentation.rb +32 -0
  17. data/lib/appsignal/hooks.rb +1 -0
  18. data/lib/appsignal/hooks/action_mailer.rb +22 -0
  19. data/lib/appsignal/hooks/active_support_notifications.rb +72 -0
  20. data/lib/appsignal/hooks/shoryuken.rb +43 -4
  21. data/lib/appsignal/integrations/object.rb +4 -34
  22. data/lib/appsignal/integrations/object_ruby_19.rb +37 -0
  23. data/lib/appsignal/integrations/object_ruby_modern.rb +64 -0
  24. data/lib/appsignal/system.rb +4 -0
  25. data/lib/appsignal/transaction.rb +30 -2
  26. data/lib/appsignal/version.rb +1 -1
  27. data/spec/lib/appsignal/hooks/action_mailer_spec.rb +54 -0
  28. data/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb +35 -0
  29. data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +145 -0
  30. data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +69 -0
  31. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +9 -137
  32. data/spec/lib/appsignal/hooks/resque_spec.rb +10 -2
  33. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +151 -104
  34. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +4 -2
  35. data/spec/lib/appsignal/integrations/object_19_spec.rb +266 -0
  36. data/spec/lib/appsignal/integrations/object_spec.rb +29 -10
  37. data/spec/lib/appsignal/transaction_spec.rb +55 -0
  38. data/spec/lib/appsignal_spec.rb +30 -0
  39. data/spec/support/helpers/dependency_helper.rb +4 -0
  40. metadata +16 -3
@@ -60,7 +60,10 @@ describe Appsignal::Hooks::ResqueHook do
60
60
  "error" => nil,
61
61
  "namespace" => namespace,
62
62
  "metadata" => {},
63
- "sample_data" => { "tags" => { "queue" => queue } }
63
+ "sample_data" => {
64
+ "breadcrumbs" => [],
65
+ "tags" => { "queue" => queue }
66
+ }
64
67
  )
65
68
  expect(transaction_hash["events"].map { |e| e["name"] })
66
69
  .to eql(["perform.resque"])
@@ -84,7 +87,10 @@ describe Appsignal::Hooks::ResqueHook do
84
87
  },
85
88
  "namespace" => namespace,
86
89
  "metadata" => {},
87
- "sample_data" => { "tags" => { "queue" => queue } }
90
+ "sample_data" => {
91
+ "breadcrumbs" => [],
92
+ "tags" => { "queue" => queue }
93
+ }
88
94
  )
89
95
  end
90
96
  end
@@ -118,6 +124,7 @@ describe Appsignal::Hooks::ResqueHook do
118
124
  "metadata" => {},
119
125
  "sample_data" => {
120
126
  "tags" => { "queue" => queue },
127
+ "breadcrumbs" => [],
121
128
  "params" => [
122
129
  "foo",
123
130
  {
@@ -174,6 +181,7 @@ describe Appsignal::Hooks::ResqueHook do
174
181
  "namespace" => namespace,
175
182
  "metadata" => {},
176
183
  "sample_data" => {
184
+ "breadcrumbs" => [],
177
185
  "tags" => { "queue" => queue }
178
186
  # Params will be set by the ActiveJob integration
179
187
  }
@@ -1,55 +1,73 @@
1
1
  describe Appsignal::Hooks::ShoryukenMiddleware do
2
- let(:current_transaction) { background_job_transaction }
3
-
4
2
  class DemoShoryukenWorker
5
3
  end
6
4
 
5
+ let(:time) { "2010-01-01 10:01:00UTC" }
7
6
  let(:worker_instance) { DemoShoryukenWorker.new }
8
- let(:queue) { double }
9
- let(:sqs_msg) { double(:attributes => {}) }
7
+ let(:queue) { "some-funky-queue-name" }
8
+ let(:sqs_msg) { double(:message_id => "msg1", :attributes => {}) }
10
9
  let(:body) { {} }
11
-
12
- before do
13
- allow(Appsignal::Transaction).to receive(:current).and_return(current_transaction)
14
- start_agent
10
+ before(:context) { start_agent }
11
+ around { |example| keep_transactions { example.run } }
12
+
13
+ def perform_job(&block)
14
+ block ||= lambda {}
15
+ Timecop.freeze(Time.parse(time)) do
16
+ Appsignal::Hooks::ShoryukenMiddleware.new.call(
17
+ worker_instance,
18
+ queue,
19
+ sqs_msg,
20
+ body,
21
+ &block
22
+ )
23
+ end
15
24
  end
16
25
 
17
26
  context "with a performance call" do
18
- let(:queue) { "some-funky-queue-name" }
27
+ let(:sent_timestamp) { Time.parse("1976-11-18 0:00:00UTC").to_i * 1000 }
19
28
  let(:sqs_msg) do
20
- double(:attributes => { "SentTimestamp" => Time.parse("1976-11-18 0:00:00UTC").to_i * 1000 })
29
+ double(:message_id => "msg1", :attributes => { "SentTimestamp" => sent_timestamp })
21
30
  end
22
31
 
23
32
  context "with complex argument" do
24
- let(:body) do
25
- {
26
- :foo => "Foo",
27
- :bar => "Bar"
28
- }
29
- end
30
- after do
31
- Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
32
- Appsignal::Hooks::ShoryukenMiddleware.new.call(worker_instance, queue, sqs_msg, body) do
33
- # nothing
34
- end
35
- end
36
- end
33
+ let(:body) { { :foo => "Foo", :bar => "Bar" } }
37
34
 
38
35
  it "wraps the job in a transaction with the correct params" do
39
- expect(Appsignal).to receive(:monitor_transaction).with(
40
- "perform_job.shoryuken",
41
- :class => "DemoShoryukenWorker",
42
- :method => "perform",
43
- :metadata => {
44
- :queue => "some-funky-queue-name",
45
- "SentTimestamp" => 217_123_200_000
46
- },
47
- :params => {
48
- :foo => "Foo",
49
- :bar => "Bar"
50
- },
51
- :queue_start => Time.parse("1976-11-18 0:00:00UTC").utc
36
+ allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original
37
+ expect { perform_job }.to change { created_transactions.length }.by(1)
38
+
39
+ transaction = last_transaction
40
+ expect(transaction).to be_completed
41
+ transaction_hash = transaction.to_h
42
+ expect(transaction_hash).to include(
43
+ "action" => "DemoShoryukenWorker#perform",
44
+ "id" => kind_of(String), # AppSignal generated id
45
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
46
+ "error" => nil
52
47
  )
48
+ expect(transaction_hash["events"].first).to include(
49
+ "allocation_count" => kind_of(Integer),
50
+ "body" => "",
51
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
52
+ "child_allocation_count" => kind_of(Integer),
53
+ "child_duration" => kind_of(Float),
54
+ "child_gc_duration" => kind_of(Float),
55
+ "count" => 1,
56
+ "gc_duration" => kind_of(Float),
57
+ "start" => kind_of(Float),
58
+ "duration" => kind_of(Float),
59
+ "name" => "perform_job.shoryuken",
60
+ "title" => ""
61
+ )
62
+ expect(transaction_hash["sample_data"]).to include(
63
+ "params" => { "foo" => "Foo", "bar" => "Bar" },
64
+ "metadata" => {
65
+ "message_id" => "msg1",
66
+ "queue" => queue,
67
+ "SentTimestamp" => sent_timestamp
68
+ }
69
+ )
70
+ expect(transaction).to have_received(:set_queue_start).with(sent_timestamp)
53
71
  end
54
72
 
55
73
  context "with parameter filtering" do
@@ -57,21 +75,16 @@ describe Appsignal::Hooks::ShoryukenMiddleware do
57
75
  Appsignal.config = project_fixture_config("production")
58
76
  Appsignal.config[:filter_parameters] = ["foo"]
59
77
  end
78
+ after do
79
+ Appsignal.config[:filter_parameters] = []
80
+ end
60
81
 
61
82
  it "filters selected arguments" do
62
- expect(Appsignal).to receive(:monitor_transaction).with(
63
- "perform_job.shoryuken",
64
- :class => "DemoShoryukenWorker",
65
- :method => "perform",
66
- :metadata => {
67
- :queue => "some-funky-queue-name",
68
- "SentTimestamp" => 217_123_200_000
69
- },
70
- :params => {
71
- :foo => "[FILTERED]",
72
- :bar => "Bar"
73
- },
74
- :queue_start => Time.parse("1976-11-18 0:00:00UTC").utc
83
+ perform_job
84
+
85
+ transaction_hash = last_transaction.to_h
86
+ expect(transaction_hash["sample_data"]).to include(
87
+ "params" => { "foo" => "[FILTERED]", "bar" => "Bar" }
75
88
  )
76
89
  end
77
90
  end
@@ -81,23 +94,12 @@ describe Appsignal::Hooks::ShoryukenMiddleware do
81
94
  let(:body) { "foo bar" }
82
95
 
83
96
  it "handles string arguments" do
84
- expect(Appsignal).to receive(:monitor_transaction).with(
85
- "perform_job.shoryuken",
86
- :class => "DemoShoryukenWorker",
87
- :method => "perform",
88
- :metadata => {
89
- :queue => "some-funky-queue-name",
90
- "SentTimestamp" => 217_123_200_000
91
- },
92
- :params => { :params => body },
93
- :queue_start => Time.parse("1976-11-18 0:00:00UTC").utc
94
- )
97
+ perform_job
95
98
 
96
- Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
97
- Appsignal::Hooks::ShoryukenMiddleware.new.call(worker_instance, queue, sqs_msg, body) do
98
- # nothing
99
- end
100
- end
99
+ transaction_hash = last_transaction.to_h
100
+ expect(transaction_hash["sample_data"]).to include(
101
+ "params" => { "params" => body }
102
+ )
101
103
  end
102
104
  end
103
105
 
@@ -105,58 +107,103 @@ describe Appsignal::Hooks::ShoryukenMiddleware do
105
107
  let(:body) { 1 }
106
108
 
107
109
  it "handles primitive types as arguments" do
108
- expect(Appsignal).to receive(:monitor_transaction).with(
109
- "perform_job.shoryuken",
110
- :class => "DemoShoryukenWorker",
111
- :method => "perform",
112
- :metadata => {
113
- :queue => "some-funky-queue-name",
114
- "SentTimestamp" => 217_123_200_000
115
- },
116
- :params => { :params => body },
117
- :queue_start => Time.parse("1976-11-18 0:00:00UTC").utc
118
- )
110
+ perform_job
119
111
 
120
- Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
121
- Appsignal::Hooks::ShoryukenMiddleware.new.call(worker_instance, queue, sqs_msg, body) do
122
- # nothing
123
- end
124
- end
112
+ transaction_hash = last_transaction.to_h
113
+ expect(transaction_hash["sample_data"]).to include(
114
+ "params" => { "params" => body }
115
+ )
125
116
  end
126
117
  end
127
118
  end
128
119
 
129
120
  context "with exception" do
130
- let(:transaction) do
131
- Appsignal::Transaction.new(
132
- SecureRandom.uuid,
133
- Appsignal::Transaction::BACKGROUND_JOB,
134
- Appsignal::Transaction::GenericRequest.new({})
121
+ it "sets the exception on the transaction" do
122
+ expect do
123
+ expect do
124
+ perform_job { raise ExampleException, "error message" }
125
+ end.to raise_error(ExampleException)
126
+ end.to change { created_transactions.length }.by(1)
127
+
128
+ transaction = last_transaction
129
+ expect(transaction).to be_completed
130
+ transaction_hash = transaction.to_h
131
+ expect(transaction_hash).to include(
132
+ "action" => "DemoShoryukenWorker#perform",
133
+ "id" => kind_of(String), # AppSignal generated id
134
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
135
+ "error" => {
136
+ "name" => "ExampleException",
137
+ "message" => "error message",
138
+ "backtrace" => kind_of(String)
139
+ }
135
140
  )
136
141
  end
142
+ end
137
143
 
138
- before do
139
- allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
140
- expect(Appsignal::Transaction).to receive(:create)
141
- .with(
142
- kind_of(String),
143
- Appsignal::Transaction::BACKGROUND_JOB,
144
- kind_of(Appsignal::Transaction::GenericRequest)
145
- ).and_return(transaction)
144
+ context "with batched jobs" do
145
+ let(:sqs_msg) do
146
+ [
147
+ double(
148
+ :message_id => "msg2",
149
+ :attributes => { "SentTimestamp" => (Time.parse("1976-11-18 01:00:00UTC").to_i * 1000).to_s }
150
+ ),
151
+ double(
152
+ :message_id => "msg1",
153
+ :attributes => { "SentTimestamp" => sent_timestamp.to_s }
154
+ )
155
+ ]
146
156
  end
147
-
148
- it "sets the exception on the transaction" do
149
- expect(transaction).to receive(:set_error).with(ExampleException)
157
+ let(:body) do
158
+ [
159
+ "foo bar",
160
+ { :id => "123", :foo => "Foo", :bar => "Bar" }
161
+ ]
150
162
  end
163
+ let(:sent_timestamp) { Time.parse("1976-11-18 01:00:00UTC").to_i * 1000 }
151
164
 
152
- after do
165
+ it "creates a transaction for the batch" do
166
+ allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original
153
167
  expect do
154
- Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
155
- Appsignal::Hooks::ShoryukenMiddleware.new.call(worker_instance, queue, sqs_msg, body) do
156
- raise ExampleException
157
- end
158
- end
159
- end.to raise_error(ExampleException)
168
+ perform_job {}
169
+ end.to change { created_transactions.length }.by(1)
170
+
171
+ transaction = last_transaction
172
+ expect(transaction).to be_completed
173
+ transaction_hash = transaction.to_h
174
+ expect(transaction_hash).to include(
175
+ "action" => "DemoShoryukenWorker#perform",
176
+ "id" => kind_of(String), # AppSignal generated id
177
+ "namespace" => Appsignal::Transaction::BACKGROUND_JOB,
178
+ "error" => nil
179
+ )
180
+ expect(transaction_hash["events"].first).to include(
181
+ "allocation_count" => kind_of(Integer),
182
+ "body" => "",
183
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
184
+ "child_allocation_count" => kind_of(Integer),
185
+ "child_duration" => kind_of(Float),
186
+ "child_gc_duration" => kind_of(Float),
187
+ "count" => 1,
188
+ "gc_duration" => kind_of(Float),
189
+ "start" => kind_of(Float),
190
+ "duration" => kind_of(Float),
191
+ "name" => "perform_job.shoryuken",
192
+ "title" => ""
193
+ )
194
+ expect(transaction_hash["sample_data"]).to include(
195
+ "params" => {
196
+ "msg2" => "foo bar",
197
+ "msg1" => { "id" => "123", "foo" => "Foo", "bar" => "Bar" }
198
+ },
199
+ "metadata" => {
200
+ "batch" => true,
201
+ "queue" => "some-funky-queue-name",
202
+ "SentTimestamp" => sent_timestamp.to_s # Earliest/oldest timestamp from messages
203
+ }
204
+ )
205
+ # Queue time based on earliest/oldest timestamp from messages
206
+ expect(transaction).to have_received(:set_queue_start).with(sent_timestamp)
160
207
  end
161
208
  end
162
209
  end
@@ -262,7 +262,8 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
262
262
  "sample_data" => {
263
263
  "environment" => {},
264
264
  "params" => expected_args,
265
- "tags" => {}
265
+ "tags" => {},
266
+ "breadcrumbs" => []
266
267
  }
267
268
  )
268
269
  expect_transaction_to_have_sidekiq_event(transaction_hash)
@@ -290,7 +291,8 @@ describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
290
291
  "sample_data" => {
291
292
  "environment" => {},
292
293
  "params" => expected_args,
293
- "tags" => {}
294
+ "tags" => {},
295
+ "breadcrumbs" => []
294
296
  }
295
297
  )
296
298
  # TODO: Not available in transaction.to_h yet.
@@ -0,0 +1,266 @@
1
+ require "appsignal/integrations/object"
2
+
3
+ def is_ruby_19
4
+ RUBY_VERSION < "2.0"
5
+ end
6
+
7
+ describe Object do
8
+ describe "#instrument_method" do
9
+ context "with instance method" do
10
+ let(:klass) do
11
+ Class.new do
12
+ def foo(param1, options = {})
13
+ [param1, options]
14
+ end
15
+ appsignal_instrument_method :foo
16
+ end
17
+ end
18
+ let(:instance) { klass.new }
19
+
20
+ def call_with_arguments
21
+ instance.foo(
22
+ "abc",
23
+ :foo => "bar"
24
+ )
25
+ end
26
+
27
+ context "when active" do
28
+ let(:transaction) { http_request_transaction }
29
+ before do
30
+ Appsignal.config = project_fixture_config
31
+ expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
32
+ end
33
+ after { Appsignal.config = nil }
34
+
35
+ context "with anonymous class" do
36
+ it "instruments the method and calls it" do
37
+ expect(Appsignal.active?).to be_truthy
38
+ expect(transaction).to receive(:start_event)
39
+ expect(transaction).to receive(:finish_event).with \
40
+ "foo.AnonymousClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
41
+ expect(call_with_arguments).to eq(["abc", { :foo => "bar" }])
42
+ end
43
+ end
44
+
45
+ context "with named class" do
46
+ before do
47
+ class NamedClass
48
+ def foo
49
+ 1
50
+ end
51
+ appsignal_instrument_method :foo
52
+ end
53
+ end
54
+ after { Object.send(:remove_const, :NamedClass) }
55
+ let(:klass) { NamedClass }
56
+
57
+ it "instruments the method and calls it" do
58
+ expect(Appsignal.active?).to be_truthy
59
+ expect(transaction).to receive(:start_event)
60
+ expect(transaction).to receive(:finish_event).with \
61
+ "foo.NamedClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
62
+ expect(instance.foo).to eq(1)
63
+ end
64
+ end
65
+
66
+ context "with nested named class" do
67
+ before do
68
+ module MyModule
69
+ module NestedModule
70
+ class NamedClass
71
+ def bar
72
+ 2
73
+ end
74
+ appsignal_instrument_method :bar
75
+ end
76
+ end
77
+ end
78
+ end
79
+ after { Object.send(:remove_const, :MyModule) }
80
+ let(:klass) { MyModule::NestedModule::NamedClass }
81
+
82
+ it "instruments the method and calls it" do
83
+ expect(Appsignal.active?).to be_truthy
84
+ expect(transaction).to receive(:start_event)
85
+ expect(transaction).to receive(:finish_event).with \
86
+ "bar.NamedClass.NestedModule.MyModule.other", nil, nil,
87
+ Appsignal::EventFormatter::DEFAULT
88
+ expect(instance.bar).to eq(2)
89
+ end
90
+ end
91
+
92
+ context "with custom name" do
93
+ let(:klass) do
94
+ Class.new do
95
+ def foo
96
+ 1
97
+ end
98
+ appsignal_instrument_method :foo, :name => "my_method.group"
99
+ end
100
+ end
101
+
102
+ it "instruments with custom name" do
103
+ expect(Appsignal.active?).to be_truthy
104
+ expect(transaction).to receive(:start_event)
105
+ expect(transaction).to receive(:finish_event).with \
106
+ "my_method.group", nil, nil, Appsignal::EventFormatter::DEFAULT
107
+ expect(instance.foo).to eq(1)
108
+ end
109
+ end
110
+
111
+ context "with a method given a block" do
112
+ let(:klass) do
113
+ Class.new do
114
+ def foo
115
+ yield
116
+ end
117
+ appsignal_instrument_method :foo
118
+ end
119
+ end
120
+
121
+ it "should yield the block" do
122
+ expect(instance.foo { 42 }).to eq(42)
123
+ end
124
+ end
125
+ end
126
+
127
+ context "when not active" do
128
+ let(:transaction) { Appsignal::Transaction.current }
129
+
130
+ it "does not instrument, but still calls the method" do
131
+ expect(Appsignal.active?).to be_falsy
132
+ expect(transaction).to_not receive(:start_event)
133
+ expect(call_with_arguments).to eq(["abc", { :foo => "bar" }])
134
+ end
135
+ end
136
+ end
137
+
138
+ context "with class method" do
139
+ let(:klass) do
140
+ Class.new do
141
+ def self.bar(param1, options = {})
142
+ [param1, options]
143
+ end
144
+ appsignal_instrument_class_method :bar
145
+ end
146
+ end
147
+ def call_with_arguments
148
+ klass.bar(
149
+ "abc",
150
+ :foo => "bar"
151
+ )
152
+ end
153
+
154
+ context "when active" do
155
+ let(:transaction) { http_request_transaction }
156
+ before do
157
+ Appsignal.config = project_fixture_config
158
+ expect(Appsignal::Transaction).to receive(:current).at_least(:once)
159
+ .and_return(transaction)
160
+ end
161
+ after { Appsignal.config = nil }
162
+
163
+ context "with anonymous class" do
164
+ it "instruments the method and calls it" do
165
+ expect(Appsignal.active?).to be_truthy
166
+ expect(transaction).to receive(:start_event)
167
+ expect(transaction).to receive(:finish_event).with \
168
+ "bar.class_method.AnonymousClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
169
+ expect(call_with_arguments).to eq(["abc", { :foo => "bar" }])
170
+ end
171
+ end
172
+
173
+ context "with named class" do
174
+ before do
175
+ class NamedClass
176
+ def self.bar
177
+ 2
178
+ end
179
+ appsignal_instrument_class_method :bar
180
+ end
181
+ end
182
+ after { Object.send(:remove_const, :NamedClass) }
183
+ let(:klass) { NamedClass }
184
+
185
+ it "instruments the method and calls it" do
186
+ expect(Appsignal.active?).to be_truthy
187
+ expect(transaction).to receive(:start_event)
188
+ expect(transaction).to receive(:finish_event).with \
189
+ "bar.class_method.NamedClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
190
+ expect(klass.bar).to eq(2)
191
+ end
192
+
193
+ context "with nested named class" do
194
+ before do
195
+ module MyModule
196
+ module NestedModule
197
+ class NamedClass
198
+ def self.bar
199
+ 2
200
+ end
201
+ appsignal_instrument_class_method :bar
202
+ end
203
+ end
204
+ end
205
+ end
206
+ after { Object.send(:remove_const, :MyModule) }
207
+ let(:klass) { MyModule::NestedModule::NamedClass }
208
+
209
+ it "instruments the method and calls it" do
210
+ expect(Appsignal.active?).to be_truthy
211
+ expect(transaction).to receive(:start_event)
212
+ expect(transaction).to receive(:finish_event).with \
213
+ "bar.class_method.NamedClass.NestedModule.MyModule.other", nil, nil,
214
+ Appsignal::EventFormatter::DEFAULT
215
+ expect(klass.bar).to eq(2)
216
+ end
217
+ end
218
+ end
219
+
220
+ context "with custom name" do
221
+ let(:klass) do
222
+ Class.new do
223
+ def self.bar
224
+ 2
225
+ end
226
+ appsignal_instrument_class_method :bar, :name => "my_method.group"
227
+ end
228
+ end
229
+
230
+ it "instruments with custom name" do
231
+ expect(Appsignal.active?).to be_truthy
232
+ expect(transaction).to receive(:start_event)
233
+ expect(transaction).to receive(:finish_event).with \
234
+ "my_method.group", nil, nil, Appsignal::EventFormatter::DEFAULT
235
+ expect(klass.bar).to eq(2)
236
+ end
237
+ end
238
+
239
+ context "with a method given a block" do
240
+ let(:klass) do
241
+ Class.new do
242
+ def self.bar
243
+ yield
244
+ end
245
+ appsignal_instrument_class_method :bar
246
+ end
247
+ end
248
+
249
+ it "should yield the block" do
250
+ expect(klass.bar { 42 }).to eq(42)
251
+ end
252
+ end
253
+ end
254
+
255
+ context "when not active" do
256
+ let(:transaction) { Appsignal::Transaction.current }
257
+
258
+ it "does not instrument, but still call the method" do
259
+ expect(Appsignal.active?).to be_falsy
260
+ expect(transaction).to_not receive(:start_event)
261
+ expect(call_with_arguments).to eq(["abc", { :foo => "bar" }])
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end