appsignal 2.2.1 → 2.3.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/appsignal.gemspec +1 -2
- data/ext/agent.yml +11 -11
- data/ext/appsignal_extension.c +17 -1
- data/lib/appsignal/config.rb +7 -4
- data/lib/appsignal/hooks.rb +1 -6
- data/lib/appsignal/hooks/action_cable.rb +113 -0
- data/lib/appsignal/hooks/active_support_notifications.rb +12 -4
- data/lib/appsignal/hooks/shoryuken.rb +11 -11
- data/lib/appsignal/hooks/sidekiq.rb +5 -3
- data/lib/appsignal/integrations/delayed_job_plugin.rb +5 -1
- data/lib/appsignal/integrations/resque_active_job.rb +4 -1
- data/lib/appsignal/transaction.rb +40 -13
- data/lib/appsignal/version.rb +1 -1
- data/spec/lib/appsignal/config_spec.rb +8 -1
- data/spec/lib/appsignal/hooks/action_cable_spec.rb +370 -0
- data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +39 -7
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +179 -34
- data/spec/lib/appsignal/hooks/shoryuken_spec.rb +125 -30
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +140 -21
- data/spec/lib/appsignal/hooks_spec.rb +0 -21
- data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +62 -17
- data/spec/lib/appsignal/integrations/resque_spec.rb +24 -12
- data/spec/lib/appsignal/transaction_spec.rb +230 -91
- data/spec/spec_helper.rb +8 -2
- data/spec/support/helpers/dependency_helper.rb +4 -0
- data/spec/support/helpers/env_helpers.rb +1 -1
- data/spec/support/helpers/log_helpers.rb +17 -0
- data/spec/support/matchers/contains_log.rb +7 -0
- data/spec/support/shared_examples/instrument.rb +2 -2
- metadata +13 -20
@@ -2,13 +2,14 @@ describe Appsignal::Hooks::SidekiqPlugin do
|
|
2
2
|
let(:worker) { double }
|
3
3
|
let(:queue) { double }
|
4
4
|
let(:current_transaction) { background_job_transaction }
|
5
|
+
let(:args) { ["Model", 1] }
|
5
6
|
let(:item) do
|
6
7
|
{
|
7
8
|
"class" => "TestClass",
|
8
9
|
"retry_count" => 0,
|
9
10
|
"queue" => "default",
|
10
11
|
"enqueued_at" => Time.parse("01-01-2001 10:00:00UTC"),
|
11
|
-
"args" =>
|
12
|
+
"args" => args,
|
12
13
|
"extra" => "data"
|
13
14
|
}
|
14
15
|
end
|
@@ -20,7 +21,15 @@ describe Appsignal::Hooks::SidekiqPlugin do
|
|
20
21
|
end
|
21
22
|
|
22
23
|
context "with a performance call" do
|
23
|
-
|
24
|
+
after do
|
25
|
+
Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
|
26
|
+
Appsignal::Hooks::SidekiqPlugin.new.call(worker, item, queue) do
|
27
|
+
# nothing
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "wraps it in a transaction with the correct params" do
|
24
33
|
expect(Appsignal).to receive(:monitor_transaction).with(
|
25
34
|
"perform_job.sidekiq",
|
26
35
|
:class => "TestClass",
|
@@ -30,12 +39,69 @@ describe Appsignal::Hooks::SidekiqPlugin do
|
|
30
39
|
"queue" => "default",
|
31
40
|
"extra" => "data"
|
32
41
|
},
|
33
|
-
:params =>
|
42
|
+
:params => ["Model", 1],
|
34
43
|
:queue_start => Time.parse("01-01-2001 10:00:00UTC"),
|
35
44
|
:queue_time => 60_000.to_f
|
36
45
|
)
|
37
46
|
end
|
38
47
|
|
48
|
+
context "with more complex arguments" do
|
49
|
+
let(:default_params) do
|
50
|
+
{
|
51
|
+
:class => "TestClass",
|
52
|
+
:method => "perform",
|
53
|
+
:metadata => {
|
54
|
+
"retry_count" => "0",
|
55
|
+
"queue" => "default",
|
56
|
+
"extra" => "data"
|
57
|
+
},
|
58
|
+
:params => {
|
59
|
+
:foo => "Foo",
|
60
|
+
:bar => "Bar"
|
61
|
+
},
|
62
|
+
:queue_start => Time.parse("01-01-2001 10:00:00UTC"),
|
63
|
+
:queue_time => 60_000.to_f
|
64
|
+
}
|
65
|
+
end
|
66
|
+
let(:args) do
|
67
|
+
{
|
68
|
+
:foo => "Foo",
|
69
|
+
:bar => "Bar"
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
it "adds the more complex arguments" do
|
74
|
+
expect(Appsignal).to receive(:monitor_transaction).with(
|
75
|
+
"perform_job.sidekiq",
|
76
|
+
default_params.merge(
|
77
|
+
:params => {
|
78
|
+
:foo => "Foo",
|
79
|
+
:bar => "Bar"
|
80
|
+
}
|
81
|
+
)
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with parameter filtering" do
|
86
|
+
before do
|
87
|
+
Appsignal.config = project_fixture_config("production")
|
88
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
89
|
+
end
|
90
|
+
|
91
|
+
it "filters selected arguments" do
|
92
|
+
expect(Appsignal).to receive(:monitor_transaction).with(
|
93
|
+
"perform_job.sidekiq",
|
94
|
+
default_params.merge(
|
95
|
+
:params => {
|
96
|
+
:foo => "[FILTERED]",
|
97
|
+
:bar => "Bar"
|
98
|
+
}
|
99
|
+
)
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
39
105
|
context "when wrapped by ActiveJob" do
|
40
106
|
let(:item) do
|
41
107
|
{
|
@@ -46,7 +112,7 @@ describe Appsignal::Hooks::SidekiqPlugin do
|
|
46
112
|
"job_class" => "TestJob",
|
47
113
|
"job_id" => "23e79d48-6966-40d0-b2d4-f7938463a263",
|
48
114
|
"queue_name" => "default",
|
49
|
-
"arguments" =>
|
115
|
+
"arguments" => args
|
50
116
|
}],
|
51
117
|
"retry" => true,
|
52
118
|
"jid" => "efb140489485999d32b5504c",
|
@@ -54,50 +120,103 @@ describe Appsignal::Hooks::SidekiqPlugin do
|
|
54
120
|
"enqueued_at" => Time.parse("01-01-2001 10:00:00UTC").to_f
|
55
121
|
}
|
56
122
|
end
|
57
|
-
|
58
|
-
|
59
|
-
expect(Appsignal).to receive(:monitor_transaction).with(
|
60
|
-
"perform_job.sidekiq",
|
123
|
+
let(:default_params) do
|
124
|
+
{
|
61
125
|
:class => "TestClass",
|
62
126
|
:method => "perform",
|
63
127
|
:metadata => {
|
64
128
|
"queue" => "default"
|
65
129
|
},
|
66
|
-
:params => %w(Model 1),
|
67
130
|
:queue_start => Time.parse("01-01-2001 10:00:00UTC").to_f,
|
68
131
|
:queue_time => 60_000.to_f
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
it "wraps it in a transaction with the correct params" do
|
136
|
+
expect(Appsignal).to receive(:monitor_transaction).with(
|
137
|
+
"perform_job.sidekiq",
|
138
|
+
default_params.merge(:params => ["Model", 1])
|
69
139
|
)
|
70
140
|
end
|
71
|
-
end
|
72
141
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
142
|
+
context "with more complex arguments" do
|
143
|
+
let(:args) do
|
144
|
+
{
|
145
|
+
:foo => "Foo",
|
146
|
+
:bar => "Bar"
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
it "adds the more complex arguments" do
|
151
|
+
expect(Appsignal).to receive(:monitor_transaction).with(
|
152
|
+
"perform_job.sidekiq",
|
153
|
+
default_params.merge(
|
154
|
+
:params => {
|
155
|
+
:foo => "Foo",
|
156
|
+
:bar => "Bar"
|
157
|
+
}
|
158
|
+
)
|
159
|
+
)
|
160
|
+
end
|
161
|
+
|
162
|
+
context "with parameter filtering" do
|
163
|
+
before do
|
164
|
+
Appsignal.config = project_fixture_config("production")
|
165
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
166
|
+
end
|
167
|
+
|
168
|
+
it "filters selected arguments" do
|
169
|
+
expect(Appsignal).to receive(:monitor_transaction).with(
|
170
|
+
"perform_job.sidekiq",
|
171
|
+
default_params.merge(
|
172
|
+
:params => {
|
173
|
+
:foo => "[FILTERED]",
|
174
|
+
:bar => "Bar"
|
175
|
+
}
|
176
|
+
)
|
177
|
+
)
|
178
|
+
end
|
77
179
|
end
|
78
180
|
end
|
79
181
|
end
|
80
182
|
end
|
81
183
|
|
82
184
|
context "with an erroring call" do
|
83
|
-
let(:error) { VerySpecificError
|
185
|
+
let(:error) { VerySpecificError }
|
186
|
+
let(:transaction) do
|
187
|
+
Appsignal::Transaction.new(
|
188
|
+
SecureRandom.uuid,
|
189
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
190
|
+
Appsignal::Transaction::GenericRequest.new({})
|
191
|
+
)
|
192
|
+
end
|
193
|
+
before do
|
194
|
+
allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
|
195
|
+
expect(Appsignal::Transaction).to receive(:create)
|
196
|
+
.with(
|
197
|
+
kind_of(String),
|
198
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
199
|
+
kind_of(Appsignal::Transaction::GenericRequest)
|
200
|
+
).and_return(transaction)
|
201
|
+
end
|
84
202
|
|
85
|
-
it "
|
86
|
-
|
203
|
+
it "adds the error to the transaction" do
|
204
|
+
expect(transaction).to receive(:set_error).with(error)
|
205
|
+
expect(transaction).to receive(:complete)
|
87
206
|
end
|
88
207
|
|
89
208
|
after do
|
90
|
-
|
209
|
+
expect do
|
91
210
|
Timecop.freeze(Time.parse("01-01-2001 10:01:00UTC")) do
|
92
211
|
Appsignal::Hooks::SidekiqPlugin.new.call(worker, item, queue) do
|
93
212
|
raise error
|
94
213
|
end
|
95
214
|
end
|
96
|
-
|
97
|
-
end
|
215
|
+
end.to raise_error(error)
|
98
216
|
end
|
99
217
|
end
|
100
218
|
|
219
|
+
# TODO: Don't test (what are basically) private methods
|
101
220
|
describe "#formatted_data" do
|
102
221
|
let(:item) do
|
103
222
|
{
|
@@ -106,7 +225,7 @@ describe Appsignal::Hooks::SidekiqPlugin do
|
|
106
225
|
}
|
107
226
|
end
|
108
227
|
|
109
|
-
it "
|
228
|
+
it "only adds items to the hash that do not appear in JOB_KEYS" do
|
110
229
|
expect(plugin.formatted_metadata(item)).to eq("foo" => "bar")
|
111
230
|
end
|
112
231
|
end
|
@@ -192,25 +192,4 @@ describe Appsignal::Hooks::Helpers do
|
|
192
192
|
it { is_expected.to eq "1" }
|
193
193
|
end
|
194
194
|
end
|
195
|
-
|
196
|
-
describe "#format_args" do
|
197
|
-
let(:object) { Object.new }
|
198
|
-
let(:args) do
|
199
|
-
[
|
200
|
-
"Model",
|
201
|
-
1,
|
202
|
-
object,
|
203
|
-
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
204
|
-
]
|
205
|
-
end
|
206
|
-
|
207
|
-
it "should format the arguments" do
|
208
|
-
expect(with_helpers.format_args(args)).to eq([
|
209
|
-
"Model",
|
210
|
-
"1",
|
211
|
-
object.inspect,
|
212
|
-
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ..."
|
213
|
-
])
|
214
|
-
end
|
215
|
-
end
|
216
195
|
end
|
@@ -1,38 +1,83 @@
|
|
1
1
|
if DependencyHelper.resque_present? && DependencyHelper.active_job_present?
|
2
|
-
|
3
|
-
let(:file) { File.expand_path("lib/appsignal/integrations/resque_active_job.rb") }
|
2
|
+
require "active_job"
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
describe Appsignal::Integrations::ResqueActiveJobPlugin do
|
5
|
+
let(:file) { File.expand_path("lib/appsignal/integrations/resque_active_job.rb") }
|
6
|
+
let(:args) { "argument" }
|
7
|
+
let(:job) { TestActiveJob.new(args) }
|
8
|
+
before do
|
9
|
+
load file
|
10
|
+
start_agent
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
class TestActiveJob < ActiveJob::Base
|
13
|
+
include Appsignal::Integrations::ResqueActiveJobPlugin
|
12
14
|
|
13
|
-
|
14
|
-
end
|
15
|
+
def perform(_)
|
15
16
|
end
|
16
17
|
end
|
18
|
+
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
it "wraps it in a transaction with the correct params" do
|
21
|
+
expect(Appsignal).to receive(:monitor_single_transaction).with(
|
22
|
+
"perform_job.resque",
|
23
|
+
:class => "TestActiveJob",
|
24
|
+
:method => "perform",
|
25
|
+
:params => ["argument"],
|
26
|
+
:metadata => {
|
27
|
+
:id => kind_of(String),
|
28
|
+
:queue => "default"
|
29
|
+
}
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with complex arguments" do
|
34
|
+
let(:args) do
|
35
|
+
{
|
36
|
+
:foo => "Foo",
|
37
|
+
:bar => "Bar"
|
38
|
+
}
|
39
|
+
end
|
21
40
|
|
22
|
-
|
41
|
+
it "truncates large argument values" do
|
42
|
+
expect(Appsignal).to receive(:monitor_single_transaction).with(
|
43
|
+
"perform_job.resque",
|
44
|
+
:class => "TestActiveJob",
|
45
|
+
:method => "perform",
|
46
|
+
:params => [
|
47
|
+
:foo => "Foo",
|
48
|
+
:bar => "Bar"
|
49
|
+
],
|
50
|
+
:metadata => {
|
51
|
+
:id => kind_of(String),
|
52
|
+
:queue => "default"
|
53
|
+
}
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
context "with parameter filtering" do
|
58
|
+
before do
|
59
|
+
Appsignal.config = project_fixture_config("production")
|
60
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
61
|
+
end
|
62
|
+
|
63
|
+
it "filters selected arguments" do
|
23
64
|
expect(Appsignal).to receive(:monitor_single_transaction).with(
|
24
65
|
"perform_job.resque",
|
25
66
|
:class => "TestActiveJob",
|
26
67
|
:method => "perform",
|
27
|
-
:params => [
|
68
|
+
:params => [
|
69
|
+
:foo => "[FILTERED]",
|
70
|
+
:bar => "Bar"
|
71
|
+
],
|
28
72
|
:metadata => {
|
29
|
-
:id =>
|
73
|
+
:id => kind_of(String),
|
30
74
|
:queue => "default"
|
31
75
|
}
|
32
76
|
)
|
33
77
|
end
|
34
|
-
after { job.perform_now }
|
35
78
|
end
|
36
79
|
end
|
80
|
+
|
81
|
+
after { job.perform_now }
|
37
82
|
end
|
38
83
|
end
|
@@ -33,11 +33,11 @@ if DependencyHelper.resque_present?
|
|
33
33
|
end
|
34
34
|
|
35
35
|
context "without exception" do
|
36
|
-
it "
|
36
|
+
it "creates a new transaction" do
|
37
37
|
expect(Appsignal::Transaction).to receive(:create).and_return(transaction)
|
38
38
|
end
|
39
39
|
|
40
|
-
it "
|
40
|
+
it "wraps it in a transaction with the correct params" do
|
41
41
|
expect(Appsignal).to receive(:monitor_transaction).with(
|
42
42
|
"perform_job.resque",
|
43
43
|
:class => "TestJob",
|
@@ -45,7 +45,7 @@ if DependencyHelper.resque_present?
|
|
45
45
|
)
|
46
46
|
end
|
47
47
|
|
48
|
-
it "
|
48
|
+
it "closes the transaction" do
|
49
49
|
expect(transaction).to receive(:complete)
|
50
50
|
end
|
51
51
|
|
@@ -54,17 +54,29 @@ if DependencyHelper.resque_present?
|
|
54
54
|
|
55
55
|
context "with exception" do
|
56
56
|
let(:job) { ::Resque::Job.new("default", "class" => "BrokenTestJob") }
|
57
|
+
let(:transaction) do
|
58
|
+
Appsignal::Transaction.new(
|
59
|
+
SecureRandom.uuid,
|
60
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
61
|
+
Appsignal::Transaction::GenericRequest.new({})
|
62
|
+
)
|
63
|
+
end
|
64
|
+
before do
|
65
|
+
allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
|
66
|
+
expect(Appsignal::Transaction).to receive(:create)
|
67
|
+
.with(
|
68
|
+
kind_of(String),
|
69
|
+
Appsignal::Transaction::BACKGROUND_JOB,
|
70
|
+
kind_of(Appsignal::Transaction::GenericRequest)
|
71
|
+
).and_return(transaction)
|
72
|
+
end
|
57
73
|
|
58
|
-
it "
|
59
|
-
|
74
|
+
it "sets the exception on the transaction" do
|
75
|
+
expect(transaction).to receive(:set_error).with(VerySpecificError)
|
60
76
|
end
|
61
77
|
|
62
78
|
after do
|
63
|
-
|
64
|
-
job.perform
|
65
|
-
rescue VerySpecificError
|
66
|
-
# Do nothing
|
67
|
-
end
|
79
|
+
expect { job.perform }.to raise_error(VerySpecificError)
|
68
80
|
end
|
69
81
|
end
|
70
82
|
end
|
@@ -73,8 +85,8 @@ if DependencyHelper.resque_present?
|
|
73
85
|
context "without resque" do
|
74
86
|
before(:context) { Object.send(:remove_const, :Resque) }
|
75
87
|
|
76
|
-
|
77
|
-
|
88
|
+
it { expect { ::Resque }.to raise_error(NameError) }
|
89
|
+
it { expect { load file }.to_not raise_error }
|
78
90
|
end
|
79
91
|
end
|
80
92
|
end
|
@@ -3,151 +3,212 @@ describe Appsignal::Transaction do
|
|
3
3
|
start_agent
|
4
4
|
end
|
5
5
|
|
6
|
-
let(:
|
7
|
-
let(:
|
8
|
-
let(:
|
9
|
-
let(:
|
10
|
-
let(:
|
11
|
-
let(:
|
12
|
-
let(:
|
6
|
+
let(:transaction_id) { "1" }
|
7
|
+
let(:time) { Time.at(fixed_time) }
|
8
|
+
let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
|
9
|
+
let(:env) { {} }
|
10
|
+
let(:merged_env) { http_request_env_with_data(env) }
|
11
|
+
let(:options) { {} }
|
12
|
+
let(:request) { Rack::Request.new(merged_env) }
|
13
|
+
let(:transaction) { Appsignal::Transaction.new(transaction_id, namespace, request, options) }
|
14
|
+
let(:log) { StringIO.new }
|
13
15
|
|
14
16
|
before { Timecop.freeze(time) }
|
15
|
-
after
|
17
|
+
after { Timecop.return }
|
18
|
+
around do |example|
|
19
|
+
use_logger_with log do
|
20
|
+
example.run
|
21
|
+
end
|
22
|
+
end
|
16
23
|
|
17
24
|
describe "class methods" do
|
25
|
+
def current_transaction
|
26
|
+
Appsignal::Transaction.current
|
27
|
+
end
|
28
|
+
|
18
29
|
describe ".create" do
|
19
|
-
|
20
|
-
|
30
|
+
def create_transaction(id = transaction_id)
|
31
|
+
Appsignal::Transaction.create(id, namespace, request, options)
|
32
|
+
end
|
21
33
|
|
22
|
-
|
34
|
+
context "when no transaction is running" do
|
35
|
+
let!(:transaction) { create_transaction }
|
23
36
|
|
24
|
-
|
25
|
-
|
37
|
+
it "returns the created transaction" do
|
38
|
+
expect(transaction).to be_a Appsignal::Transaction
|
39
|
+
expect(transaction.transaction_id).to eq transaction_id
|
40
|
+
expect(transaction.namespace).to eq namespace
|
41
|
+
expect(transaction.request).to eq request
|
26
42
|
|
27
|
-
|
28
|
-
|
43
|
+
expect(transaction.to_h).to include(
|
44
|
+
"id" => transaction_id,
|
45
|
+
"namespace" => namespace
|
46
|
+
)
|
47
|
+
end
|
29
48
|
|
30
|
-
|
31
|
-
|
32
|
-
|
49
|
+
it "assigns the transaction to current" do
|
50
|
+
expect(transaction).to eq current_transaction
|
51
|
+
end
|
33
52
|
end
|
34
53
|
|
35
54
|
context "when a transaction is already running" do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
55
|
+
before { create_transaction }
|
56
|
+
|
57
|
+
it "does not create a new transaction, but returns the current transaction" do
|
58
|
+
expect do
|
59
|
+
new_transaction = create_transaction("2")
|
60
|
+
expect(new_transaction).to eq(current_transaction)
|
61
|
+
expect(new_transaction.transaction_id).to eq(transaction_id)
|
62
|
+
end.to_not change { current_transaction }
|
43
63
|
end
|
44
64
|
|
45
|
-
it "
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
65
|
+
it "logs a debug message" do
|
66
|
+
create_transaction("2")
|
67
|
+
expect(log_contents(log)).to contains_log :debug,
|
68
|
+
"Trying to start new transaction with id '2', but a " \
|
69
|
+
"transaction with id '#{transaction_id}' is already " \
|
70
|
+
"running. Using transaction '#{transaction_id}'."
|
50
71
|
end
|
51
72
|
|
52
|
-
context "with option
|
53
|
-
|
54
|
-
|
55
|
-
expect(
|
56
|
-
|
57
|
-
|
73
|
+
context "with option :force => true" do
|
74
|
+
it "returns the newly created (and current) transaction" do
|
75
|
+
original_transaction = current_transaction
|
76
|
+
expect(original_transaction).to_not be_nil
|
77
|
+
expect(current_transaction.transaction_id).to eq transaction_id
|
78
|
+
|
79
|
+
options[:force] = true
|
80
|
+
expect(create_transaction("2")).to_not eq original_transaction
|
81
|
+
expect(current_transaction.transaction_id).to eq "2"
|
58
82
|
end
|
59
83
|
end
|
60
84
|
end
|
61
85
|
end
|
62
86
|
|
63
87
|
describe ".current" do
|
64
|
-
before { Thread.current[:appsignal_transaction] = transaction }
|
65
|
-
|
66
88
|
subject { Appsignal::Transaction.current }
|
67
89
|
|
68
|
-
context "
|
69
|
-
|
70
|
-
|
71
|
-
it "should return the correct transaction" do
|
72
|
-
is_expected.to eq transaction
|
90
|
+
context "when there is a current transaction" do
|
91
|
+
let!(:transaction) do
|
92
|
+
Appsignal::Transaction.create(transaction_id, namespace, request, options)
|
73
93
|
end
|
74
94
|
|
75
|
-
it "
|
76
|
-
expect(subject
|
95
|
+
it "reads :appsignal_transaction from the current Thread" do
|
96
|
+
expect(subject).to eq Thread.current[:appsignal_transaction]
|
97
|
+
expect(subject).to eq transaction
|
77
98
|
end
|
78
|
-
end
|
79
99
|
|
80
|
-
|
81
|
-
|
82
|
-
|
100
|
+
it "is not a NilTransaction" do
|
101
|
+
expect(subject.nil_transaction?).to eq false
|
102
|
+
expect(subject).to be_a Appsignal::Transaction
|
83
103
|
end
|
104
|
+
end
|
84
105
|
|
85
|
-
|
86
|
-
|
106
|
+
context "when there is no current transaction" do
|
107
|
+
it "has no :appsignal_transaction registered on the current Thread" do
|
108
|
+
expect(Thread.current[:appsignal_transaction]).to be_nil
|
87
109
|
end
|
88
110
|
|
89
|
-
it "
|
90
|
-
expect(subject.nil_transaction?).to
|
111
|
+
it "returns a NilTransaction stub" do
|
112
|
+
expect(subject.nil_transaction?).to eq true
|
113
|
+
expect(subject).to be_a Appsignal::Transaction::NilTransaction
|
91
114
|
end
|
92
115
|
end
|
93
116
|
end
|
94
117
|
|
95
|
-
describe "complete_current!" do
|
96
|
-
|
118
|
+
describe ".complete_current!" do
|
119
|
+
let!(:transaction) { Appsignal::Transaction.create(transaction_id, namespace, options) }
|
97
120
|
|
98
|
-
it "
|
99
|
-
expect(
|
121
|
+
it "completes the current transaction" do
|
122
|
+
expect(transaction).to eq current_transaction
|
123
|
+
expect(transaction).to receive(:complete).and_call_original
|
100
124
|
|
101
125
|
Appsignal::Transaction.complete_current!
|
102
|
-
|
103
|
-
expect(Thread.current[:appsignal_transaction]).to be_nil
|
104
126
|
end
|
105
127
|
|
106
|
-
it "
|
107
|
-
expect
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
expect(Thread.current[:appsignal_transaction]).to be_nil
|
128
|
+
it "unsets the current transaction on the current Thread" do
|
129
|
+
expect do
|
130
|
+
Appsignal::Transaction.complete_current!
|
131
|
+
end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
|
112
132
|
end
|
113
133
|
|
114
|
-
context "
|
115
|
-
|
116
|
-
expect(
|
117
|
-
|
118
|
-
Appsignal::Transaction.current.discard!
|
119
|
-
expect(Appsignal::Transaction.current.discarded?).to be_truthy
|
134
|
+
context "when encountering an error while completing" do
|
135
|
+
before do
|
136
|
+
expect(transaction).to receive(:complete).and_raise VerySpecificError
|
137
|
+
end
|
120
138
|
|
139
|
+
it "logs an error message" do
|
121
140
|
Appsignal::Transaction.complete_current!
|
122
|
-
|
123
|
-
|
141
|
+
expect(log_contents(log)).to contains_log :error,
|
142
|
+
"Failed to complete transaction ##{transaction.transaction_id}. VerySpecificError"
|
124
143
|
end
|
125
144
|
|
126
|
-
it "
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
expect(Appsignal::Transaction.current.discarded?).to be_falsy
|
145
|
+
it "clears the current transaction" do
|
146
|
+
expect do
|
147
|
+
Appsignal::Transaction.complete_current!
|
148
|
+
end.to change { Thread.current[:appsignal_transaction] }.from(transaction).to(nil)
|
131
149
|
end
|
132
150
|
end
|
133
151
|
end
|
134
152
|
end
|
135
153
|
|
136
154
|
describe "#complete" do
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
155
|
+
context "when transaction is being sampled" do
|
156
|
+
it "samples data" do
|
157
|
+
expect(transaction.ext).to receive(:finish).and_return(true)
|
158
|
+
# Stub call to extension, because that would remove the transaction
|
159
|
+
# from the extension.
|
160
|
+
expect(transaction.ext).to receive(:complete)
|
161
|
+
|
162
|
+
transaction.set_tags(:foo => "bar")
|
163
|
+
transaction.complete
|
164
|
+
expect(transaction.to_h["sample_data"]).to include(
|
165
|
+
"tags" => { "foo" => "bar" }
|
166
|
+
)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "when transaction is not being sampled" do
|
171
|
+
it "does not sample data" do
|
172
|
+
expect(transaction).to_not receive(:sample_data)
|
173
|
+
expect(transaction.ext).to receive(:finish).and_return(false)
|
174
|
+
expect(transaction.ext).to receive(:complete).and_call_original
|
141
175
|
|
142
|
-
|
176
|
+
transaction.complete
|
177
|
+
end
|
143
178
|
end
|
144
179
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
180
|
+
context "when a transaction is marked as discarded" do
|
181
|
+
it "does not complete the transaction" do
|
182
|
+
expect(transaction.ext).to_not receive(:complete)
|
183
|
+
|
184
|
+
expect do
|
185
|
+
transaction.discard!
|
186
|
+
end.to change { transaction.discarded? }.from(false).to(true)
|
187
|
+
|
188
|
+
transaction.complete
|
189
|
+
end
|
190
|
+
|
191
|
+
it "logs a debug message" do
|
192
|
+
transaction.discard!
|
193
|
+
transaction.complete
|
194
|
+
|
195
|
+
expect(log_contents(log)).to contains_log :debug,
|
196
|
+
"Skipping transaction '#{transaction_id}' because it was manually discarded."
|
197
|
+
end
|
198
|
+
|
199
|
+
context "when a discarded transaction is restored" do
|
200
|
+
before { transaction.discard! }
|
201
|
+
|
202
|
+
it "completes the transaction" do
|
203
|
+
expect(transaction.ext).to receive(:complete).and_call_original
|
149
204
|
|
150
|
-
|
205
|
+
expect do
|
206
|
+
transaction.restore!
|
207
|
+
end.to change { transaction.discarded? }.from(true).to(false)
|
208
|
+
|
209
|
+
transaction.complete
|
210
|
+
end
|
211
|
+
end
|
151
212
|
end
|
152
213
|
end
|
153
214
|
|
@@ -241,6 +302,37 @@ describe Appsignal::Transaction do
|
|
241
302
|
end
|
242
303
|
end
|
243
304
|
|
305
|
+
describe "#params" do
|
306
|
+
subject { transaction.params }
|
307
|
+
|
308
|
+
context "with custom params set on transaction" do
|
309
|
+
before do
|
310
|
+
transaction.params = { :foo => "bar" }
|
311
|
+
end
|
312
|
+
|
313
|
+
it "returns custom parameters" do
|
314
|
+
expect(subject).to eq(:foo => "bar")
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
context "without custom params set on transaction" do
|
319
|
+
it "returns parameters from request" do
|
320
|
+
expect(subject).to eq(
|
321
|
+
"action" => "show",
|
322
|
+
"controller" => "blog_posts",
|
323
|
+
"id" => "1"
|
324
|
+
)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
describe "#params=" do
|
330
|
+
it "sets params on the transaction" do
|
331
|
+
transaction.params = { :foo => "bar" }
|
332
|
+
expect(transaction.params).to eq(:foo => "bar")
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
244
336
|
describe "#set_tags" do
|
245
337
|
it "should add tags to transaction" do
|
246
338
|
expect do
|
@@ -733,19 +825,38 @@ describe Appsignal::Transaction do
|
|
733
825
|
describe "#sanitized_params" do
|
734
826
|
subject { transaction.send(:sanitized_params) }
|
735
827
|
|
736
|
-
context "
|
828
|
+
context "with custom params" do
|
829
|
+
before do
|
830
|
+
transaction.params = { :foo => "bar", :baz => :bat }
|
831
|
+
end
|
832
|
+
|
833
|
+
it "returns custom params" do
|
834
|
+
is_expected.to eq(:foo => "bar", :baz => :bat)
|
835
|
+
end
|
836
|
+
|
837
|
+
context "with AppSignal filtering" do
|
838
|
+
before { Appsignal.config.config_hash[:filter_parameters] = %w(foo) }
|
839
|
+
after { Appsignal.config.config_hash[:filter_parameters] = [] }
|
840
|
+
|
841
|
+
it "returns sanitized custom params" do
|
842
|
+
expect(subject).to eq(:foo => "[FILTERED]", :baz => :bat)
|
843
|
+
end
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
context "without request params" do
|
737
848
|
before { allow(transaction.request).to receive(:params).and_return(nil) }
|
738
849
|
|
739
850
|
it { is_expected.to be_nil }
|
740
851
|
end
|
741
852
|
|
742
|
-
context "when params crashes" do
|
853
|
+
context "when request params crashes" do
|
743
854
|
before { allow(transaction.request).to receive(:params).and_raise(NoMethodError) }
|
744
855
|
|
745
856
|
it { is_expected.to be_nil }
|
746
857
|
end
|
747
858
|
|
748
|
-
context "when params method does not exist" do
|
859
|
+
context "when request params method does not exist" do
|
749
860
|
let(:options) { { :params_method => :nonsense } }
|
750
861
|
|
751
862
|
it { is_expected.to be_nil }
|
@@ -982,6 +1093,34 @@ describe Appsignal::Transaction do
|
|
982
1093
|
end
|
983
1094
|
end
|
984
1095
|
|
1096
|
+
describe ".to_hash / .to_h" do
|
1097
|
+
subject { transaction.to_hash }
|
1098
|
+
|
1099
|
+
context "when extension returns serialized JSON" do
|
1100
|
+
it "parses the result and returns a Hash" do
|
1101
|
+
expect(subject).to include(
|
1102
|
+
"action" => nil,
|
1103
|
+
"error" => nil,
|
1104
|
+
"events" => [],
|
1105
|
+
"id" => transaction_id,
|
1106
|
+
"metadata" => {},
|
1107
|
+
"namespace" => namespace,
|
1108
|
+
"sample_data" => {}
|
1109
|
+
)
|
1110
|
+
end
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
context "when the extension returns invalid serialized JSON" do
|
1114
|
+
before do
|
1115
|
+
expect(transaction.ext).to receive(:to_json).and_return("foo")
|
1116
|
+
end
|
1117
|
+
|
1118
|
+
it "raises a JSON parse error" do
|
1119
|
+
expect { subject }.to raise_error(JSON::ParserError)
|
1120
|
+
end
|
1121
|
+
end
|
1122
|
+
end
|
1123
|
+
|
985
1124
|
describe Appsignal::Transaction::NilTransaction do
|
986
1125
|
subject { Appsignal::Transaction::NilTransaction.new }
|
987
1126
|
|