appsignal 3.10.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +197 -0
  4. data/Gemfile +1 -0
  5. data/Rakefile +1 -1
  6. data/benchmark.rake +99 -42
  7. data/lib/appsignal/cli/demo.rb +0 -1
  8. data/lib/appsignal/cli/diagnose.rb +1 -1
  9. data/lib/appsignal/config.rb +204 -130
  10. data/lib/appsignal/demo.rb +16 -26
  11. data/lib/appsignal/event_formatter/rom/sql_formatter.rb +1 -0
  12. data/lib/appsignal/event_formatter.rb +3 -2
  13. data/lib/appsignal/helpers/instrumentation.rb +331 -19
  14. data/lib/appsignal/hooks/action_cable.rb +21 -16
  15. data/lib/appsignal/hooks/active_job.rb +14 -8
  16. data/lib/appsignal/hooks/delayed_job.rb +1 -1
  17. data/lib/appsignal/hooks/shoryuken.rb +3 -63
  18. data/lib/appsignal/integrations/action_cable.rb +5 -7
  19. data/lib/appsignal/integrations/active_support_notifications.rb +1 -0
  20. data/lib/appsignal/integrations/capistrano/capistrano_2_tasks.rb +36 -35
  21. data/lib/appsignal/integrations/data_mapper.rb +1 -0
  22. data/lib/appsignal/integrations/delayed_job_plugin.rb +27 -33
  23. data/lib/appsignal/integrations/dry_monitor.rb +1 -0
  24. data/lib/appsignal/integrations/excon.rb +1 -0
  25. data/lib/appsignal/integrations/grape.rb +7 -0
  26. data/lib/appsignal/integrations/hanami.rb +8 -43
  27. data/lib/appsignal/integrations/http.rb +1 -0
  28. data/lib/appsignal/integrations/net_http.rb +1 -0
  29. data/lib/appsignal/integrations/object.rb +6 -0
  30. data/lib/appsignal/integrations/padrino.rb +8 -73
  31. data/lib/appsignal/integrations/que.rb +13 -20
  32. data/lib/appsignal/integrations/railtie.rb +36 -14
  33. data/lib/appsignal/integrations/rake.rb +1 -5
  34. data/lib/appsignal/integrations/redis.rb +1 -0
  35. data/lib/appsignal/integrations/redis_client.rb +1 -0
  36. data/lib/appsignal/integrations/resque.rb +2 -5
  37. data/lib/appsignal/integrations/shoryuken.rb +75 -0
  38. data/lib/appsignal/integrations/sidekiq.rb +7 -15
  39. data/lib/appsignal/integrations/sinatra.rb +8 -19
  40. data/lib/appsignal/integrations/unicorn.rb +1 -0
  41. data/lib/appsignal/integrations/webmachine.rb +2 -5
  42. data/lib/appsignal/loaders/grape.rb +13 -0
  43. data/lib/appsignal/loaders/hanami.rb +40 -0
  44. data/lib/appsignal/loaders/padrino.rb +68 -0
  45. data/lib/appsignal/loaders/sinatra.rb +24 -0
  46. data/lib/appsignal/loaders.rb +92 -0
  47. data/lib/appsignal/logger.rb +7 -3
  48. data/lib/appsignal/probes/helpers.rb +1 -0
  49. data/lib/appsignal/probes/mri.rb +1 -0
  50. data/lib/appsignal/probes/sidekiq.rb +1 -0
  51. data/lib/appsignal/probes.rb +3 -0
  52. data/lib/appsignal/rack/abstract_middleware.rb +20 -13
  53. data/lib/appsignal/rack/event_handler.rb +44 -13
  54. data/lib/appsignal/rack/generic_instrumentation.rb +1 -0
  55. data/lib/appsignal/rack/grape_middleware.rb +2 -1
  56. data/lib/appsignal/rack/streaming_listener.rb +1 -0
  57. data/lib/appsignal/rack.rb +35 -0
  58. data/lib/appsignal/span.rb +1 -0
  59. data/lib/appsignal/transaction.rb +308 -101
  60. data/lib/appsignal/utils/data.rb +0 -1
  61. data/lib/appsignal/utils/hash_sanitizer.rb +0 -1
  62. data/lib/appsignal/utils/integration_logger.rb +0 -13
  63. data/lib/appsignal/utils/integration_memory_logger.rb +0 -13
  64. data/lib/appsignal/utils/json.rb +0 -1
  65. data/lib/appsignal/utils/query_params_sanitizer.rb +0 -1
  66. data/lib/appsignal/utils/stdout_and_logger_message.rb +0 -1
  67. data/lib/appsignal/utils.rb +6 -0
  68. data/lib/appsignal/version.rb +1 -1
  69. data/lib/appsignal.rb +169 -14
  70. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  71. data/spec/lib/appsignal/cli/demo_spec.rb +0 -1
  72. data/spec/lib/appsignal/cli/diagnose/paths_spec.rb +1 -1
  73. data/spec/lib/appsignal/cli/diagnose_spec.rb +0 -1
  74. data/spec/lib/appsignal/config_spec.rb +291 -44
  75. data/spec/lib/appsignal/demo_spec.rb +1 -2
  76. data/spec/lib/appsignal/environment_spec.rb +4 -2
  77. data/spec/lib/appsignal/hooks/action_cable_spec.rb +43 -74
  78. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +3 -6
  79. data/spec/lib/appsignal/hooks/activejob_spec.rb +12 -3
  80. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +2 -443
  81. data/spec/lib/appsignal/hooks/dry_monitor_spec.rb +4 -7
  82. data/spec/lib/appsignal/hooks/excon_spec.rb +3 -6
  83. data/spec/lib/appsignal/hooks/gvl_spec.rb +2 -2
  84. data/spec/lib/appsignal/hooks/http_spec.rb +1 -3
  85. data/spec/lib/appsignal/hooks/net_http_spec.rb +1 -1
  86. data/spec/lib/appsignal/hooks/redis_client_spec.rb +5 -8
  87. data/spec/lib/appsignal/hooks/redis_spec.rb +3 -6
  88. data/spec/lib/appsignal/hooks/resque_spec.rb +1 -1
  89. data/spec/lib/appsignal/hooks/sequel_spec.rb +3 -5
  90. data/spec/lib/appsignal/hooks/shoryuken_spec.rb +0 -171
  91. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +1 -1
  92. data/spec/lib/appsignal/hooks/webmachine_spec.rb +1 -1
  93. data/spec/lib/appsignal/integrations/delayed_job_plugin_spec.rb +459 -0
  94. data/spec/lib/appsignal/integrations/grape_spec.rb +36 -0
  95. data/spec/lib/appsignal/integrations/hanami_spec.rb +9 -178
  96. data/spec/lib/appsignal/integrations/http_spec.rb +1 -5
  97. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +4 -2
  98. data/spec/lib/appsignal/integrations/net_http_spec.rb +1 -1
  99. data/spec/lib/appsignal/integrations/object_spec.rb +1 -3
  100. data/spec/lib/appsignal/integrations/padrino_spec.rb +8 -330
  101. data/spec/lib/appsignal/integrations/que_spec.rb +3 -4
  102. data/spec/lib/appsignal/integrations/railtie_spec.rb +275 -191
  103. data/spec/lib/appsignal/integrations/shoryuken_spec.rb +167 -0
  104. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +15 -13
  105. data/spec/lib/appsignal/integrations/sinatra_spec.rb +9 -104
  106. data/spec/lib/appsignal/integrations/webmachine_spec.rb +13 -1
  107. data/spec/lib/appsignal/loaders/grape_spec.rb +12 -0
  108. data/spec/lib/appsignal/loaders/hanami_spec.rb +95 -0
  109. data/spec/lib/appsignal/loaders/padrino_spec.rb +277 -0
  110. data/spec/lib/appsignal/loaders/sinatra_spec.rb +47 -0
  111. data/spec/lib/appsignal/loaders_spec.rb +137 -0
  112. data/spec/lib/appsignal/probes/sidekiq_spec.rb +1 -1
  113. data/spec/lib/appsignal/probes_spec.rb +6 -5
  114. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +51 -5
  115. data/spec/lib/appsignal/rack/event_handler_spec.rb +114 -10
  116. data/spec/lib/appsignal/rack/generic_instrumentation_spec.rb +1 -1
  117. data/spec/lib/appsignal/rack/grape_middleware_spec.rb +2 -35
  118. data/spec/lib/appsignal/rack/hanami_middleware_spec.rb +1 -1
  119. data/spec/lib/appsignal/rack/rails_instrumentation_spec.rb +4 -2
  120. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +3 -3
  121. data/spec/lib/appsignal/rack_spec.rb +63 -0
  122. data/spec/lib/appsignal/span_spec.rb +1 -3
  123. data/spec/lib/appsignal/transaction_spec.rb +1640 -1075
  124. data/spec/lib/appsignal/utils/integration_logger_spec.rb +12 -16
  125. data/spec/lib/appsignal/utils/integration_memory_logger_spec.rb +0 -10
  126. data/spec/lib/appsignal_spec.rb +601 -36
  127. data/spec/lib/puma/appsignal_spec.rb +0 -3
  128. data/spec/spec_helper.rb +5 -4
  129. data/spec/support/helpers/config_helpers.rb +2 -1
  130. data/spec/support/helpers/loader_helper.rb +21 -0
  131. data/spec/support/helpers/transaction_helpers.rb +44 -20
  132. data/spec/support/matchers/transaction.rb +15 -1
  133. data/spec/support/stubs/appsignal/loaders/loader_stub.rb +7 -0
  134. data/spec/support/testing.rb +47 -1
  135. metadata +19 -2
@@ -1,174 +1,3 @@
1
- describe Appsignal::Hooks::ShoryukenMiddleware do
2
- class DemoShoryukenWorker
3
- end
4
-
5
- let(:time) { "2010-01-01 10:01:00UTC" }
6
- let(:worker_instance) { DemoShoryukenWorker.new }
7
- let(:queue) { "some-funky-queue-name" }
8
- let(:sqs_msg) { double(:message_id => "msg1", :attributes => {}) }
9
- let(:body) { {} }
10
- before(:context) { start_agent }
11
- around { |example| keep_transactions { example.run } }
12
-
13
- def perform_shoryuken_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
24
- end
25
-
26
- context "with a performance call" do
27
- let(:sent_timestamp) { Time.parse("1976-11-18 0:00:00UTC").to_i * 1000 }
28
- let(:sqs_msg) do
29
- double(:message_id => "msg1", :attributes => { "SentTimestamp" => sent_timestamp })
30
- end
31
-
32
- context "with complex argument" do
33
- let(:body) { { :foo => "Foo", :bar => "Bar" } }
34
-
35
- it "wraps the job in a transaction with the correct params" do
36
- allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original
37
- expect { perform_shoryuken_job }.to change { created_transactions.length }.by(1)
38
-
39
- transaction = last_transaction
40
- expect(transaction).to have_id
41
- expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
42
- expect(transaction).to have_action("DemoShoryukenWorker#perform")
43
- expect(transaction).to_not have_error
44
- expect(transaction).to include_event(
45
- "body" => "",
46
- "body_format" => Appsignal::EventFormatter::DEFAULT,
47
- "count" => 1,
48
- "name" => "perform_job.shoryuken",
49
- "title" => ""
50
- )
51
- expect(transaction).to include_params("foo" => "Foo", "bar" => "Bar")
52
- expect(transaction).to include_sample_metadata(
53
- "message_id" => "msg1",
54
- "queue" => queue,
55
- "SentTimestamp" => sent_timestamp
56
- )
57
- expect(transaction).to have_queue_start(sent_timestamp)
58
- expect(transaction).to be_completed
59
- end
60
-
61
- context "with parameter filtering" do
62
- before do
63
- Appsignal.config = project_fixture_config("production")
64
- Appsignal.config[:filter_parameters] = ["foo"]
65
- end
66
- after do
67
- Appsignal.config[:filter_parameters] = []
68
- end
69
-
70
- it "filters selected arguments" do
71
- perform_shoryuken_job
72
-
73
- expect(last_transaction).to include_params("foo" => "[FILTERED]", "bar" => "Bar")
74
- end
75
- end
76
- end
77
-
78
- context "with a string as an argument" do
79
- let(:body) { "foo bar" }
80
-
81
- it "handles string arguments" do
82
- perform_shoryuken_job
83
-
84
- expect(last_transaction).to include_params("params" => body)
85
- end
86
- end
87
-
88
- context "with primitive type as argument" do
89
- let(:body) { 1 }
90
-
91
- it "handles primitive types as arguments" do
92
- perform_shoryuken_job
93
-
94
- expect(last_transaction).to include_params("params" => body)
95
- end
96
- end
97
- end
98
-
99
- context "with exception" do
100
- it "sets the exception on the transaction" do
101
- expect do
102
- expect do
103
- perform_shoryuken_job { raise ExampleException, "error message" }
104
- end.to raise_error(ExampleException)
105
- end.to change { created_transactions.length }.by(1)
106
-
107
- transaction = last_transaction
108
- expect(transaction).to have_id
109
- expect(transaction).to have_action("DemoShoryukenWorker#perform")
110
- expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
111
- expect(transaction).to have_error("ExampleException", "error message")
112
- expect(transaction).to be_completed
113
- end
114
- end
115
-
116
- context "with batched jobs" do
117
- let(:sqs_msg) do
118
- [
119
- double(
120
- :message_id => "msg2",
121
- :attributes => {
122
- "SentTimestamp" => (Time.parse("1976-11-18 01:00:00UTC").to_i * 1000).to_s
123
- }
124
- ),
125
- double(
126
- :message_id => "msg1",
127
- :attributes => { "SentTimestamp" => sent_timestamp.to_s }
128
- )
129
- ]
130
- end
131
- let(:body) do
132
- [
133
- "foo bar",
134
- { :id => "123", :foo => "Foo", :bar => "Bar" }
135
- ]
136
- end
137
- let(:sent_timestamp) { Time.parse("1976-11-18 01:00:00UTC").to_i * 1000 }
138
-
139
- it "creates a transaction for the batch" do
140
- allow_any_instance_of(Appsignal::Transaction).to receive(:set_queue_start).and_call_original
141
- expect do
142
- perform_shoryuken_job {} # rubocop:disable Lint/EmptyBlock
143
- end.to change { created_transactions.length }.by(1)
144
-
145
- transaction = last_transaction
146
- expect(transaction).to have_id
147
- expect(transaction).to have_action("DemoShoryukenWorker#perform")
148
- expect(transaction).to have_namespace(Appsignal::Transaction::BACKGROUND_JOB)
149
- expect(transaction).to_not have_error
150
- expect(transaction).to include_event(
151
- "body" => "",
152
- "body_format" => Appsignal::EventFormatter::DEFAULT,
153
- "count" => 1,
154
- "name" => "perform_job.shoryuken",
155
- "title" => ""
156
- )
157
- expect(transaction).to include_params(
158
- "msg2" => "foo bar",
159
- "msg1" => { "id" => "123", "foo" => "Foo", "bar" => "Bar" }
160
- )
161
- expect(transaction).to include_sample_metadata(
162
- "batch" => true,
163
- "queue" => "some-funky-queue-name",
164
- "SentTimestamp" => sent_timestamp.to_s # Earliest/oldest timestamp from messages
165
- )
166
- # Queue time based on earliest/oldest timestamp from messages
167
- expect(transaction).to have_queue_start(sent_timestamp)
168
- end
169
- end
170
- end
171
-
172
1
  describe Appsignal::Hooks::ShoryukenHook do
173
2
  context "with shoryuken" do
174
3
  before(:context) do
@@ -65,7 +65,7 @@ describe Appsignal::Hooks::SidekiqHook do
65
65
  end
66
66
 
67
67
  before do
68
- Appsignal.config = project_fixture_config
68
+ start_agent
69
69
  stub_const "Sidekiq", SidekiqMock
70
70
  end
71
71
 
@@ -2,7 +2,7 @@ describe Appsignal::Hooks::WebmachineHook do
2
2
  if DependencyHelper.webmachine_present?
3
3
  context "with webmachine" do
4
4
  let(:fsm) { Webmachine::Decision::FSM.new(double(:trace? => false), double, double) }
5
- before(:context) { start_agent }
5
+ before { start_agent }
6
6
 
7
7
  describe "#dependencies_present?" do
8
8
  subject { described_class.new.dependencies_present? }
@@ -0,0 +1,459 @@
1
+ describe "Appsignal::Integrations::DelayedJobHook" do
2
+ before(:context) do
3
+ module Delayed
4
+ class Plugin
5
+ def self.callbacks
6
+ end
7
+ end
8
+
9
+ class Worker
10
+ def self.plugins
11
+ @plugins ||= []
12
+ end
13
+ end
14
+ end
15
+ require "appsignal/integrations/delayed_job_plugin"
16
+ end
17
+ after(:context) { Object.send(:remove_const, :Delayed) }
18
+ before { start_agent }
19
+
20
+ # We haven't found a way to test the hooks, we'll have to do that manually
21
+
22
+ describe ".invoke_with_instrumentation" do
23
+ let(:plugin) { Appsignal::Integrations::DelayedJobPlugin }
24
+ let(:time) { Time.parse("01-01-2001 10:01:00UTC") }
25
+ let(:created_at) { time - 3600 }
26
+ let(:run_at) { time - 3600 }
27
+ let(:payload_object) { double(:args => args) }
28
+ let(:job_data) do
29
+ {
30
+ :id => 123,
31
+ :name => "TestClass#perform",
32
+ :priority => 1,
33
+ :attempts => 1,
34
+ :queue => "default",
35
+ :created_at => created_at,
36
+ :run_at => run_at,
37
+ :payload_object => payload_object
38
+ }
39
+ end
40
+ let(:args) { ["argument"] }
41
+ let(:job) { double(job_data) }
42
+ let(:invoked_block) { proc {} }
43
+
44
+ def perform
45
+ Timecop.freeze(time) do
46
+ keep_transactions do
47
+ plugin.invoke_with_instrumentation(job, invoked_block)
48
+ end
49
+ end
50
+ end
51
+
52
+ context "with a normal call" do
53
+ it "wraps it in a transaction" do
54
+ perform
55
+
56
+ transaction = last_transaction
57
+ expect(transaction).to have_namespace("background_job")
58
+ expect(transaction).to have_action("TestClass#perform")
59
+ expect(transaction).to_not have_error
60
+ expect(transaction).to include_event(:name => "perform_job.delayed_job")
61
+ expect(transaction).to include_tags(
62
+ "priority" => 1,
63
+ "attempts" => 1,
64
+ "queue" => "default",
65
+ "id" => "123"
66
+ )
67
+ expect(transaction).to include_params(["argument"])
68
+ end
69
+
70
+ context "with more complex params" do
71
+ let(:args) do
72
+ {
73
+ :foo => "Foo",
74
+ :bar => "Bar"
75
+ }
76
+ end
77
+
78
+ it "adds the more complex arguments" do
79
+ perform
80
+
81
+ expect(last_transaction).to include_params("foo" => "Foo", "bar" => "Bar")
82
+ end
83
+
84
+ context "with parameter filtering" do
85
+ before do
86
+ start_agent("production")
87
+ Appsignal.config[:filter_parameters] = ["foo"]
88
+ end
89
+
90
+ it "filters selected arguments" do
91
+ perform
92
+
93
+ expect(last_transaction).to include_params("foo" => "[FILTERED]", "bar" => "Bar")
94
+ end
95
+ end
96
+ end
97
+
98
+ context "with run_at in the future" do
99
+ let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") }
100
+
101
+ it "reports queue_start with run_at time" do
102
+ perform
103
+
104
+ expect(last_transaction).to have_queue_start(run_at.to_i * 1000)
105
+ end
106
+ end
107
+
108
+ context "with class method job" do
109
+ let(:job_data) do
110
+ { :name => "CustomClassMethod.perform", :payload_object => payload_object }
111
+ end
112
+
113
+ it "wraps it in a transaction using the class method job name" do
114
+ perform
115
+ expect(last_transaction).to have_action("CustomClassMethod.perform")
116
+ end
117
+ end
118
+
119
+ context "with custom name call" do
120
+ before { perform }
121
+
122
+ context "with appsignal_name defined" do
123
+ context "with payload_object being an object" do
124
+ context "with value" do
125
+ let(:payload_object) { double(:appsignal_name => "CustomClass#perform") }
126
+
127
+ it "wraps it in a transaction using the custom name" do
128
+ expect(last_transaction).to have_action("CustomClass#perform")
129
+ end
130
+ end
131
+
132
+ context "with non-String value" do
133
+ let(:payload_object) { double(:appsignal_name => Object.new) }
134
+
135
+ it "wraps it in a transaction using the original job name" do
136
+ expect(last_transaction).to have_action("TestClass#perform")
137
+ end
138
+ end
139
+
140
+ context "with class method name as job" do
141
+ let(:payload_object) { double(:appsignal_name => "CustomClassMethod.perform") }
142
+
143
+ it "wraps it in a transaction using the custom name" do
144
+ perform
145
+ expect(last_transaction).to have_action("CustomClassMethod.perform")
146
+ end
147
+ end
148
+ end
149
+
150
+ context "with payload_object being a Hash" do
151
+ context "with value" do
152
+ let(:payload_object) { double(:appsignal_name => "CustomClassHash#perform") }
153
+
154
+ it "wraps it in a transaction using the custom name" do
155
+ expect(last_transaction).to have_action("CustomClassHash#perform")
156
+ end
157
+ end
158
+
159
+ context "with non-String value" do
160
+ let(:payload_object) { double(:appsignal_name => Object.new) }
161
+
162
+ it "wraps it in a transaction using the original job name" do
163
+ expect(last_transaction).to have_action("TestClass#perform")
164
+ end
165
+ end
166
+
167
+ context "with class method name as job" do
168
+ let(:payload_object) { { :appsignal_name => "CustomClassMethod.perform" } }
169
+
170
+ it "wraps it in a transaction using the custom name" do
171
+ perform
172
+ expect(last_transaction).to have_action("CustomClassMethod.perform")
173
+ end
174
+ end
175
+ end
176
+
177
+ context "with payload_object acting like a Hash and returning a non-String value" do
178
+ class ClassActingAsHash
179
+ def self.[](_key)
180
+ Object.new
181
+ end
182
+
183
+ def self.appsignal_name
184
+ "ClassActingAsHash#perform"
185
+ end
186
+ end
187
+ let(:payload_object) { ClassActingAsHash }
188
+
189
+ # We check for hash values before object values
190
+ # this means ClassActingAsHash returns `Object.new` instead
191
+ # of `self.appsignal_name`. Since this isn't a valid `String`
192
+ # we return the default job name as action name.
193
+ it "wraps it in a transaction using the original job name" do
194
+ expect(last_transaction).to have_action("TestClass#perform")
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ context "with only job class name" do
201
+ let(:job_data) do
202
+ { :name => "Banana", :payload_object => payload_object }
203
+ end
204
+
205
+ it "appends #perform to the class name" do
206
+ perform
207
+ expect(last_transaction).to have_action("Banana#perform")
208
+ end
209
+ end
210
+
211
+ if active_job_present?
212
+ require "active_job"
213
+
214
+ context "when wrapped by ActiveJob" do
215
+ let(:payload_object) do
216
+ ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(
217
+ "arguments" => args,
218
+ "job_class" => "TestClass",
219
+ "job_id" => 123,
220
+ "locale" => :en,
221
+ "queue_name" => "default"
222
+ )
223
+ end
224
+ let(:job) do
225
+ double(
226
+ :id => 123,
227
+ :name => "ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper",
228
+ :priority => 1,
229
+ :attempts => 1,
230
+ :queue => "default",
231
+ :created_at => created_at,
232
+ :run_at => run_at,
233
+ :payload_object => payload_object
234
+ )
235
+ end
236
+ let(:args) { ["activejob_argument"] }
237
+
238
+ it "wraps it in a transaction with the correct params" do
239
+ perform
240
+
241
+ transaction = last_transaction
242
+ expect(transaction).to have_namespace("background_job")
243
+ expect(transaction).to have_action("TestClass#perform")
244
+ expect(transaction).to_not have_error
245
+ expect(transaction).to include_event("name" => "perform_job.delayed_job")
246
+ expect(transaction).to include_tags(
247
+ "priority" => 1,
248
+ "attempts" => 1,
249
+ "queue" => "default",
250
+ "id" => "123"
251
+ )
252
+ expect(transaction).to include_params(["activejob_argument"])
253
+ end
254
+
255
+ context "with more complex params" do
256
+ let(:args) do
257
+ {
258
+ :foo => "Foo",
259
+ :bar => "Bar"
260
+ }
261
+ end
262
+
263
+ it "adds the more complex arguments" do
264
+ perform
265
+ transaction = last_transaction
266
+ expect(transaction).to have_action("TestClass#perform")
267
+ expect(transaction).to include_params(
268
+ "foo" => "Foo",
269
+ "bar" => "Bar"
270
+ )
271
+ end
272
+
273
+ context "with parameter filtering" do
274
+ before do
275
+ start_agent("production")
276
+ Appsignal.config[:filter_parameters] = ["foo"]
277
+ end
278
+
279
+ it "filters selected arguments" do
280
+ perform
281
+ transaction = last_transaction
282
+ expect(transaction).to have_action("TestClass#perform")
283
+ expect(transaction).to include_params(
284
+ "foo" => "[FILTERED]",
285
+ "bar" => "Bar"
286
+ )
287
+ end
288
+ end
289
+ end
290
+
291
+ context "with run_at in the future" do
292
+ let(:run_at) { Time.parse("2017-01-01 10:01:00UTC") }
293
+
294
+ it "reports queue_start with run_at time" do
295
+ perform
296
+
297
+ expect(last_transaction).to have_queue_start(run_at.to_i * 1000)
298
+ end
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ context "with an erroring call" do
305
+ let(:error) { ExampleException.new("uh oh") }
306
+ before do
307
+ expect(invoked_block).to receive(:call).and_raise(error)
308
+ end
309
+
310
+ it "adds the error to the transaction" do
311
+ expect do
312
+ perform
313
+ end.to raise_error(error)
314
+
315
+ transaction = last_transaction
316
+ expect(transaction).to have_namespace("background_job")
317
+ expect(transaction).to have_action("TestClass#perform")
318
+ expect(transaction).to have_error("ExampleException", "uh oh")
319
+ end
320
+ end
321
+ end
322
+
323
+ describe ".extract_value" do
324
+ let(:plugin) { Appsignal::Integrations::DelayedJobPlugin }
325
+
326
+ context "for a hash" do
327
+ let(:hash) { { :key => "value", :bool_false => false } }
328
+
329
+ context "when the key exists" do
330
+ subject { plugin.extract_value(hash, :key) }
331
+
332
+ it { is_expected.to eq "value" }
333
+
334
+ context "when the value is false" do
335
+ subject { plugin.extract_value(hash, :bool_false) }
336
+
337
+ it { is_expected.to be false }
338
+ end
339
+ end
340
+
341
+ context "when the key does not exist" do
342
+ subject { plugin.extract_value(hash, :nonexistent_key) }
343
+
344
+ it { is_expected.to be_nil }
345
+
346
+ context "with a default value" do
347
+ subject { plugin.extract_value(hash, :nonexistent_key, 1) }
348
+
349
+ it { is_expected.to eq 1 }
350
+ end
351
+ end
352
+ end
353
+
354
+ context "for a struct" do
355
+ before :context do
356
+ TestStruct = Struct.new(:key)
357
+ end
358
+ let(:struct) { TestStruct.new("value") }
359
+
360
+ context "when the key exists" do
361
+ subject { plugin.extract_value(struct, :key) }
362
+
363
+ it { is_expected.to eq "value" }
364
+ end
365
+
366
+ context "when the key does not exist" do
367
+ subject { plugin.extract_value(struct, :nonexistent_key) }
368
+
369
+ it { is_expected.to be_nil }
370
+
371
+ context "with a default value" do
372
+ subject { plugin.extract_value(struct, :nonexistent_key, 1) }
373
+
374
+ it { is_expected.to eq 1 }
375
+ end
376
+ end
377
+ end
378
+
379
+ context "for a struct with a method" do
380
+ before :context do
381
+ class TestStructClass < Struct.new(:id) # rubocop:disable Style/StructInheritance
382
+ def appsignal_name
383
+ "TestStruct#perform"
384
+ end
385
+
386
+ def bool_false
387
+ false
388
+ end
389
+ end
390
+ end
391
+ let(:struct) { TestStructClass.new("id") }
392
+
393
+ context "when the Struct responds to a method" do
394
+ subject { plugin.extract_value(struct, :appsignal_name) }
395
+
396
+ it "returns the method value" do
397
+ is_expected.to eq "TestStruct#perform"
398
+ end
399
+
400
+ context "when the value is false" do
401
+ subject { plugin.extract_value(struct, :bool_false) }
402
+
403
+ it "returns the method value" do
404
+ is_expected.to be false
405
+ end
406
+ end
407
+ end
408
+
409
+ context "when the key does not exist" do
410
+ subject { plugin.extract_value(struct, :nonexistent_key) }
411
+
412
+ context "without a method with the same name" do
413
+ it "returns nil" do
414
+ is_expected.to be_nil
415
+ end
416
+ end
417
+
418
+ context "with a default value" do
419
+ let(:default_value) { :my_default_value }
420
+ subject { plugin.extract_value(struct, :nonexistent_key, default_value) }
421
+
422
+ it "returns the default value" do
423
+ is_expected.to eq default_value
424
+ end
425
+ end
426
+ end
427
+ end
428
+
429
+ context "for an object" do
430
+ let(:object) { double(:existing_method => "value") }
431
+
432
+ context "when the method exists" do
433
+ subject { plugin.extract_value(object, :existing_method) }
434
+
435
+ it { is_expected.to eq "value" }
436
+ end
437
+
438
+ context "when the method does not exist" do
439
+ subject { plugin.extract_value(object, :nonexistent_method) }
440
+
441
+ it { is_expected.to be_nil }
442
+
443
+ context "and there is a default value" do
444
+ subject { plugin.extract_value(object, :nonexistent_method, 1) }
445
+
446
+ it { is_expected.to eq 1 }
447
+ end
448
+ end
449
+ end
450
+
451
+ context "when we need to call to_s on the value" do
452
+ let(:object) { double(:existing_method => 1) }
453
+
454
+ subject { plugin.extract_value(object, :existing_method, nil, true) }
455
+
456
+ it { is_expected.to eq "1" }
457
+ end
458
+ end
459
+ end
@@ -0,0 +1,36 @@
1
+ if DependencyHelper.grape_present?
2
+ require "appsignal/integrations/grape"
3
+
4
+ context "Appsignal::Grape::Middleware constant" do
5
+ let(:err_stream) { std_stream }
6
+ let(:stderr) { err_stream.read }
7
+
8
+ it "returns the Rack::GrapeMiddleware constant calling the Grape::Middleware constant" do
9
+ silence { expect(Appsignal::Grape::Middleware).to be(Appsignal::Rack::GrapeMiddleware) }
10
+ end
11
+
12
+ it "prints a deprecation warning to STDERR" do
13
+ capture_std_streams(std_stream, err_stream) do
14
+ expect(Appsignal::Grape::Middleware).to be(Appsignal::Rack::GrapeMiddleware)
15
+ end
16
+
17
+ expect(stderr).to include(
18
+ "appsignal WARNING: The constant Appsignal::Grape::Middleware has been deprecated."
19
+ )
20
+ end
21
+
22
+ it "logs a warning" do
23
+ logs =
24
+ capture_logs do
25
+ silence do
26
+ expect(Appsignal::Grape::Middleware).to be(Appsignal::Rack::GrapeMiddleware)
27
+ end
28
+ end
29
+
30
+ expect(logs).to contains_log(
31
+ :warn,
32
+ "The constant Appsignal::Grape::Middleware has been deprecated."
33
+ )
34
+ end
35
+ end
36
+ end