appsignal 2.10.6 → 2.11.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.semaphore/semaphore.yml +6 -0
- data/CHANGELOG.md +20 -0
- data/build_matrix.yml +5 -0
- data/ext/agent.yml +19 -19
- data/ext/appsignal_extension.c +10 -1
- data/ext/base.rb +15 -4
- data/lib/appsignal.rb +21 -1
- data/lib/appsignal/capistrano.rb +2 -0
- data/lib/appsignal/config.rb +6 -2
- data/lib/appsignal/environment.rb +126 -0
- data/lib/appsignal/extension/jruby.rb +10 -0
- data/lib/appsignal/hooks/net_http.rb +10 -13
- data/lib/appsignal/hooks/redis.rb +2 -0
- data/lib/appsignal/hooks/sequel.rb +2 -0
- data/lib/appsignal/integrations/delayed_job_plugin.rb +16 -3
- data/lib/appsignal/integrations/object.rb +4 -0
- data/lib/appsignal/integrations/resque_active_job.rb +12 -4
- data/lib/appsignal/system.rb +0 -6
- data/lib/appsignal/transaction.rb +22 -7
- data/lib/appsignal/version.rb +1 -1
- data/spec/lib/appsignal/cli/diagnose_spec.rb +2 -1
- data/spec/lib/appsignal/config_spec.rb +6 -1
- data/spec/lib/appsignal/environment_spec.rb +167 -0
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +198 -166
- data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +55 -13
- data/spec/lib/appsignal/system_spec.rb +0 -36
- data/spec/lib/appsignal/transaction_spec.rb +30 -13
- data/spec/lib/appsignal_spec.rb +22 -0
- data/spec/support/helpers/dependency_helper.rb +5 -0
- data/spec/support/helpers/env_helpers.rb +1 -1
- data/spec/support/helpers/environment_metdata_helper.rb +16 -0
- metadata +13 -9
- data/lib/appsignal/integrations/net_http.rb +0 -16
@@ -60,6 +60,9 @@ module Appsignal
|
|
60
60
|
[:appsignal_string],
|
61
61
|
:appsignal_string
|
62
62
|
attach_function :appsignal_running_in_container, [], :bool
|
63
|
+
attach_function :appsignal_set_environment_metadata,
|
64
|
+
[:appsignal_string, :appsignal_string],
|
65
|
+
:void
|
63
66
|
|
64
67
|
# Metrics methods
|
65
68
|
attach_function :appsignal_set_gauge,
|
@@ -224,6 +227,13 @@ module Appsignal
|
|
224
227
|
appsignal_running_in_container
|
225
228
|
end
|
226
229
|
|
230
|
+
def set_environment_metadata(key, value)
|
231
|
+
appsignal_set_environment_metadata(
|
232
|
+
make_appsignal_string(key),
|
233
|
+
make_appsignal_string(value)
|
234
|
+
)
|
235
|
+
end
|
236
|
+
|
227
237
|
def set_gauge(key, value, tags)
|
228
238
|
appsignal_set_gauge(make_appsignal_string(key), value, tags.pointer)
|
229
239
|
end
|
@@ -13,23 +13,20 @@ module Appsignal
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def install
|
16
|
-
|
17
|
-
|
18
|
-
Net::HTTP.send(:prepend, Appsignal::Integrations::NetHttpIntegration)
|
19
|
-
else
|
20
|
-
Net::HTTP.class_eval do
|
21
|
-
alias request_without_appsignal request
|
16
|
+
Net::HTTP.class_eval do
|
17
|
+
alias request_without_appsignal request
|
22
18
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
19
|
+
def request(request, body = nil, &block)
|
20
|
+
Appsignal.instrument(
|
21
|
+
"request.net_http",
|
22
|
+
"#{request.method} #{use_ssl? ? "https" : "http"}://#{request["host"] || address}"
|
23
|
+
) do
|
24
|
+
request_without_appsignal(request, body, &block)
|
30
25
|
end
|
31
26
|
end
|
32
27
|
end
|
28
|
+
|
29
|
+
Appsignal::Environment.report_enabled("net_http")
|
33
30
|
end
|
34
31
|
end
|
35
32
|
end
|
@@ -27,9 +27,8 @@ module Appsignal
|
|
27
27
|
method_name = "perform"
|
28
28
|
else
|
29
29
|
# Delayed Job
|
30
|
-
args = extract_value(
|
31
|
-
|
32
|
-
class_name, method_name = class_and_method_name.split("#")
|
30
|
+
args = extract_value(payload, :args, {})
|
31
|
+
class_name, method_name = class_and_method_name_from_object_or_hash(payload, job.name)
|
33
32
|
end
|
34
33
|
|
35
34
|
params = Appsignal::Utils::HashSanitizer.sanitize(
|
@@ -54,6 +53,20 @@ module Appsignal
|
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
56
|
+
def self.class_and_method_name_from_object_or_hash(payload, default_name)
|
57
|
+
# Attempt to find appsignal_name override
|
58
|
+
class_and_method_name = extract_value(payload, :appsignal_name, nil)
|
59
|
+
return class_and_method_name.split("#") if class_and_method_name.is_a?(String)
|
60
|
+
|
61
|
+
pound_split = default_name.split("#")
|
62
|
+
return pound_split if pound_split.length == 2
|
63
|
+
|
64
|
+
dot_split = default_name.split(".")
|
65
|
+
return default_name if dot_split.length == 2
|
66
|
+
|
67
|
+
["unknown"]
|
68
|
+
end
|
69
|
+
|
57
70
|
def self.extract_value(object_or_hash, field, default_value = nil, convert_to_s = false)
|
58
71
|
value = nil
|
59
72
|
|
@@ -14,12 +14,18 @@ module Appsignal
|
|
14
14
|
Appsignal.config[:filter_parameters]
|
15
15
|
)
|
16
16
|
|
17
|
+
queue_start =
|
18
|
+
if job.respond_to?(:enqueued_at) && job.enqueued_at
|
19
|
+
Time.parse(job.enqueued_at).utc
|
20
|
+
end
|
21
|
+
|
17
22
|
Appsignal.monitor_single_transaction(
|
18
23
|
"perform_job.resque",
|
19
|
-
:class
|
20
|
-
:method
|
21
|
-
:params
|
22
|
-
:
|
24
|
+
:class => job.class.to_s,
|
25
|
+
:method => "perform",
|
26
|
+
:params => params,
|
27
|
+
:queue_start => queue_start,
|
28
|
+
:metadata => {
|
23
29
|
:id => job.job_id,
|
24
30
|
:queue => job.queue_name
|
25
31
|
}
|
@@ -28,6 +34,8 @@ module Appsignal
|
|
28
34
|
end
|
29
35
|
end
|
30
36
|
end
|
37
|
+
|
38
|
+
Appsignal::Environment.report("ruby_active_job_resque_enabled") { true }
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
data/lib/appsignal/system.rb
CHANGED
@@ -76,14 +76,8 @@ module Appsignal
|
|
76
76
|
ldd_version && ldd_version[0]
|
77
77
|
end
|
78
78
|
|
79
|
-
# @api private
|
80
79
|
def self.jruby?
|
81
80
|
RUBY_PLATFORM == "java"
|
82
81
|
end
|
83
|
-
|
84
|
-
# @api private
|
85
|
-
def self.ruby_2_or_up?
|
86
|
-
versionify(RUBY_VERSION) >= versionify("2.0")
|
87
|
-
end
|
88
82
|
end
|
89
83
|
end
|
@@ -228,12 +228,27 @@ module Appsignal
|
|
228
228
|
Appsignal.logger.warn("Queue start value #{start} is too big")
|
229
229
|
end
|
230
230
|
|
231
|
+
# Set the queue time based on the HTTP header or `:queue_start` env key
|
232
|
+
# value.
|
233
|
+
#
|
234
|
+
# This method will first try to read the queue time from the HTTP headers
|
235
|
+
# `X-Request-Start` or `X-Queue-Start`. Which are parsed by Rack as
|
236
|
+
# `HTTP_X_QUEUE_START` and `HTTP_X_REQUEST_START`.
|
237
|
+
# The header value is parsed by AppSignal as either milliseconds or
|
238
|
+
# microseconds.
|
239
|
+
#
|
240
|
+
# If no headers are found, or the value could not be parsed, it falls back
|
241
|
+
# on the `:queue_start` env key on this Transaction's {request} environment
|
242
|
+
# (called like `request.env[:queue_start]`). This value is parsed by
|
243
|
+
# AppSignal as seconds.
|
244
|
+
#
|
245
|
+
# @see https://docs.appsignal.com/ruby/instrumentation/request-queue-time.html
|
246
|
+
# @return [void]
|
231
247
|
def set_http_or_background_queue_start
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
end
|
248
|
+
start = http_queue_start || background_queue_start
|
249
|
+
return unless start
|
250
|
+
|
251
|
+
set_queue_start(start)
|
237
252
|
end
|
238
253
|
|
239
254
|
def set_metadata(key, value)
|
@@ -346,14 +361,14 @@ module Appsignal
|
|
346
361
|
#
|
347
362
|
# @return [nil] if no {#environment} is present.
|
348
363
|
# @return [nil] if there is no `:queue_start` in the {#environment}.
|
349
|
-
# @return [Integer]
|
364
|
+
# @return [Integer] `:queue_start` time (in seconds) converted to milliseconds
|
350
365
|
def background_queue_start
|
351
366
|
env = environment
|
352
367
|
return unless env
|
353
368
|
queue_start = env[:queue_start]
|
354
369
|
return unless queue_start
|
355
370
|
|
356
|
-
(queue_start.to_f * 1000.0).to_i
|
371
|
+
(queue_start.to_f * 1000.0).to_i # Convert seconds to milliseconds
|
357
372
|
end
|
358
373
|
|
359
374
|
# Returns HTTP queue start time in milliseconds.
|
data/lib/appsignal/version.rb
CHANGED
@@ -263,7 +263,8 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
|
|
263
263
|
},
|
264
264
|
"download" => {
|
265
265
|
"download_url" => kind_of(String),
|
266
|
-
"checksum" => "verified"
|
266
|
+
"checksum" => "verified",
|
267
|
+
"http_proxy" => nil
|
267
268
|
},
|
268
269
|
"build" => {
|
269
270
|
"time" => kind_of(String),
|
@@ -148,6 +148,7 @@ describe Appsignal::Config do
|
|
148
148
|
:instrument_redis => true,
|
149
149
|
:instrument_sequel => true,
|
150
150
|
:skip_session_data => false,
|
151
|
+
:send_environment_metadata => true,
|
151
152
|
:send_params => true,
|
152
153
|
:endpoint => "https://push.appsignal.com",
|
153
154
|
:push_api_key => "abc",
|
@@ -411,7 +412,8 @@ describe Appsignal::Config do
|
|
411
412
|
:instrument_sequel => false,
|
412
413
|
:files_world_accessible => false,
|
413
414
|
:request_headers => %w[accept accept-charset],
|
414
|
-
:revision => "v2.5.1"
|
415
|
+
:revision => "v2.5.1",
|
416
|
+
:send_environment_metadata => false
|
415
417
|
}
|
416
418
|
end
|
417
419
|
before do
|
@@ -428,6 +430,7 @@ describe Appsignal::Config do
|
|
428
430
|
ENV["APPSIGNAL_INSTRUMENT_SEQUEL"] = "false"
|
429
431
|
ENV["APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = "false"
|
430
432
|
ENV["APPSIGNAL_REQUEST_HEADERS"] = "accept,accept-charset"
|
433
|
+
ENV["APPSIGNAL_SEND_ENVIRONMENT_METADATA"] = "false"
|
431
434
|
ENV["APP_REVISION"] = "v2.5.1"
|
432
435
|
end
|
433
436
|
|
@@ -527,6 +530,7 @@ describe Appsignal::Config do
|
|
527
530
|
config[:running_in_container] = false
|
528
531
|
config[:dns_servers] = ["8.8.8.8", "8.8.4.4"]
|
529
532
|
config[:transaction_debug_mode] = true
|
533
|
+
config[:send_environment_metadata] = false
|
530
534
|
config[:revision] = "v2.5.1"
|
531
535
|
config.write_to_environment
|
532
536
|
end
|
@@ -555,6 +559,7 @@ describe Appsignal::Config do
|
|
555
559
|
expect(ENV["_APPSIGNAL_DNS_SERVERS"]).to eq "8.8.8.8,8.8.4.4"
|
556
560
|
expect(ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"]).to eq "true"
|
557
561
|
expect(ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"]).to eq "true"
|
562
|
+
expect(ENV["_APPSIGNAL_SEND_ENVIRONMENT_METADATA"]).to eq "false"
|
558
563
|
expect(ENV["_APP_REVISION"]).to eq "v2.5.1"
|
559
564
|
expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIR_PATH")
|
560
565
|
expect(ENV).to_not have_key("_APPSIGNAL_WORKING_DIRECTORY_PATH")
|
@@ -0,0 +1,167 @@
|
|
1
|
+
describe Appsignal::Environment do
|
2
|
+
include EnvironmentMetadataHelper
|
3
|
+
|
4
|
+
before(:context) { start_agent }
|
5
|
+
before { capture_environment_metadata_report_calls }
|
6
|
+
|
7
|
+
def report(key, &value_block)
|
8
|
+
described_class.report(key, &value_block)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ".report" do
|
12
|
+
it "sends environment metadata to the extension" do
|
13
|
+
logs =
|
14
|
+
capture_logs do
|
15
|
+
report("_test_ruby_version") { "1.0.0" }
|
16
|
+
expect_environment_metadata("_test_ruby_version", "1.0.0")
|
17
|
+
end
|
18
|
+
expect(logs).to be_empty
|
19
|
+
end
|
20
|
+
|
21
|
+
context "when the key is a non String type" do
|
22
|
+
it "does not set the value" do
|
23
|
+
logs =
|
24
|
+
capture_logs do
|
25
|
+
report(:_test_symbol) { "1.0.0" }
|
26
|
+
expect_not_environment_metadata(:_test_symbol)
|
27
|
+
expect_not_environment_metadata("_test_symbol")
|
28
|
+
end
|
29
|
+
expect(logs).to contains_log(
|
30
|
+
:error,
|
31
|
+
"Unable to report on environment metadata: Unsupported value type for :_test_symbol"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when the key is nil" do
|
37
|
+
it "does not set the value" do
|
38
|
+
logs =
|
39
|
+
capture_logs do
|
40
|
+
report(nil) { "1" }
|
41
|
+
expect_not_environment_metadata(nil)
|
42
|
+
end
|
43
|
+
expect(logs).to contains_log(
|
44
|
+
:error,
|
45
|
+
"Unable to report on environment metadata: Unsupported value type for nil"
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when the value is true or false" do
|
51
|
+
it "reports true or false as Strings" do
|
52
|
+
logs =
|
53
|
+
capture_logs do
|
54
|
+
report("_test_true") { true }
|
55
|
+
report("_test_false") { false }
|
56
|
+
expect_environment_metadata("_test_true", "true")
|
57
|
+
expect_environment_metadata("_test_false", "false")
|
58
|
+
end
|
59
|
+
expect(logs).to be_empty
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when the value is nil" do
|
64
|
+
it "does not set the value" do
|
65
|
+
logs =
|
66
|
+
capture_logs do
|
67
|
+
report("_test_ruby_version") { nil }
|
68
|
+
expect_not_environment_metadata("_test_ruby_version")
|
69
|
+
end
|
70
|
+
expect(logs).to contains_log(
|
71
|
+
:error,
|
72
|
+
"Unable to report on environment metadata \"_test_ruby_version\": " \
|
73
|
+
"Unsupported value type for nil"
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "when the value block raises an error" do
|
79
|
+
it "does not re-raise the error and writes it to the log" do
|
80
|
+
logs =
|
81
|
+
capture_logs do
|
82
|
+
report("_test_error") { raise "uh oh" }
|
83
|
+
expect_not_environment_metadata("_test_error")
|
84
|
+
end
|
85
|
+
expect(logs).to contains_log(
|
86
|
+
:error,
|
87
|
+
"Unable to report on environment metadata \"_test_error\":\n" \
|
88
|
+
"RuntimeError: uh oh"
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "when something unforseen errors" do
|
94
|
+
it "does not re-raise the error and writes it to the log" do
|
95
|
+
klass = Class.new do
|
96
|
+
def inspect
|
97
|
+
raise "inspect error"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
logs =
|
102
|
+
capture_logs do
|
103
|
+
report(klass.new) { raise "value error" }
|
104
|
+
expect(Appsignal::Extension).to_not have_received(:set_environment_metadata)
|
105
|
+
end
|
106
|
+
expect(logs).to contains_log(
|
107
|
+
:error,
|
108
|
+
"Unable to report on environment metadata:\n" \
|
109
|
+
"RuntimeError: inspect error"
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe ".report_supported_gems" do
|
116
|
+
it "reports about all AppSignal supported gems in the bundle" do
|
117
|
+
logs = capture_logs { described_class.report_supported_gems }
|
118
|
+
|
119
|
+
expect(logs).to be_empty
|
120
|
+
|
121
|
+
bundle_gem_specs = ::Bundler.rubygems.all_specs
|
122
|
+
rack_spec = bundle_gem_specs.find { |s| s.name == "rack" }
|
123
|
+
rake_spec = bundle_gem_specs.find { |s| s.name == "rake" }
|
124
|
+
expect_environment_metadata("ruby_rack_version", rack_spec.version.to_s)
|
125
|
+
expect_environment_metadata("ruby_rake_version", rake_spec.version.to_s)
|
126
|
+
expect(rack_spec.version.to_s).to_not be_empty
|
127
|
+
expect(rake_spec.version.to_s).to_not be_empty
|
128
|
+
end
|
129
|
+
|
130
|
+
context "when something unforseen errors" do
|
131
|
+
it "does not re-raise the error and writes it to the log" do
|
132
|
+
expect(Bundler).to receive(:rubygems).and_raise(RuntimeError, "bundler error")
|
133
|
+
|
134
|
+
logs = capture_logs { described_class.report_supported_gems }
|
135
|
+
expect(logs).to contains_log(
|
136
|
+
:error,
|
137
|
+
"Unable to report supported gems:\nRuntimeError: bundler error"
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe ".report_enabled" do
|
144
|
+
it "reports a feature being enabled" do
|
145
|
+
logs = capture_logs { described_class.report_enabled("a_test") }
|
146
|
+
|
147
|
+
expect(logs).to be_empty
|
148
|
+
expect_environment_metadata("ruby_a_test_enabled", "true")
|
149
|
+
end
|
150
|
+
|
151
|
+
context "when something unforseen errors" do
|
152
|
+
it "does not re-raise the error and writes it to the log" do
|
153
|
+
klass = Class.new do
|
154
|
+
def to_s
|
155
|
+
raise "to_s error"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
logs = capture_logs { described_class.report_enabled(klass.new) }
|
160
|
+
expect(logs).to contains_log(
|
161
|
+
:error,
|
162
|
+
"Unable to report integration enabled:\nRuntimeError: to_s error"
|
163
|
+
)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -36,6 +36,7 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
36
36
|
let(:time) { Time.parse("01-01-2001 10:01:00UTC") }
|
37
37
|
let(:created_at) { time - 3600 }
|
38
38
|
let(:run_at) { time - 3600 }
|
39
|
+
let(:payload_object) { double(:args => args) }
|
39
40
|
let(:job_data) do
|
40
41
|
{
|
41
42
|
:id => 123,
|
@@ -45,38 +46,40 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
45
46
|
:queue => "default",
|
46
47
|
:created_at => created_at,
|
47
48
|
:run_at => run_at,
|
48
|
-
:payload_object =>
|
49
|
+
:payload_object => payload_object
|
49
50
|
}
|
50
51
|
end
|
51
52
|
let(:args) { ["argument"] }
|
52
53
|
let(:job) { double(job_data) }
|
53
54
|
let(:invoked_block) { proc {} }
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
:class => "TestClass",
|
59
|
-
:method => "perform",
|
60
|
-
:metadata => {
|
61
|
-
:priority => 1,
|
62
|
-
:attempts => 1,
|
63
|
-
:queue => "default",
|
64
|
-
:id => "123"
|
65
|
-
},
|
66
|
-
:params => args,
|
67
|
-
:queue_start => run_at
|
68
|
-
}
|
69
|
-
end
|
70
|
-
after do
|
71
|
-
Timecop.freeze(time) do
|
56
|
+
def perform
|
57
|
+
Timecop.freeze(time) do
|
58
|
+
keep_transactions do
|
72
59
|
plugin.invoke_with_instrumentation(job, invoked_block)
|
73
60
|
end
|
74
61
|
end
|
62
|
+
end
|
75
63
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
64
|
+
context "with a normal call" do
|
65
|
+
it "wraps it in a transaction" do
|
66
|
+
perform
|
67
|
+
transaction_data = last_transaction.to_h
|
68
|
+
expect(transaction_data).to include(
|
69
|
+
"action" => "TestClass#perform",
|
70
|
+
"namespace" => "background_job",
|
71
|
+
"error" => nil
|
72
|
+
)
|
73
|
+
expect(transaction_data["events"].map { |e| e["name"] })
|
74
|
+
.to eql(["perform_job.delayed_job"])
|
75
|
+
expect(transaction_data["sample_data"]).to include(
|
76
|
+
"metadata" => {
|
77
|
+
"priority" => 1,
|
78
|
+
"attempts" => 1,
|
79
|
+
"queue" => "default",
|
80
|
+
"id" => "123"
|
81
|
+
},
|
82
|
+
"params" => ["argument"]
|
80
83
|
)
|
81
84
|
end
|
82
85
|
|
@@ -89,14 +92,13 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
89
92
|
end
|
90
93
|
|
91
94
|
it "adds the more complex arguments" do
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
)
|
95
|
+
perform
|
96
|
+
transaction_data = last_transaction.to_h
|
97
|
+
expect(transaction_data["sample_data"]).to include(
|
98
|
+
"params" => {
|
99
|
+
"foo" => "Foo",
|
100
|
+
"bar" => "Bar"
|
101
|
+
}
|
100
102
|
)
|
101
103
|
end
|
102
104
|
|
@@ -107,14 +109,13 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
107
109
|
end
|
108
110
|
|
109
111
|
it "filters selected arguments" do
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
)
|
112
|
+
perform
|
113
|
+
transaction_data = last_transaction.to_h
|
114
|
+
expect(transaction_data["sample_data"]).to include(
|
115
|
+
"params" => {
|
116
|
+
"foo" => "[FILTERED]",
|
117
|
+
"bar" => "Bar"
|
118
|
+
}
|
118
119
|
)
|
119
120
|
end
|
120
121
|
end
|
@@ -124,98 +125,135 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
124
125
|
let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") }
|
125
126
|
|
126
127
|
it "reports queue_start with run_at time" do
|
128
|
+
# TODO: Not available in transaction.to_h yet.
|
129
|
+
# https://github.com/appsignal/appsignal-agent/issues/293
|
127
130
|
expect(Appsignal).to receive(:monitor_transaction).with(
|
128
131
|
"perform_job.delayed_job",
|
129
|
-
|
130
|
-
)
|
132
|
+
a_hash_including(:queue_start => run_at)
|
133
|
+
).and_call_original
|
134
|
+
perform
|
131
135
|
end
|
132
136
|
end
|
133
137
|
|
134
|
-
context "with
|
138
|
+
context "with class method job" do
|
135
139
|
let(:job_data) do
|
136
|
-
{
|
137
|
-
:payload_object => double(
|
138
|
-
:appsignal_name => "CustomClass#perform",
|
139
|
-
:args => args
|
140
|
-
),
|
141
|
-
:id => "123",
|
142
|
-
:name => "TestClass#perform",
|
143
|
-
:priority => 1,
|
144
|
-
:attempts => 1,
|
145
|
-
:queue => "default",
|
146
|
-
:created_at => created_at,
|
147
|
-
:run_at => run_at
|
148
|
-
}
|
149
|
-
end
|
150
|
-
let(:default_params) do
|
151
|
-
{
|
152
|
-
:class => "CustomClass",
|
153
|
-
:method => "perform",
|
154
|
-
:metadata => {
|
155
|
-
:priority => 1,
|
156
|
-
:attempts => 1,
|
157
|
-
:queue => "default",
|
158
|
-
:id => "123"
|
159
|
-
},
|
160
|
-
:queue_start => run_at
|
161
|
-
}
|
140
|
+
{ :name => "CustomClassMethod.perform", :payload_object => payload_object }
|
162
141
|
end
|
163
142
|
|
164
|
-
it "wraps it in a transaction
|
165
|
-
|
166
|
-
|
167
|
-
default_params.merge(
|
168
|
-
:params => ["argument"]
|
169
|
-
)
|
170
|
-
)
|
143
|
+
it "wraps it in a transaction using the class method job name" do
|
144
|
+
perform
|
145
|
+
expect(last_transaction.to_h["action"]).to eql("CustomClassMethod.perform")
|
171
146
|
end
|
147
|
+
end
|
172
148
|
|
173
|
-
|
174
|
-
|
175
|
-
{
|
176
|
-
:foo => "Foo",
|
177
|
-
:bar => "Bar"
|
178
|
-
}
|
179
|
-
end
|
149
|
+
context "with custom name call" do
|
150
|
+
before { perform }
|
180
151
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
152
|
+
context "with appsignal_name defined" do
|
153
|
+
context "with payload_object being an object" do
|
154
|
+
context "with value" do
|
155
|
+
let(:payload_object) { double(:appsignal_name => "CustomClass#perform") }
|
156
|
+
|
157
|
+
it "wraps it in a transaction using the custom name" do
|
158
|
+
expect(last_transaction.to_h["action"]).to eql("CustomClass#perform")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "with non-String value" do
|
163
|
+
let(:payload_object) { double(:appsignal_name => Object.new) }
|
164
|
+
|
165
|
+
it "wraps it in a transaction using the original job name" do
|
166
|
+
expect(last_transaction.to_h["action"]).to eql("TestClass#perform")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context "with class method name as job" do
|
171
|
+
let(:payload_object) { double(:appsignal_name => "CustomClassMethod.perform") }
|
172
|
+
|
173
|
+
it "wraps it in a transaction using the custom name" do
|
174
|
+
perform
|
175
|
+
expect(last_transaction.to_h["action"]).to eql("CustomClassMethod.perform")
|
176
|
+
end
|
177
|
+
end
|
191
178
|
end
|
192
179
|
|
193
|
-
context "with
|
194
|
-
|
195
|
-
|
196
|
-
|
180
|
+
context "with payload_object being a Hash" do
|
181
|
+
context "with value" do
|
182
|
+
let(:payload_object) { double(:appsignal_name => "CustomClassHash#perform") }
|
183
|
+
|
184
|
+
it "wraps it in a transaction using the custom name" do
|
185
|
+
expect(last_transaction.to_h["action"]).to eql("CustomClassHash#perform")
|
186
|
+
end
|
197
187
|
end
|
198
188
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
)
|
189
|
+
context "with non-String value" do
|
190
|
+
let(:payload_object) { double(:appsignal_name => Object.new) }
|
191
|
+
|
192
|
+
it "wraps it in a transaction using the original job name" do
|
193
|
+
expect(last_transaction.to_h["action"]).to eql("TestClass#perform")
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context "with class method name as job" do
|
198
|
+
let(:payload_object) { { :appsignal_name => "CustomClassMethod.perform" } }
|
199
|
+
|
200
|
+
it "wraps it in a transaction using the custom name" do
|
201
|
+
perform
|
202
|
+
expect(last_transaction.to_h["action"]).to eql("CustomClassMethod.perform")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context "with payload_object being acting like a Hash and returning a non-String value" do
|
208
|
+
class ClassActingAsHash
|
209
|
+
def self.[](_key)
|
210
|
+
Object.new
|
211
|
+
end
|
212
|
+
|
213
|
+
def self.appsignal_name
|
214
|
+
"ClassActingAsHash#perform"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
let(:payload_object) { ClassActingAsHash }
|
218
|
+
|
219
|
+
# We check for hash values before object values
|
220
|
+
# this means ClassActingAsHash returns `Object.new` instead
|
221
|
+
# of `self.appsignal_name`. Since this isn't a valid `String`
|
222
|
+
# we return the default job name as action name.
|
223
|
+
it "wraps it in a transaction using the original job name" do
|
224
|
+
expect(last_transaction.to_h["action"]).to eql("TestClass#perform")
|
209
225
|
end
|
210
226
|
end
|
211
227
|
end
|
212
228
|
end
|
213
229
|
|
230
|
+
context "without job name" do
|
231
|
+
let(:job_data) do
|
232
|
+
{ :name => "", :payload_object => payload_object }
|
233
|
+
end
|
234
|
+
|
235
|
+
it "wraps it in a transaction using the class method job name" do
|
236
|
+
perform
|
237
|
+
expect(last_transaction.to_h["action"]).to eql("unknown")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
context "with invalid job name" do
|
242
|
+
let(:job_data) do
|
243
|
+
{ :name => "Banana", :payload_object => payload_object }
|
244
|
+
end
|
245
|
+
|
246
|
+
it "wraps it in a transaction using the class method job name" do
|
247
|
+
perform
|
248
|
+
expect(last_transaction.to_h["action"]).to eql("unknown")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
214
252
|
if active_job_present?
|
215
253
|
require "active_job"
|
216
254
|
|
217
255
|
context "when wrapped by ActiveJob" do
|
218
|
-
let(:
|
256
|
+
let(:payload_object) do
|
219
257
|
ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(
|
220
258
|
"arguments" => args,
|
221
259
|
"job_class" => "TestClass",
|
@@ -233,32 +271,30 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
233
271
|
:queue => "default",
|
234
272
|
:created_at => created_at,
|
235
273
|
:run_at => run_at,
|
236
|
-
:payload_object =>
|
274
|
+
:payload_object => payload_object
|
237
275
|
)
|
238
276
|
end
|
239
|
-
let(:default_params) do
|
240
|
-
{
|
241
|
-
:class => "TestClass",
|
242
|
-
:method => "perform",
|
243
|
-
:metadata => {
|
244
|
-
:priority => 1,
|
245
|
-
:attempts => 1,
|
246
|
-
:queue => "default",
|
247
|
-
:id => "123"
|
248
|
-
},
|
249
|
-
:queue_start => run_at,
|
250
|
-
:params => args
|
251
|
-
}
|
252
|
-
end
|
253
277
|
let(:args) { ["activejob_argument"] }
|
254
278
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
279
|
+
it "wraps it in a transaction with the correct params" do
|
280
|
+
perform
|
281
|
+
transaction_data = last_transaction.to_h
|
282
|
+
expect(transaction_data).to include(
|
283
|
+
"action" => "TestClass#perform",
|
284
|
+
"namespace" => "background_job",
|
285
|
+
"error" => nil
|
286
|
+
)
|
287
|
+
expect(transaction_data["events"].map { |e| e["name"] })
|
288
|
+
.to eql(["perform_job.delayed_job"])
|
289
|
+
expect(transaction_data["sample_data"]).to include(
|
290
|
+
"metadata" => {
|
291
|
+
"priority" => 1,
|
292
|
+
"attempts" => 1,
|
293
|
+
"queue" => "default",
|
294
|
+
"id" => "123"
|
295
|
+
},
|
296
|
+
"params" => ["activejob_argument"]
|
297
|
+
)
|
262
298
|
end
|
263
299
|
|
264
300
|
context "with more complex params" do
|
@@ -270,14 +306,14 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
270
306
|
end
|
271
307
|
|
272
308
|
it "adds the more complex arguments" do
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
309
|
+
perform
|
310
|
+
transaction_data = last_transaction.to_h
|
311
|
+
expect(transaction_data).to include("action" => "TestClass#perform")
|
312
|
+
expect(transaction_data["sample_data"]).to include(
|
313
|
+
"params" => {
|
314
|
+
"foo" => "Foo",
|
315
|
+
"bar" => "Bar"
|
316
|
+
}
|
281
317
|
)
|
282
318
|
end
|
283
319
|
|
@@ -288,14 +324,14 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
288
324
|
end
|
289
325
|
|
290
326
|
it "filters selected arguments" do
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
327
|
+
perform
|
328
|
+
transaction_data = last_transaction.to_h
|
329
|
+
expect(transaction_data).to include("action" => "TestClass#perform")
|
330
|
+
expect(transaction_data["sample_data"]).to include(
|
331
|
+
"params" => {
|
332
|
+
"foo" => "[FILTERED]",
|
333
|
+
"bar" => "Bar"
|
334
|
+
}
|
299
335
|
)
|
300
336
|
end
|
301
337
|
end
|
@@ -307,8 +343,9 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
307
343
|
it "reports queue_start with run_at time" do
|
308
344
|
expect(Appsignal).to receive(:monitor_transaction).with(
|
309
345
|
"perform_job.delayed_job",
|
310
|
-
|
311
|
-
)
|
346
|
+
a_hash_including(:queue_start => run_at)
|
347
|
+
).and_call_original
|
348
|
+
perform
|
312
349
|
end
|
313
350
|
end
|
314
351
|
end
|
@@ -316,33 +353,28 @@ describe Appsignal::Hooks::DelayedJobHook do
|
|
316
353
|
end
|
317
354
|
|
318
355
|
context "with an erroring call" do
|
319
|
-
let(:error) { ExampleException }
|
320
|
-
let(:transaction) do
|
321
|
-
Appsignal::Transaction.new(
|
322
|
-
SecureRandom.uuid,
|
323
|
-
Appsignal::Transaction::BACKGROUND_JOB,
|
324
|
-
Appsignal::Transaction::GenericRequest.new({})
|
325
|
-
)
|
326
|
-
end
|
356
|
+
let(:error) { ExampleException.new("uh oh") }
|
327
357
|
before do
|
328
358
|
expect(invoked_block).to receive(:call).and_raise(error)
|
329
|
-
|
330
|
-
allow(Appsignal::Transaction).to receive(:current).and_return(transaction)
|
331
|
-
expect(Appsignal::Transaction).to receive(:create)
|
332
|
-
.with(
|
333
|
-
kind_of(String),
|
334
|
-
Appsignal::Transaction::BACKGROUND_JOB,
|
335
|
-
kind_of(Appsignal::Transaction::GenericRequest)
|
336
|
-
).and_return(transaction)
|
337
359
|
end
|
338
360
|
|
339
361
|
it "adds the error to the transaction" do
|
340
|
-
expect(transaction).to receive(:set_error).with(error)
|
341
|
-
expect(transaction).to receive(:complete)
|
342
|
-
|
343
362
|
expect do
|
344
|
-
|
363
|
+
perform
|
345
364
|
end.to raise_error(error)
|
365
|
+
|
366
|
+
transaction_data = last_transaction.to_h
|
367
|
+
expect(transaction_data).to include(
|
368
|
+
"action" => "TestClass#perform",
|
369
|
+
"namespace" => "background_job",
|
370
|
+
"error" => {
|
371
|
+
"name" => "ExampleException",
|
372
|
+
"message" => "uh oh",
|
373
|
+
# TODO: backtrace should be an Array of Strings
|
374
|
+
# https://github.com/appsignal/appsignal-agent/issues/294
|
375
|
+
"backtrace" => kind_of(String)
|
376
|
+
}
|
377
|
+
)
|
346
378
|
end
|
347
379
|
end
|
348
380
|
end
|