appsignal 3.0.0.beta.1-java → 3.0.3-java
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +1 -1
- data/.semaphore/semaphore.yml +88 -88
- data/CHANGELOG.md +41 -1
- data/Rakefile +12 -4
- data/appsignal.gemspec +7 -5
- data/build_matrix.yml +11 -11
- data/ext/agent.yml +17 -17
- data/gemfiles/no_dependencies.gemfile +0 -7
- data/lib/appsignal.rb +1 -2
- data/lib/appsignal/config.rb +1 -1
- data/lib/appsignal/extension.rb +50 -0
- data/lib/appsignal/helpers/instrumentation.rb +69 -5
- data/lib/appsignal/hooks.rb +16 -0
- data/lib/appsignal/hooks/action_cable.rb +10 -2
- data/lib/appsignal/hooks/sidekiq.rb +9 -142
- data/lib/appsignal/integrations/object.rb +21 -43
- data/lib/appsignal/integrations/railtie.rb +0 -4
- data/lib/appsignal/integrations/sidekiq.rb +171 -0
- data/lib/appsignal/minutely.rb +6 -0
- data/lib/appsignal/transaction.rb +2 -2
- data/lib/appsignal/version.rb +1 -1
- data/spec/lib/appsignal/config_spec.rb +2 -0
- data/spec/lib/appsignal/extension_install_failure_spec.rb +0 -7
- data/spec/lib/appsignal/extension_spec.rb +43 -9
- data/spec/lib/appsignal/hooks/action_cable_spec.rb +88 -0
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +60 -458
- data/spec/lib/appsignal/hooks_spec.rb +41 -0
- data/spec/lib/appsignal/integrations/object_spec.rb +91 -4
- data/spec/lib/appsignal/integrations/sidekiq_spec.rb +524 -0
- data/spec/lib/appsignal/transaction_spec.rb +17 -0
- data/spec/lib/appsignal/utils/data_spec.rb +133 -87
- data/spec/lib/appsignal_spec.rb +162 -47
- data/spec/lib/puma/appsignal_spec.rb +28 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/testing.rb +11 -1
- metadata +9 -8
- data/gemfiles/rails-4.0.gemfile +0 -6
- data/gemfiles/rails-4.1.gemfile +0 -6
@@ -78,6 +78,47 @@ describe Appsignal::Hooks do
|
|
78
78
|
expect(Appsignal::Hooks.hooks[:mock_error_hook].installed?).to be_falsy
|
79
79
|
Appsignal::Hooks.hooks.delete(:mock_error_hook)
|
80
80
|
end
|
81
|
+
|
82
|
+
describe "missing constants" do
|
83
|
+
let(:err_stream) { std_stream }
|
84
|
+
let(:stderr) { err_stream.read }
|
85
|
+
let(:log_stream) { std_stream }
|
86
|
+
let(:log) { log_contents(log_stream) }
|
87
|
+
before do
|
88
|
+
Appsignal.logger = test_logger(log_stream)
|
89
|
+
end
|
90
|
+
|
91
|
+
def call_constant(&block)
|
92
|
+
capture_std_streams(std_stream, err_stream, &block)
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "SidekiqPlugin" do
|
96
|
+
it "logs a deprecation message and returns the new constant" do
|
97
|
+
constant = call_constant { Appsignal::Hooks::SidekiqPlugin }
|
98
|
+
|
99
|
+
expect(constant).to eql(Appsignal::Integrations::SidekiqMiddleware)
|
100
|
+
expect(constant.name).to eql("Appsignal::Integrations::SidekiqMiddleware")
|
101
|
+
|
102
|
+
deprecation_message =
|
103
|
+
"The constant Appsignal::Hooks::SidekiqPlugin has been deprecated. " \
|
104
|
+
"Please update the constant name to Appsignal::Integrations::SidekiqMiddleware " \
|
105
|
+
"in the following file to remove this message.\n#{__FILE__}:"
|
106
|
+
expect(stderr).to include "appsignal WARNING: #{deprecation_message}"
|
107
|
+
expect(log).to contains_log :warn, deprecation_message
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "other constant" do
|
112
|
+
it "raises a NameError like Ruby normally does" do
|
113
|
+
expect do
|
114
|
+
call_constant { Appsignal::Hooks::Unknown }
|
115
|
+
end.to raise_error(NameError)
|
116
|
+
|
117
|
+
expect(stderr).to be_empty
|
118
|
+
expect(log).to be_empty
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
81
122
|
end
|
82
123
|
|
83
124
|
describe Appsignal::Hooks::Helpers do
|
@@ -26,12 +26,57 @@ describe Object do
|
|
26
26
|
before do
|
27
27
|
Appsignal.config = project_fixture_config
|
28
28
|
expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
|
29
|
+
expect(Appsignal.active?).to be_truthy
|
29
30
|
end
|
30
31
|
after { Appsignal.config = nil }
|
31
32
|
|
33
|
+
context "with different kind of arguments" do
|
34
|
+
let(:klass) do
|
35
|
+
Class.new do
|
36
|
+
def positional_arguments(param1, param2)
|
37
|
+
[param1, param2]
|
38
|
+
end
|
39
|
+
appsignal_instrument_method :positional_arguments
|
40
|
+
|
41
|
+
def positional_arguments_splat(*params)
|
42
|
+
params
|
43
|
+
end
|
44
|
+
appsignal_instrument_method :positional_arguments_splat
|
45
|
+
|
46
|
+
def keyword_arguments(a: nil, b: nil)
|
47
|
+
[a, b]
|
48
|
+
end
|
49
|
+
appsignal_instrument_method :keyword_arguments
|
50
|
+
|
51
|
+
def keyword_arguments_splat(**kwargs)
|
52
|
+
kwargs
|
53
|
+
end
|
54
|
+
appsignal_instrument_method :keyword_arguments_splat
|
55
|
+
|
56
|
+
def splat(*args, **kwargs)
|
57
|
+
[args, kwargs]
|
58
|
+
end
|
59
|
+
appsignal_instrument_method :splat
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "instruments the method and calls it" do
|
64
|
+
expect(instance.positional_arguments("abc", "def")).to eq(["abc", "def"])
|
65
|
+
expect(instance.positional_arguments_splat("abc", "def")).to eq(["abc", "def"])
|
66
|
+
expect(instance.keyword_arguments(:a => "a", :b => "b")).to eq(["a", "b"])
|
67
|
+
expect(instance.keyword_arguments_splat(:a => "a", :b => "b"))
|
68
|
+
.to eq(:a => "a", :b => "b")
|
69
|
+
|
70
|
+
expect(instance.splat).to eq([[], {}])
|
71
|
+
expect(instance.splat(:a => "a", :b => "b")).to eq([[], { :a => "a", :b => "b" }])
|
72
|
+
expect(instance.splat("abc", "def")).to eq([["abc", "def"], {}])
|
73
|
+
expect(instance.splat("abc", "def", :a => "a", :b => "b"))
|
74
|
+
.to eq([["abc", "def"], { :a => "a", :b => "b" }])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
32
78
|
context "with anonymous class" do
|
33
79
|
it "instruments the method and calls it" do
|
34
|
-
expect(Appsignal.active?).to be_truthy
|
35
80
|
expect(transaction).to receive(:start_event)
|
36
81
|
expect(transaction).to receive(:finish_event).with \
|
37
82
|
"foo.AnonymousClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
|
@@ -52,7 +97,6 @@ describe Object do
|
|
52
97
|
let(:klass) { NamedClass }
|
53
98
|
|
54
99
|
it "instruments the method and calls it" do
|
55
|
-
expect(Appsignal.active?).to be_truthy
|
56
100
|
expect(transaction).to receive(:start_event)
|
57
101
|
expect(transaction).to receive(:finish_event).with \
|
58
102
|
"foo.NamedClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
|
@@ -77,7 +121,6 @@ describe Object do
|
|
77
121
|
let(:klass) { MyModule::NestedModule::NamedClass }
|
78
122
|
|
79
123
|
it "instruments the method and calls it" do
|
80
|
-
expect(Appsignal.active?).to be_truthy
|
81
124
|
expect(transaction).to receive(:start_event)
|
82
125
|
expect(transaction).to receive(:finish_event).with \
|
83
126
|
"bar.NamedClass.NestedModule.MyModule.other", nil, nil,
|
@@ -97,7 +140,6 @@ describe Object do
|
|
97
140
|
end
|
98
141
|
|
99
142
|
it "instruments with custom name" do
|
100
|
-
expect(Appsignal.active?).to be_truthy
|
101
143
|
expect(transaction).to receive(:start_event)
|
102
144
|
expect(transaction).to receive(:finish_event).with \
|
103
145
|
"my_method.group", nil, nil, Appsignal::EventFormatter::DEFAULT
|
@@ -158,6 +200,51 @@ describe Object do
|
|
158
200
|
end
|
159
201
|
after { Appsignal.config = nil }
|
160
202
|
|
203
|
+
context "with different kind of arguments" do
|
204
|
+
let(:klass) do
|
205
|
+
Class.new do
|
206
|
+
def self.positional_arguments(param1, param2)
|
207
|
+
[param1, param2]
|
208
|
+
end
|
209
|
+
appsignal_instrument_class_method :positional_arguments
|
210
|
+
|
211
|
+
def self.positional_arguments_splat(*params)
|
212
|
+
params
|
213
|
+
end
|
214
|
+
appsignal_instrument_class_method :positional_arguments_splat
|
215
|
+
|
216
|
+
def self.keyword_arguments(a: nil, b: nil)
|
217
|
+
[a, b]
|
218
|
+
end
|
219
|
+
appsignal_instrument_class_method :keyword_arguments
|
220
|
+
|
221
|
+
def self.keyword_arguments_splat(**kwargs)
|
222
|
+
kwargs
|
223
|
+
end
|
224
|
+
appsignal_instrument_class_method :keyword_arguments_splat
|
225
|
+
|
226
|
+
def self.splat(*args, **kwargs)
|
227
|
+
[args, kwargs]
|
228
|
+
end
|
229
|
+
appsignal_instrument_class_method :splat
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
it "instruments the method and calls it" do
|
234
|
+
expect(klass.positional_arguments("abc", "def")).to eq(["abc", "def"])
|
235
|
+
expect(klass.positional_arguments_splat("abc", "def")).to eq(["abc", "def"])
|
236
|
+
expect(klass.keyword_arguments(:a => "a", :b => "b")).to eq(["a", "b"])
|
237
|
+
expect(klass.keyword_arguments_splat(:a => "a", :b => "b"))
|
238
|
+
.to eq(:a => "a", :b => "b")
|
239
|
+
|
240
|
+
expect(klass.splat).to eq([[], {}])
|
241
|
+
expect(klass.splat(:a => "a", :b => "b")).to eq([[], { :a => "a", :b => "b" }])
|
242
|
+
expect(klass.splat("abc", "def")).to eq([["abc", "def"], {}])
|
243
|
+
expect(klass.splat("abc", "def", :a => "a", :b => "b"))
|
244
|
+
.to eq([["abc", "def"], { :a => "a", :b => "b" }])
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
161
248
|
context "with anonymous class" do
|
162
249
|
it "instruments the method and calls it" do
|
163
250
|
expect(Appsignal.active?).to be_truthy
|
@@ -0,0 +1,524 @@
|
|
1
|
+
require "appsignal/integrations/sidekiq"
|
2
|
+
|
3
|
+
describe Appsignal::Integrations::SidekiqErrorHandler do
|
4
|
+
let(:log) { StringIO.new }
|
5
|
+
before do
|
6
|
+
start_agent
|
7
|
+
Appsignal.logger = test_logger(log)
|
8
|
+
end
|
9
|
+
around { |example| keep_transactions { example.run } }
|
10
|
+
|
11
|
+
context "without a current transction" do
|
12
|
+
let(:exception) do
|
13
|
+
begin
|
14
|
+
raise ExampleStandardError, "uh oh"
|
15
|
+
rescue => error
|
16
|
+
error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
let(:job_context) do
|
20
|
+
{
|
21
|
+
:context => "Sidekiq internal error!",
|
22
|
+
:jobstr => "{ bad json }"
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it "tracks error on a new transaction" do
|
27
|
+
described_class.new.call(exception, job_context)
|
28
|
+
|
29
|
+
transaction_hash = last_transaction.to_h
|
30
|
+
expect(transaction_hash["error"]).to include(
|
31
|
+
"name" => "ExampleStandardError",
|
32
|
+
"message" => "uh oh",
|
33
|
+
"backtrace" => kind_of(String)
|
34
|
+
)
|
35
|
+
expect(transaction_hash["sample_data"]).to include(
|
36
|
+
"params" => {
|
37
|
+
"jobstr" => "{ bad json }"
|
38
|
+
}
|
39
|
+
)
|
40
|
+
expect(transaction_hash["metadata"]).to include(
|
41
|
+
"sidekiq_error" => "Sidekiq internal error!"
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe Appsignal::Integrations::SidekiqMiddleware, :with_yaml_parse_error => false do
|
48
|
+
class DelayedTestClass; end
|
49
|
+
|
50
|
+
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
51
|
+
let(:worker) { anything }
|
52
|
+
let(:queue) { anything }
|
53
|
+
let(:given_args) do
|
54
|
+
[
|
55
|
+
"foo",
|
56
|
+
{
|
57
|
+
:foo => "Foo",
|
58
|
+
:bar => "Bar",
|
59
|
+
"baz" => { 1 => :foo }
|
60
|
+
}
|
61
|
+
]
|
62
|
+
end
|
63
|
+
let(:expected_args) do
|
64
|
+
[
|
65
|
+
"foo",
|
66
|
+
{
|
67
|
+
"foo" => "Foo",
|
68
|
+
"bar" => "Bar",
|
69
|
+
"baz" => { "1" => "foo" }
|
70
|
+
}
|
71
|
+
]
|
72
|
+
end
|
73
|
+
let(:job_class) { "TestClass" }
|
74
|
+
let(:jid) { "b4a577edbccf1d805744efa9" }
|
75
|
+
let(:item) do
|
76
|
+
{
|
77
|
+
"jid" => jid,
|
78
|
+
"class" => job_class,
|
79
|
+
"retry_count" => 0,
|
80
|
+
"queue" => "default",
|
81
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
82
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
83
|
+
"args" => given_args,
|
84
|
+
"extra" => "data"
|
85
|
+
}
|
86
|
+
end
|
87
|
+
let(:plugin) { Appsignal::Integrations::SidekiqMiddleware.new }
|
88
|
+
let(:log) { StringIO.new }
|
89
|
+
before do
|
90
|
+
start_agent
|
91
|
+
Appsignal.logger = test_logger(log)
|
92
|
+
end
|
93
|
+
around { |example| keep_transactions { example.run } }
|
94
|
+
after :with_yaml_parse_error => false do
|
95
|
+
expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "internal Sidekiq job values" do
|
99
|
+
it "does not save internal Sidekiq values as metadata on transaction" do
|
100
|
+
perform_job
|
101
|
+
|
102
|
+
transaction_hash = transaction.to_h
|
103
|
+
expect(transaction_hash["metadata"].keys)
|
104
|
+
.to_not include(*Appsignal::Integrations::SidekiqMiddleware::EXCLUDED_JOB_KEYS)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "with parameter filtering" do
|
109
|
+
before do
|
110
|
+
Appsignal.config = project_fixture_config("production")
|
111
|
+
Appsignal.config[:filter_parameters] = ["foo"]
|
112
|
+
end
|
113
|
+
|
114
|
+
it "filters selected arguments" do
|
115
|
+
perform_job
|
116
|
+
|
117
|
+
transaction_hash = transaction.to_h
|
118
|
+
expect(transaction_hash["sample_data"]).to include(
|
119
|
+
"params" => [
|
120
|
+
"foo",
|
121
|
+
{
|
122
|
+
"foo" => "[FILTERED]",
|
123
|
+
"bar" => "Bar",
|
124
|
+
"baz" => { "1" => "foo" }
|
125
|
+
}
|
126
|
+
]
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "with encrypted arguments" do
|
132
|
+
before do
|
133
|
+
item["encrypt"] = true
|
134
|
+
item["args"] << "super secret value" # Last argument will be replaced
|
135
|
+
end
|
136
|
+
|
137
|
+
it "replaces the last argument (the secret bag) with an [encrypted data] string" do
|
138
|
+
perform_job
|
139
|
+
|
140
|
+
transaction_hash = transaction.to_h
|
141
|
+
expect(transaction_hash["sample_data"]).to include(
|
142
|
+
"params" => expected_args << "[encrypted data]"
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "when using the Sidekiq delayed extension" do
|
148
|
+
let(:item) do
|
149
|
+
{
|
150
|
+
"jid" => jid,
|
151
|
+
"class" => "Sidekiq::Extensions::DelayedClass",
|
152
|
+
"queue" => "default",
|
153
|
+
"args" => [
|
154
|
+
"---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
|
155
|
+
],
|
156
|
+
"retry" => true,
|
157
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
158
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
159
|
+
"extra" => "data"
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
it "uses the delayed class and method name for the action" do
|
164
|
+
perform_job
|
165
|
+
|
166
|
+
transaction_hash = transaction.to_h
|
167
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
|
168
|
+
expect(transaction_hash["sample_data"]).to include(
|
169
|
+
"params" => ["bar" => "baz"]
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
174
|
+
before { item["args"] = [] }
|
175
|
+
|
176
|
+
it "logs a warning and uses the default argument" do
|
177
|
+
perform_job
|
178
|
+
|
179
|
+
transaction_hash = transaction.to_h
|
180
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
|
181
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
182
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "when using the Sidekiq ActiveRecord instance delayed extension" do
|
188
|
+
let(:item) do
|
189
|
+
{
|
190
|
+
"jid" => jid,
|
191
|
+
"class" => "Sidekiq::Extensions::DelayedModel",
|
192
|
+
"queue" => "default",
|
193
|
+
"args" => [
|
194
|
+
"---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
|
195
|
+
],
|
196
|
+
"retry" => true,
|
197
|
+
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
198
|
+
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
199
|
+
"extra" => "data"
|
200
|
+
}
|
201
|
+
end
|
202
|
+
|
203
|
+
it "uses the delayed class and method name for the action" do
|
204
|
+
perform_job
|
205
|
+
|
206
|
+
transaction_hash = transaction.to_h
|
207
|
+
expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
|
208
|
+
expect(transaction_hash["sample_data"]).to include(
|
209
|
+
"params" => ["bar" => "baz"]
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
214
|
+
before { item["args"] = [] }
|
215
|
+
|
216
|
+
it "logs a warning and uses the default argument" do
|
217
|
+
perform_job
|
218
|
+
|
219
|
+
transaction_hash = transaction.to_h
|
220
|
+
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
|
221
|
+
expect(transaction_hash["sample_data"]).to include("params" => [])
|
222
|
+
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context "with an error" do
|
228
|
+
let(:error) { ExampleException }
|
229
|
+
|
230
|
+
it "creates a transaction and adds the error" do
|
231
|
+
expect(Appsignal).to receive(:increment_counter)
|
232
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :failed)
|
233
|
+
expect(Appsignal).to receive(:increment_counter)
|
234
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
235
|
+
|
236
|
+
expect do
|
237
|
+
perform_job { raise error, "uh oh" }
|
238
|
+
end.to raise_error(error)
|
239
|
+
|
240
|
+
transaction_hash = transaction.to_h
|
241
|
+
expect(transaction_hash).to include(
|
242
|
+
"id" => jid,
|
243
|
+
"action" => "TestClass#perform",
|
244
|
+
"error" => {
|
245
|
+
"name" => "ExampleException",
|
246
|
+
"message" => "uh oh",
|
247
|
+
# TODO: backtrace should be an Array of Strings
|
248
|
+
# https://github.com/appsignal/appsignal-agent/issues/294
|
249
|
+
"backtrace" => kind_of(String)
|
250
|
+
},
|
251
|
+
"metadata" => {
|
252
|
+
"extra" => "data",
|
253
|
+
"queue" => "default",
|
254
|
+
"retry_count" => "0"
|
255
|
+
},
|
256
|
+
"namespace" => namespace,
|
257
|
+
"sample_data" => {
|
258
|
+
"environment" => {},
|
259
|
+
"params" => expected_args,
|
260
|
+
"tags" => {},
|
261
|
+
"breadcrumbs" => []
|
262
|
+
}
|
263
|
+
)
|
264
|
+
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
context "without an error" do
|
269
|
+
it "creates a transaction with events" do
|
270
|
+
expect(Appsignal).to receive(:increment_counter)
|
271
|
+
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
272
|
+
|
273
|
+
perform_job
|
274
|
+
|
275
|
+
transaction_hash = transaction.to_h
|
276
|
+
expect(transaction_hash).to include(
|
277
|
+
"id" => jid,
|
278
|
+
"action" => "TestClass#perform",
|
279
|
+
"error" => nil,
|
280
|
+
"metadata" => {
|
281
|
+
"extra" => "data",
|
282
|
+
"queue" => "default",
|
283
|
+
"retry_count" => "0"
|
284
|
+
},
|
285
|
+
"namespace" => namespace,
|
286
|
+
"sample_data" => {
|
287
|
+
"environment" => {},
|
288
|
+
"params" => expected_args,
|
289
|
+
"tags" => {},
|
290
|
+
"breadcrumbs" => []
|
291
|
+
}
|
292
|
+
)
|
293
|
+
# TODO: Not available in transaction.to_h yet.
|
294
|
+
# https://github.com/appsignal/appsignal-agent/issues/293
|
295
|
+
expect(transaction.request.env).to eq(
|
296
|
+
:queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
|
297
|
+
)
|
298
|
+
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
def perform_job
|
303
|
+
Timecop.freeze(Time.parse("2001-01-01 10:01:00UTC")) do
|
304
|
+
begin
|
305
|
+
exception = nil
|
306
|
+
plugin.call(worker, item, queue) do
|
307
|
+
yield if block_given?
|
308
|
+
end
|
309
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
310
|
+
raise exception
|
311
|
+
ensure
|
312
|
+
if exception
|
313
|
+
Appsignal::Integrations::SidekiqErrorHandler.new.call(exception, :job => item)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def transaction
|
320
|
+
last_transaction
|
321
|
+
end
|
322
|
+
|
323
|
+
def expect_transaction_to_have_sidekiq_event(transaction_hash)
|
324
|
+
events = transaction_hash["events"]
|
325
|
+
expect(events.count).to eq(1)
|
326
|
+
expect(events.first).to include(
|
327
|
+
"name" => "perform_job.sidekiq",
|
328
|
+
"title" => "",
|
329
|
+
"count" => 1,
|
330
|
+
"body" => "",
|
331
|
+
"body_format" => Appsignal::EventFormatter::DEFAULT
|
332
|
+
)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
if DependencyHelper.active_job_present?
|
337
|
+
require "active_job"
|
338
|
+
require "action_mailer"
|
339
|
+
require "sidekiq/testing"
|
340
|
+
|
341
|
+
describe "Sidekiq ActiveJob integration" do
|
342
|
+
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
343
|
+
let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
|
344
|
+
let(:log) { StringIO.new }
|
345
|
+
let(:given_args) do
|
346
|
+
[
|
347
|
+
"foo",
|
348
|
+
{
|
349
|
+
:foo => "Foo",
|
350
|
+
"bar" => "Bar",
|
351
|
+
"baz" => { "1" => "foo" }
|
352
|
+
}
|
353
|
+
]
|
354
|
+
end
|
355
|
+
let(:expected_args) do
|
356
|
+
[
|
357
|
+
"foo",
|
358
|
+
{
|
359
|
+
"_aj_symbol_keys" => ["foo"],
|
360
|
+
"foo" => "Foo",
|
361
|
+
"bar" => "Bar",
|
362
|
+
"baz" => {
|
363
|
+
"_aj_symbol_keys" => [],
|
364
|
+
"1" => "foo"
|
365
|
+
}
|
366
|
+
}
|
367
|
+
]
|
368
|
+
end
|
369
|
+
let(:expected_tags) do
|
370
|
+
{}.tap do |hash|
|
371
|
+
hash["active_job_id"] = kind_of(String)
|
372
|
+
if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
|
373
|
+
hash["provider_job_id"] = kind_of(String)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
before do
|
378
|
+
start_agent
|
379
|
+
Appsignal.logger = test_logger(log)
|
380
|
+
ActiveJob::Base.queue_adapter = :sidekiq
|
381
|
+
|
382
|
+
class ActiveJobSidekiqTestJob < ActiveJob::Base
|
383
|
+
self.queue_adapter = :sidekiq
|
384
|
+
|
385
|
+
def perform(*_args)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
|
390
|
+
self.queue_adapter = :sidekiq
|
391
|
+
|
392
|
+
def perform(*_args)
|
393
|
+
raise "uh oh"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
# Manually add the AppSignal middleware for the Testing environment.
|
397
|
+
# It doesn't use configured middlewares by default looks like.
|
398
|
+
# We test somewhere else if the middleware is installed properly.
|
399
|
+
Sidekiq::Testing.server_middleware do |chain|
|
400
|
+
chain.add Appsignal::Integrations::SidekiqMiddleware
|
401
|
+
end
|
402
|
+
end
|
403
|
+
around do |example|
|
404
|
+
keep_transactions do
|
405
|
+
Sidekiq::Testing.fake! do
|
406
|
+
example.run
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
after do
|
411
|
+
Object.send(:remove_const, :ActiveJobSidekiqTestJob)
|
412
|
+
Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
|
413
|
+
end
|
414
|
+
|
415
|
+
it "reports the transaction from the ActiveJob integration" do
|
416
|
+
perform_job(ActiveJobSidekiqTestJob, given_args)
|
417
|
+
|
418
|
+
transaction = last_transaction
|
419
|
+
transaction_hash = transaction.to_h
|
420
|
+
expect(transaction_hash).to include(
|
421
|
+
"action" => "ActiveJobSidekiqTestJob#perform",
|
422
|
+
"error" => nil,
|
423
|
+
"namespace" => namespace,
|
424
|
+
"metadata" => hash_including(
|
425
|
+
"queue" => "default"
|
426
|
+
),
|
427
|
+
"sample_data" => hash_including(
|
428
|
+
"environment" => {},
|
429
|
+
"params" => [expected_args],
|
430
|
+
"tags" => expected_tags.merge("queue" => "default")
|
431
|
+
)
|
432
|
+
)
|
433
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
434
|
+
events = transaction_hash["events"]
|
435
|
+
.sort_by { |e| e["start"] }
|
436
|
+
.map { |event| event["name"] }
|
437
|
+
expect(events)
|
438
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
439
|
+
end
|
440
|
+
|
441
|
+
context "with error" do
|
442
|
+
it "reports the error on the transaction from the ActiveRecord integration" do
|
443
|
+
expect do
|
444
|
+
perform_job(ActiveJobSidekiqErrorTestJob, given_args)
|
445
|
+
end.to raise_error(RuntimeError, "uh oh")
|
446
|
+
|
447
|
+
transaction = last_transaction
|
448
|
+
transaction_hash = transaction.to_h
|
449
|
+
expect(transaction_hash).to include(
|
450
|
+
"action" => "ActiveJobSidekiqErrorTestJob#perform",
|
451
|
+
"error" => {
|
452
|
+
"name" => "RuntimeError",
|
453
|
+
"message" => "uh oh",
|
454
|
+
"backtrace" => kind_of(String)
|
455
|
+
},
|
456
|
+
"namespace" => namespace,
|
457
|
+
"metadata" => hash_including(
|
458
|
+
"queue" => "default"
|
459
|
+
),
|
460
|
+
"sample_data" => hash_including(
|
461
|
+
"environment" => {},
|
462
|
+
"params" => [expected_args],
|
463
|
+
"tags" => expected_tags.merge("queue" => "default")
|
464
|
+
)
|
465
|
+
)
|
466
|
+
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
467
|
+
events = transaction_hash["events"]
|
468
|
+
.sort_by { |e| e["start"] }
|
469
|
+
.map { |event| event["name"] }
|
470
|
+
expect(events)
|
471
|
+
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context "with ActionMailer" do
|
476
|
+
include ActionMailerHelpers
|
477
|
+
|
478
|
+
before do
|
479
|
+
class ActionMailerSidekiqTestJob < ActionMailer::Base
|
480
|
+
def welcome(*args)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
it "reports ActionMailer data on the transaction" do
|
486
|
+
perform_mailer(ActionMailerSidekiqTestJob, :welcome, given_args)
|
487
|
+
|
488
|
+
transaction = last_transaction
|
489
|
+
transaction_hash = transaction.to_h
|
490
|
+
expect(transaction_hash).to include(
|
491
|
+
"action" => "ActionMailerSidekiqTestJob#welcome",
|
492
|
+
"sample_data" => hash_including(
|
493
|
+
"params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
|
494
|
+
)
|
495
|
+
)
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
def perform_sidekiq
|
500
|
+
Timecop.freeze(time) do
|
501
|
+
begin
|
502
|
+
yield
|
503
|
+
# Combined with Sidekiq::Testing.fake! and drain_all we get a
|
504
|
+
# enqueue_at in the job data.
|
505
|
+
Sidekiq::Worker.drain_all
|
506
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
507
|
+
raise exception
|
508
|
+
ensure
|
509
|
+
if exception
|
510
|
+
Appsignal::Integrations::SidekiqErrorHandler.new.call(exception, {})
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
def perform_job(job_class, args)
|
517
|
+
perform_sidekiq { job_class.perform_later(args) }
|
518
|
+
end
|
519
|
+
|
520
|
+
def perform_mailer(mailer, method, args = nil)
|
521
|
+
perform_sidekiq { perform_action_mailer(mailer, method, args) }
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|