appsignal 2.11.2-java → 2.11.7-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.yml +2 -0
- data/.semaphore/semaphore.yml +1 -1
- data/CHANGELOG.md +21 -0
- data/Rakefile +9 -6
- data/build_matrix.yml +1 -1
- data/ext/agent.yml +17 -17
- data/lib/appsignal/hooks/action_cable.rb +10 -2
- data/lib/appsignal/hooks/shoryuken.rb +43 -4
- data/lib/appsignal/hooks/sidekiq.rb +5 -1
- data/lib/appsignal/integrations/object.rb +4 -34
- data/lib/appsignal/integrations/object_ruby_19.rb +37 -0
- data/lib/appsignal/integrations/object_ruby_modern.rb +64 -0
- data/lib/appsignal/system.rb +4 -0
- data/lib/appsignal/version.rb +1 -1
- data/spec/lib/appsignal/hooks/action_cable_spec.rb +88 -0
- data/spec/lib/appsignal/hooks/shoryuken_spec.rb +151 -104
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +61 -7
- data/spec/lib/appsignal/integrations/object_19_spec.rb +266 -0
- data/spec/lib/appsignal/integrations/object_spec.rb +29 -10
- metadata +7 -3
data/lib/appsignal/version.rb
CHANGED
@@ -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
|
@@ -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) {
|
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
|
-
|
13
|
-
|
14
|
-
|
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(:
|
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" =>
|
29
|
+
double(:message_id => "msg1", :attributes => { "SentTimestamp" => sent_timestamp })
|
21
30
|
end
|
22
31
|
|
23
32
|
context "with complex argument" do
|
24
|
-
let(:body)
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
149
|
-
|
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
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
@@ -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
|
@@ -36,15 +53,52 @@ describe Appsignal::Hooks::SidekiqHook do
|
|
36
53
|
end
|
37
54
|
end
|
38
55
|
|
56
|
+
def add_middleware(middleware)
|
57
|
+
Sidekiq.configure_server do |sidekiq_config|
|
58
|
+
sidekiq_config.middlewares.add(middleware)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
39
62
|
before do
|
40
63
|
Appsignal.config = project_fixture_config
|
41
64
|
stub_const "Sidekiq", SidekiqMock
|
42
65
|
end
|
43
66
|
|
44
|
-
|
45
|
-
|
67
|
+
context "when Sidekiq middleware responds to prepend method" do # Sidekiq 3.3.0 and newer
|
68
|
+
before { Sidekiq.middleware_mock = SidekiqMiddlewareMockWithPrepend }
|
69
|
+
|
70
|
+
it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain in the first position" do
|
71
|
+
user_middleware1 = proc {}
|
72
|
+
add_middleware(user_middleware1)
|
73
|
+
described_class.new.install
|
74
|
+
user_middleware2 = proc {}
|
75
|
+
add_middleware(user_middleware2)
|
46
76
|
|
47
|
-
|
77
|
+
expect(Sidekiq.server_middleware).to eql([
|
78
|
+
Appsignal::Hooks::SidekiqPlugin, # Prepend makes it the first entry
|
79
|
+
user_middleware1,
|
80
|
+
user_middleware2
|
81
|
+
])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when Sidekiq middleware does not respond to prepend method" do
|
86
|
+
before { Sidekiq.middleware_mock = SidekiqMiddlewareMockWithoutPrepend }
|
87
|
+
|
88
|
+
it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
|
89
|
+
user_middleware1 = proc {}
|
90
|
+
add_middleware(user_middleware1)
|
91
|
+
described_class.new.install
|
92
|
+
user_middleware2 = proc {}
|
93
|
+
add_middleware(user_middleware2)
|
94
|
+
|
95
|
+
# Add middlewares in whatever order they were added
|
96
|
+
expect(Sidekiq.server_middleware).to eql([
|
97
|
+
user_middleware1,
|
98
|
+
Appsignal::Hooks::SidekiqPlugin,
|
99
|
+
user_middleware2
|
100
|
+
])
|
101
|
+
end
|
48
102
|
end
|
49
103
|
end
|
50
104
|
end
|