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
data/lib/appsignal/minutely.rb
CHANGED
@@ -123,6 +123,12 @@ module Appsignal
|
|
123
123
|
def start
|
124
124
|
stop
|
125
125
|
@thread = Thread.new do
|
126
|
+
# Advise multi-threaded app servers to ignore this thread
|
127
|
+
# for the purposes of fork safety warnings
|
128
|
+
if Thread.current.respond_to?(:thread_variable_set)
|
129
|
+
Thread.current.thread_variable_set(:fork_safe, true)
|
130
|
+
end
|
131
|
+
|
126
132
|
sleep initial_wait_time
|
127
133
|
initialize_probes
|
128
134
|
loop do
|
@@ -90,7 +90,7 @@ module Appsignal
|
|
90
90
|
@transaction_id,
|
91
91
|
@namespace,
|
92
92
|
self.class.garbage_collection_profiler.total_time
|
93
|
-
)
|
93
|
+
) || Appsignal::Extension::MockTransaction.new
|
94
94
|
end
|
95
95
|
|
96
96
|
def nil_transaction?
|
@@ -228,7 +228,7 @@ module Appsignal
|
|
228
228
|
# "Web" and "background_job" gets transformed to "Background".
|
229
229
|
#
|
230
230
|
# @example
|
231
|
-
# transaction.
|
231
|
+
# transaction.set_namespace("background")
|
232
232
|
#
|
233
233
|
# @param namespace [String] namespace name to use for this transaction.
|
234
234
|
# @return [void]
|
data/lib/appsignal/version.rb
CHANGED
@@ -374,6 +374,7 @@ describe Appsignal::Config do
|
|
374
374
|
:active => true,
|
375
375
|
:name => "App name",
|
376
376
|
:debug => true,
|
377
|
+
:dns_servers => ["8.8.8.8", "8.8.4.4"],
|
377
378
|
:ignore_actions => %w[action1 action2],
|
378
379
|
:ignore_errors => %w[ExampleStandardError AnotherError],
|
379
380
|
:ignore_namespaces => %w[admin private_namespace],
|
@@ -393,6 +394,7 @@ describe Appsignal::Config do
|
|
393
394
|
ENV["APPSIGNAL_ACTIVE"] = "true"
|
394
395
|
ENV["APPSIGNAL_APP_NAME"] = "App name"
|
395
396
|
ENV["APPSIGNAL_DEBUG"] = "true"
|
397
|
+
ENV["APPSIGNAL_DNS_SERVERS"] = "8.8.8.8,8.8.4.4"
|
396
398
|
ENV["APPSIGNAL_IGNORE_ACTIONS"] = "action1,action2"
|
397
399
|
ENV["APPSIGNAL_IGNORE_ERRORS"] = "ExampleStandardError,AnotherError"
|
398
400
|
ENV["APPSIGNAL_IGNORE_NAMESPACES"] = "admin,private_namespace"
|
@@ -1,13 +1,6 @@
|
|
1
1
|
describe Appsignal::Extension, :extension_installation_failure do
|
2
2
|
context "when the extension library cannot be loaded" do
|
3
|
-
# This test breaks the installation on purpose and is not run by default.
|
4
|
-
# See `rake test:failure`. If this test was run, run `rake
|
5
|
-
# extension:install` again to fix the extension installation.
|
6
3
|
it "prints and logs an error" do
|
7
|
-
# ENV var to make sure installation fails on purpurse
|
8
|
-
ENV["_TEST_APPSIGNAL_EXTENSION_FAILURE"] = "true"
|
9
|
-
`rake extension:install` # Run installation
|
10
|
-
|
11
4
|
require "open3"
|
12
5
|
_stdout, stderr, _status = Open3.capture3("bin/appsignal --version")
|
13
6
|
expect(stderr).to include("ERROR: AppSignal failed to load extension")
|
@@ -119,22 +119,56 @@ describe Appsignal::Extension do
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
-
context "when the extension library cannot be loaded" do
|
123
|
-
|
122
|
+
context "when the extension library cannot be loaded", :extension_installation_failure do
|
123
|
+
let(:ext) { Appsignal::Extension }
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
allow(Appsignal).to receive(:testing?).and_return(false)
|
125
|
+
around do |example|
|
126
|
+
Appsignal::Testing.without_testing { example.run }
|
128
127
|
end
|
129
128
|
|
130
129
|
it "should indicate that the extension is not loaded" do
|
131
130
|
expect(Appsignal.extension_loaded?).to be_falsy
|
132
131
|
end
|
133
132
|
|
134
|
-
it "
|
135
|
-
|
136
|
-
|
137
|
-
|
133
|
+
it "does not raise errors when methods are called" do
|
134
|
+
ext.appsignal_start
|
135
|
+
ext.something
|
136
|
+
end
|
137
|
+
|
138
|
+
describe Appsignal::Extension::MockData do
|
139
|
+
it "does not error on missing data_map_new extension method calls" do
|
140
|
+
map = ext.data_map_new
|
141
|
+
expect(map).to be_kind_of(Appsignal::Extension::MockData)
|
142
|
+
# Does not raise errors any arbitrary method call that does not exist
|
143
|
+
map.set_string("key", "value")
|
144
|
+
map.set_int("key", 123)
|
145
|
+
map.something
|
146
|
+
end
|
147
|
+
|
148
|
+
it "does not error on missing data_array_new extension method calls" do
|
149
|
+
array = ext.data_array_new
|
150
|
+
expect(array).to be_kind_of(Appsignal::Extension::MockData)
|
151
|
+
# Does not raise errors any arbitrary method call that does not exist
|
152
|
+
array.append_string("value")
|
153
|
+
array.append_int(123)
|
154
|
+
array.something
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe Appsignal::Extension::MockTransaction do
|
159
|
+
it "does not error on missing transaction extension method calls" do
|
160
|
+
transaction = described_class.new
|
161
|
+
|
162
|
+
transaction.start_event(0)
|
163
|
+
transaction.finish_event(
|
164
|
+
"name",
|
165
|
+
"title",
|
166
|
+
"body",
|
167
|
+
Appsignal::EventFormatter::DEFAULT,
|
168
|
+
0
|
169
|
+
)
|
170
|
+
transaction.something
|
171
|
+
end
|
138
172
|
end
|
139
173
|
end
|
140
174
|
end
|
@@ -2,6 +2,8 @@ describe Appsignal::Hooks::ActionCableHook do
|
|
2
2
|
if DependencyHelper.action_cable_present?
|
3
3
|
context "with ActionCable" do
|
4
4
|
require "action_cable/engine"
|
5
|
+
# Require test helper to test with ConnectionStub
|
6
|
+
require "action_cable/channel/test_case" if DependencyHelper.rails6_present?
|
5
7
|
|
6
8
|
describe ".dependencies_present?" do
|
7
9
|
subject { described_class.new.dependencies_present? }
|
@@ -262,6 +264,49 @@ describe Appsignal::Hooks::ActionCableHook do
|
|
262
264
|
)
|
263
265
|
end
|
264
266
|
end
|
267
|
+
|
268
|
+
if DependencyHelper.rails6_present?
|
269
|
+
context "with ConnectionStub" do
|
270
|
+
let(:connection) { ActionCable::Channel::ConnectionStub.new }
|
271
|
+
let(:transaction_id) { "Stubbed transaction id" }
|
272
|
+
before do
|
273
|
+
# Stub future (private AppSignal) transaction id generated by the hook.
|
274
|
+
expect(SecureRandom).to receive(:uuid).and_return(transaction_id)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "does not fail on missing `#env` method on `ConnectionStub`" do
|
278
|
+
instance.subscribe_to_channel
|
279
|
+
|
280
|
+
expect(subject).to include(
|
281
|
+
"action" => "MyChannel#subscribed",
|
282
|
+
"error" => nil,
|
283
|
+
"id" => transaction_id,
|
284
|
+
"namespace" => Appsignal::Transaction::ACTION_CABLE,
|
285
|
+
"metadata" => {
|
286
|
+
"method" => "websocket",
|
287
|
+
"path" => "" # No path as the ConnectionStub doesn't have the real request env
|
288
|
+
}
|
289
|
+
)
|
290
|
+
expect(subject["events"].first).to include(
|
291
|
+
"allocation_count" => kind_of(Integer),
|
292
|
+
"body" => "",
|
293
|
+
"body_format" => Appsignal::EventFormatter::DEFAULT,
|
294
|
+
"child_allocation_count" => kind_of(Integer),
|
295
|
+
"child_duration" => kind_of(Float),
|
296
|
+
"child_gc_duration" => kind_of(Float),
|
297
|
+
"count" => 1,
|
298
|
+
"gc_duration" => kind_of(Float),
|
299
|
+
"start" => kind_of(Float),
|
300
|
+
"duration" => kind_of(Float),
|
301
|
+
"name" => "subscribed.action_cable",
|
302
|
+
"title" => ""
|
303
|
+
)
|
304
|
+
expect(subject["sample_data"]).to include(
|
305
|
+
"params" => { "internal" => "true" }
|
306
|
+
)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
265
310
|
end
|
266
311
|
|
267
312
|
describe "unsubscribe callback" do
|
@@ -349,6 +394,49 @@ describe Appsignal::Hooks::ActionCableHook do
|
|
349
394
|
)
|
350
395
|
end
|
351
396
|
end
|
397
|
+
|
398
|
+
if DependencyHelper.rails6_present?
|
399
|
+
context "with ConnectionStub" do
|
400
|
+
let(:connection) { ActionCable::Channel::ConnectionStub.new }
|
401
|
+
let(:transaction_id) { "Stubbed transaction id" }
|
402
|
+
before do
|
403
|
+
# Stub future (private AppSignal) transaction id generated by the hook.
|
404
|
+
expect(SecureRandom).to receive(:uuid).and_return(transaction_id)
|
405
|
+
end
|
406
|
+
|
407
|
+
it "does not fail on missing `#env` method on `ConnectionStub`" do
|
408
|
+
instance.unsubscribe_from_channel
|
409
|
+
|
410
|
+
expect(subject).to include(
|
411
|
+
"action" => "MyChannel#unsubscribed",
|
412
|
+
"error" => nil,
|
413
|
+
"id" => transaction_id,
|
414
|
+
"namespace" => Appsignal::Transaction::ACTION_CABLE,
|
415
|
+
"metadata" => {
|
416
|
+
"method" => "websocket",
|
417
|
+
"path" => "" # No path as the ConnectionStub doesn't have the real request env
|
418
|
+
}
|
419
|
+
)
|
420
|
+
expect(subject["events"].first).to include(
|
421
|
+
"allocation_count" => kind_of(Integer),
|
422
|
+
"body" => "",
|
423
|
+
"body_format" => Appsignal::EventFormatter::DEFAULT,
|
424
|
+
"child_allocation_count" => kind_of(Integer),
|
425
|
+
"child_duration" => kind_of(Float),
|
426
|
+
"child_gc_duration" => kind_of(Float),
|
427
|
+
"count" => 1,
|
428
|
+
"gc_duration" => kind_of(Float),
|
429
|
+
"start" => kind_of(Float),
|
430
|
+
"duration" => kind_of(Float),
|
431
|
+
"name" => "unsubscribed.action_cable",
|
432
|
+
"title" => ""
|
433
|
+
)
|
434
|
+
expect(subject["sample_data"]).to include(
|
435
|
+
"params" => { "internal" => "true" }
|
436
|
+
)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
352
440
|
end
|
353
441
|
end
|
354
442
|
end
|
@@ -16,14 +16,31 @@ describe Appsignal::Hooks::SidekiqHook do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe "#install" do
|
19
|
-
class
|
20
|
-
|
21
|
-
|
19
|
+
class SidekiqMiddlewareMockWithPrepend < Array
|
20
|
+
alias add <<
|
21
|
+
alias exists? include?
|
22
|
+
|
23
|
+
unless method_defined? :prepend
|
24
|
+
def prepend(middleware) # For Ruby < 2.5
|
25
|
+
insert(0, middleware)
|
26
|
+
end
|
22
27
|
end
|
23
28
|
end
|
29
|
+
|
30
|
+
class SidekiqMiddlewareMockWithoutPrepend < Array
|
31
|
+
alias add <<
|
32
|
+
alias exists? include?
|
33
|
+
|
34
|
+
undef_method :prepend if method_defined? :prepend # For Ruby >= 2.5
|
35
|
+
end
|
36
|
+
|
24
37
|
module SidekiqMock
|
38
|
+
def self.middleware_mock=(mock)
|
39
|
+
@middlewares = mock.new
|
40
|
+
end
|
41
|
+
|
25
42
|
def self.middlewares
|
26
|
-
@middlewares
|
43
|
+
@middlewares
|
27
44
|
end
|
28
45
|
|
29
46
|
def self.configure_server
|
@@ -34,479 +51,64 @@ describe Appsignal::Hooks::SidekiqHook do
|
|
34
51
|
yield middlewares if block_given?
|
35
52
|
middlewares
|
36
53
|
end
|
37
|
-
end
|
38
|
-
|
39
|
-
before do
|
40
|
-
Appsignal.config = project_fixture_config
|
41
|
-
stub_const "Sidekiq", SidekiqMock
|
42
|
-
end
|
43
|
-
|
44
|
-
it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
|
45
|
-
described_class.new.install
|
46
|
-
|
47
|
-
expect(Sidekiq.server_middleware.exists?(Appsignal::Hooks::SidekiqPlugin)).to be(true)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe Appsignal::Hooks::SidekiqPlugin, :with_yaml_parse_error => false do
|
53
|
-
class DelayedTestClass; end
|
54
|
-
|
55
|
-
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
56
|
-
let(:worker) { anything }
|
57
|
-
let(:queue) { anything }
|
58
|
-
let(:given_args) do
|
59
|
-
[
|
60
|
-
"foo",
|
61
|
-
{
|
62
|
-
:foo => "Foo",
|
63
|
-
:bar => "Bar",
|
64
|
-
"baz" => { 1 => :foo }
|
65
|
-
}
|
66
|
-
]
|
67
|
-
end
|
68
|
-
let(:expected_args) do
|
69
|
-
[
|
70
|
-
"foo",
|
71
|
-
{
|
72
|
-
"foo" => "Foo",
|
73
|
-
"bar" => "Bar",
|
74
|
-
"baz" => { "1" => "foo" }
|
75
|
-
}
|
76
|
-
]
|
77
|
-
end
|
78
|
-
let(:job_class) { "TestClass" }
|
79
|
-
let(:jid) { "b4a577edbccf1d805744efa9" }
|
80
|
-
let(:item) do
|
81
|
-
{
|
82
|
-
"jid" => jid,
|
83
|
-
"class" => job_class,
|
84
|
-
"retry_count" => 0,
|
85
|
-
"queue" => "default",
|
86
|
-
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
87
|
-
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
88
|
-
"args" => given_args,
|
89
|
-
"extra" => "data"
|
90
|
-
}
|
91
|
-
end
|
92
|
-
let(:plugin) { Appsignal::Hooks::SidekiqPlugin.new }
|
93
|
-
let(:log) { StringIO.new }
|
94
|
-
before do
|
95
|
-
start_agent
|
96
|
-
Appsignal.logger = test_logger(log)
|
97
|
-
end
|
98
|
-
around { |example| keep_transactions { example.run } }
|
99
|
-
after :with_yaml_parse_error => false do
|
100
|
-
expect(log_contents(log)).to_not contains_log(:warn, "Unable to load YAML")
|
101
|
-
end
|
102
|
-
|
103
|
-
describe "internal Sidekiq job values" do
|
104
|
-
it "does not save internal Sidekiq values as metadata on transaction" do
|
105
|
-
perform_job
|
106
|
-
|
107
|
-
transaction_hash = transaction.to_h
|
108
|
-
expect(transaction_hash["metadata"].keys)
|
109
|
-
.to_not include(*Appsignal::Hooks::SidekiqPlugin::EXCLUDED_JOB_KEYS)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
context "with parameter filtering" do
|
114
|
-
before do
|
115
|
-
Appsignal.config = project_fixture_config("production")
|
116
|
-
Appsignal.config[:filter_parameters] = ["foo"]
|
117
|
-
end
|
118
|
-
|
119
|
-
it "filters selected arguments" do
|
120
|
-
perform_job
|
121
54
|
|
122
|
-
|
123
|
-
|
124
|
-
"params" => [
|
125
|
-
"foo",
|
126
|
-
{
|
127
|
-
"foo" => "[FILTERED]",
|
128
|
-
"bar" => "Bar",
|
129
|
-
"baz" => { "1" => "foo" }
|
130
|
-
}
|
131
|
-
]
|
132
|
-
)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
context "with encrypted arguments" do
|
137
|
-
before do
|
138
|
-
item["encrypt"] = true
|
139
|
-
item["args"] << "super secret value" # Last argument will be replaced
|
140
|
-
end
|
141
|
-
|
142
|
-
it "replaces the last argument (the secret bag) with an [encrypted data] string" do
|
143
|
-
perform_job
|
144
|
-
|
145
|
-
transaction_hash = transaction.to_h
|
146
|
-
expect(transaction_hash["sample_data"]).to include(
|
147
|
-
"params" => expected_args << "[encrypted data]"
|
148
|
-
)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
context "when using the Sidekiq delayed extension" do
|
153
|
-
let(:item) do
|
154
|
-
{
|
155
|
-
"jid" => jid,
|
156
|
-
"class" => "Sidekiq::Extensions::DelayedClass",
|
157
|
-
"queue" => "default",
|
158
|
-
"args" => [
|
159
|
-
"---\n- !ruby/class 'DelayedTestClass'\n- :foo_method\n- - :bar: baz\n"
|
160
|
-
],
|
161
|
-
"retry" => true,
|
162
|
-
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
163
|
-
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
164
|
-
"extra" => "data"
|
165
|
-
}
|
166
|
-
end
|
167
|
-
|
168
|
-
it "uses the delayed class and method name for the action" do
|
169
|
-
perform_job
|
170
|
-
|
171
|
-
transaction_hash = transaction.to_h
|
172
|
-
expect(transaction_hash["action"]).to eq("DelayedTestClass.foo_method")
|
173
|
-
expect(transaction_hash["sample_data"]).to include(
|
174
|
-
"params" => ["bar" => "baz"]
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
context "when job arguments is a malformed YAML object", :with_yaml_parse_error => true do
|
179
|
-
before { item["args"] = [] }
|
180
|
-
|
181
|
-
it "logs a warning and uses the default argument" do
|
182
|
-
perform_job
|
183
|
-
|
184
|
-
transaction_hash = transaction.to_h
|
185
|
-
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedClass#perform")
|
186
|
-
expect(transaction_hash["sample_data"]).to include("params" => [])
|
187
|
-
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
55
|
+
def self.error_handlers
|
56
|
+
@error_handlers ||= []
|
188
57
|
end
|
189
58
|
end
|
190
|
-
end
|
191
|
-
|
192
|
-
context "when using the Sidekiq ActiveRecord instance delayed extension" do
|
193
|
-
let(:item) do
|
194
|
-
{
|
195
|
-
"jid" => jid,
|
196
|
-
"class" => "Sidekiq::Extensions::DelayedModel",
|
197
|
-
"queue" => "default",
|
198
|
-
"args" => [
|
199
|
-
"---\n- !ruby/object:DelayedTestClass {}\n- :foo_method\n- - :bar: :baz\n"
|
200
|
-
],
|
201
|
-
"retry" => true,
|
202
|
-
"created_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
203
|
-
"enqueued_at" => Time.parse("2001-01-01 10:00:00UTC").to_f,
|
204
|
-
"extra" => "data"
|
205
|
-
}
|
206
|
-
end
|
207
|
-
|
208
|
-
it "uses the delayed class and method name for the action" do
|
209
|
-
perform_job
|
210
|
-
|
211
|
-
transaction_hash = transaction.to_h
|
212
|
-
expect(transaction_hash["action"]).to eq("DelayedTestClass#foo_method")
|
213
|
-
expect(transaction_hash["sample_data"]).to include(
|
214
|
-
"params" => ["bar" => "baz"]
|
215
|
-
)
|
216
|
-
end
|
217
59
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
it "logs a warning and uses the default argument" do
|
222
|
-
perform_job
|
223
|
-
|
224
|
-
transaction_hash = transaction.to_h
|
225
|
-
expect(transaction_hash["action"]).to eq("Sidekiq::Extensions::DelayedModel#perform")
|
226
|
-
expect(transaction_hash["sample_data"]).to include("params" => [])
|
227
|
-
expect(log_contents(log)).to contains_log(:warn, "Unable to load YAML")
|
60
|
+
def add_middleware(middleware)
|
61
|
+
Sidekiq.configure_server do |sidekiq_config|
|
62
|
+
sidekiq_config.middlewares.add(middleware)
|
228
63
|
end
|
229
64
|
end
|
230
|
-
end
|
231
|
-
|
232
|
-
context "with an error" do
|
233
|
-
let(:error) { ExampleException }
|
234
65
|
|
235
|
-
it "creates a transaction and adds the error" do
|
236
|
-
expect(Appsignal).to receive(:increment_counter)
|
237
|
-
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :failed)
|
238
|
-
expect(Appsignal).to receive(:increment_counter)
|
239
|
-
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
240
|
-
|
241
|
-
expect do
|
242
|
-
perform_job { raise error, "uh oh" }
|
243
|
-
end.to raise_error(error)
|
244
|
-
|
245
|
-
transaction_hash = transaction.to_h
|
246
|
-
expect(transaction_hash).to include(
|
247
|
-
"id" => jid,
|
248
|
-
"action" => "TestClass#perform",
|
249
|
-
"error" => {
|
250
|
-
"name" => "ExampleException",
|
251
|
-
"message" => "uh oh",
|
252
|
-
# TODO: backtrace should be an Array of Strings
|
253
|
-
# https://github.com/appsignal/appsignal-agent/issues/294
|
254
|
-
"backtrace" => kind_of(String)
|
255
|
-
},
|
256
|
-
"metadata" => {
|
257
|
-
"extra" => "data",
|
258
|
-
"queue" => "default",
|
259
|
-
"retry_count" => "0"
|
260
|
-
},
|
261
|
-
"namespace" => namespace,
|
262
|
-
"sample_data" => {
|
263
|
-
"environment" => {},
|
264
|
-
"params" => expected_args,
|
265
|
-
"tags" => {},
|
266
|
-
"breadcrumbs" => []
|
267
|
-
}
|
268
|
-
)
|
269
|
-
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
context "without an error" do
|
274
|
-
it "creates a transaction with events" do
|
275
|
-
expect(Appsignal).to receive(:increment_counter)
|
276
|
-
.with("sidekiq_queue_job_count", 1, :queue => "default", :status => :processed)
|
277
|
-
|
278
|
-
perform_job
|
279
|
-
|
280
|
-
transaction_hash = transaction.to_h
|
281
|
-
expect(transaction_hash).to include(
|
282
|
-
"id" => jid,
|
283
|
-
"action" => "TestClass#perform",
|
284
|
-
"error" => nil,
|
285
|
-
"metadata" => {
|
286
|
-
"extra" => "data",
|
287
|
-
"queue" => "default",
|
288
|
-
"retry_count" => "0"
|
289
|
-
},
|
290
|
-
"namespace" => namespace,
|
291
|
-
"sample_data" => {
|
292
|
-
"environment" => {},
|
293
|
-
"params" => expected_args,
|
294
|
-
"tags" => {},
|
295
|
-
"breadcrumbs" => []
|
296
|
-
}
|
297
|
-
)
|
298
|
-
# TODO: Not available in transaction.to_h yet.
|
299
|
-
# https://github.com/appsignal/appsignal-agent/issues/293
|
300
|
-
expect(transaction.request.env).to eq(
|
301
|
-
:queue_start => Time.parse("2001-01-01 10:00:00UTC").to_f
|
302
|
-
)
|
303
|
-
expect_transaction_to_have_sidekiq_event(transaction_hash)
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
def perform_job
|
308
|
-
Timecop.freeze(Time.parse("2001-01-01 10:01:00UTC")) do
|
309
|
-
plugin.call(worker, item, queue) do
|
310
|
-
yield if block_given?
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
def transaction
|
316
|
-
last_transaction
|
317
|
-
end
|
318
|
-
|
319
|
-
def expect_transaction_to_have_sidekiq_event(transaction_hash)
|
320
|
-
events = transaction_hash["events"]
|
321
|
-
expect(events.count).to eq(1)
|
322
|
-
expect(events.first).to include(
|
323
|
-
"name" => "perform_job.sidekiq",
|
324
|
-
"title" => "",
|
325
|
-
"count" => 1,
|
326
|
-
"body" => "",
|
327
|
-
"body_format" => Appsignal::EventFormatter::DEFAULT
|
328
|
-
)
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
if DependencyHelper.active_job_present?
|
333
|
-
require "active_job"
|
334
|
-
require "action_mailer"
|
335
|
-
require "sidekiq/testing"
|
336
|
-
|
337
|
-
describe "Sidekiq ActiveJob integration" do
|
338
|
-
let(:namespace) { Appsignal::Transaction::BACKGROUND_JOB }
|
339
|
-
let(:time) { Time.parse("2001-01-01 10:00:00UTC") }
|
340
|
-
let(:log) { StringIO.new }
|
341
|
-
let(:given_args) do
|
342
|
-
[
|
343
|
-
"foo",
|
344
|
-
{
|
345
|
-
:foo => "Foo",
|
346
|
-
"bar" => "Bar",
|
347
|
-
"baz" => { "1" => "foo" }
|
348
|
-
}
|
349
|
-
]
|
350
|
-
end
|
351
|
-
let(:expected_args) do
|
352
|
-
[
|
353
|
-
"foo",
|
354
|
-
{
|
355
|
-
"_aj_symbol_keys" => ["foo"],
|
356
|
-
"foo" => "Foo",
|
357
|
-
"bar" => "Bar",
|
358
|
-
"baz" => {
|
359
|
-
"_aj_symbol_keys" => [],
|
360
|
-
"1" => "foo"
|
361
|
-
}
|
362
|
-
}
|
363
|
-
]
|
364
|
-
end
|
365
|
-
let(:expected_tags) do
|
366
|
-
{}.tap do |hash|
|
367
|
-
hash["active_job_id"] = kind_of(String)
|
368
|
-
if DependencyHelper.rails_version >= Gem::Version.new("5.0.0")
|
369
|
-
hash["provider_job_id"] = kind_of(String)
|
370
|
-
end
|
371
|
-
end
|
372
|
-
end
|
373
66
|
before do
|
374
|
-
|
375
|
-
|
376
|
-
ActiveJob::Base.queue_adapter = :sidekiq
|
377
|
-
|
378
|
-
class ActiveJobSidekiqTestJob < ActiveJob::Base
|
379
|
-
self.queue_adapter = :sidekiq
|
380
|
-
|
381
|
-
def perform(*_args)
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
|
-
class ActiveJobSidekiqErrorTestJob < ActiveJob::Base
|
386
|
-
self.queue_adapter = :sidekiq
|
387
|
-
|
388
|
-
def perform(*_args)
|
389
|
-
raise "uh oh"
|
390
|
-
end
|
391
|
-
end
|
392
|
-
# Manually add the AppSignal middleware for the Testing environment.
|
393
|
-
# It doesn't use configured middlewares by default looks like.
|
394
|
-
# We test somewhere else if the middleware is installed properly.
|
395
|
-
Sidekiq::Testing.server_middleware do |chain|
|
396
|
-
chain.add Appsignal::Hooks::SidekiqPlugin
|
397
|
-
end
|
398
|
-
end
|
399
|
-
around do |example|
|
400
|
-
keep_transactions do
|
401
|
-
Sidekiq::Testing.fake! do
|
402
|
-
example.run
|
403
|
-
end
|
404
|
-
end
|
405
|
-
end
|
406
|
-
after do
|
407
|
-
Object.send(:remove_const, :ActiveJobSidekiqTestJob)
|
408
|
-
Object.send(:remove_const, :ActiveJobSidekiqErrorTestJob)
|
409
|
-
end
|
410
|
-
|
411
|
-
it "reports the transaction from the ActiveJob integration" do
|
412
|
-
perform_job(ActiveJobSidekiqTestJob, given_args)
|
413
|
-
|
414
|
-
transaction = last_transaction
|
415
|
-
transaction_hash = transaction.to_h
|
416
|
-
expect(transaction_hash).to include(
|
417
|
-
"action" => "ActiveJobSidekiqTestJob#perform",
|
418
|
-
"error" => nil,
|
419
|
-
"namespace" => namespace,
|
420
|
-
"metadata" => hash_including(
|
421
|
-
"queue" => "default"
|
422
|
-
),
|
423
|
-
"sample_data" => hash_including(
|
424
|
-
"environment" => {},
|
425
|
-
"params" => [expected_args],
|
426
|
-
"tags" => expected_tags.merge("queue" => "default")
|
427
|
-
)
|
428
|
-
)
|
429
|
-
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
430
|
-
events = transaction_hash["events"]
|
431
|
-
.sort_by { |e| e["start"] }
|
432
|
-
.map { |event| event["name"] }
|
433
|
-
expect(events)
|
434
|
-
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
67
|
+
Appsignal.config = project_fixture_config
|
68
|
+
stub_const "Sidekiq", SidekiqMock
|
435
69
|
end
|
436
70
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
end.to raise_error(RuntimeError, "uh oh")
|
442
|
-
|
443
|
-
transaction = last_transaction
|
444
|
-
transaction_hash = transaction.to_h
|
445
|
-
expect(transaction_hash).to include(
|
446
|
-
"action" => "ActiveJobSidekiqErrorTestJob#perform",
|
447
|
-
"error" => {
|
448
|
-
"name" => "RuntimeError",
|
449
|
-
"message" => "uh oh",
|
450
|
-
"backtrace" => kind_of(String)
|
451
|
-
},
|
452
|
-
"namespace" => namespace,
|
453
|
-
"metadata" => hash_including(
|
454
|
-
"queue" => "default"
|
455
|
-
),
|
456
|
-
"sample_data" => hash_including(
|
457
|
-
"environment" => {},
|
458
|
-
"params" => [expected_args],
|
459
|
-
"tags" => expected_tags.merge("queue" => "default")
|
460
|
-
)
|
461
|
-
)
|
462
|
-
expect(transaction.request.env).to eq(:queue_start => time.to_f)
|
463
|
-
events = transaction_hash["events"]
|
464
|
-
.sort_by { |e| e["start"] }
|
465
|
-
.map { |event| event["name"] }
|
466
|
-
expect(events)
|
467
|
-
.to eq(["perform_job.sidekiq", "perform_start.active_job", "perform.active_job"])
|
468
|
-
end
|
71
|
+
it "adds error handler" do
|
72
|
+
Sidekiq.middleware_mock = SidekiqMiddlewareMockWithPrepend
|
73
|
+
described_class.new.install
|
74
|
+
expect(Sidekiq.error_handlers).to include(Appsignal::Integrations::SidekiqErrorHandler)
|
469
75
|
end
|
470
76
|
|
471
|
-
context "
|
472
|
-
|
77
|
+
context "when Sidekiq middleware responds to prepend method" do # Sidekiq 3.3.0 and newer
|
78
|
+
before { Sidekiq.middleware_mock = SidekiqMiddlewareMockWithPrepend }
|
473
79
|
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
80
|
+
it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain in the first position" do
|
81
|
+
user_middleware1 = proc {}
|
82
|
+
add_middleware(user_middleware1)
|
83
|
+
described_class.new.install
|
84
|
+
user_middleware2 = proc {}
|
85
|
+
add_middleware(user_middleware2)
|
480
86
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
expect(transaction_hash).to include(
|
487
|
-
"action" => "ActionMailerSidekiqTestJob#welcome",
|
488
|
-
"sample_data" => hash_including(
|
489
|
-
"params" => ["ActionMailerSidekiqTestJob", "welcome", "deliver_now"] + expected_args
|
490
|
-
)
|
491
|
-
)
|
87
|
+
expect(Sidekiq.server_middleware).to eql([
|
88
|
+
Appsignal::Integrations::SidekiqMiddleware, # Prepend makes it the first entry
|
89
|
+
user_middleware1,
|
90
|
+
user_middleware2
|
91
|
+
])
|
492
92
|
end
|
493
93
|
end
|
494
94
|
|
495
|
-
|
496
|
-
|
497
|
-
yield
|
498
|
-
# Combined with Sidekiq::Testing.fake! and drain_all we get a
|
499
|
-
# enqueue_at in the job data.
|
500
|
-
Sidekiq::Worker.drain_all
|
501
|
-
end
|
502
|
-
end
|
95
|
+
context "when Sidekiq middleware does not respond to prepend method" do
|
96
|
+
before { Sidekiq.middleware_mock = SidekiqMiddlewareMockWithoutPrepend }
|
503
97
|
|
504
|
-
|
505
|
-
|
506
|
-
|
98
|
+
it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
|
99
|
+
user_middleware1 = proc {}
|
100
|
+
add_middleware(user_middleware1)
|
101
|
+
described_class.new.install
|
102
|
+
user_middleware2 = proc {}
|
103
|
+
add_middleware(user_middleware2)
|
507
104
|
|
508
|
-
|
509
|
-
|
105
|
+
# Add middlewares in whatever order they were added
|
106
|
+
expect(Sidekiq.server_middleware).to eql([
|
107
|
+
user_middleware1,
|
108
|
+
Appsignal::Integrations::SidekiqMiddleware,
|
109
|
+
user_middleware2
|
110
|
+
])
|
111
|
+
end
|
510
112
|
end
|
511
113
|
end
|
512
114
|
end
|