appsignal 4.0.4 → 4.0.6

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +151 -16
  3. data/CHANGELOG.md +42 -0
  4. data/build_matrix.yml +2 -1
  5. data/ext/agent.rb +27 -27
  6. data/gemfiles/que-1.gemfile +5 -0
  7. data/gemfiles/que-2.gemfile +5 -0
  8. data/lib/appsignal/check_in/scheduler.rb +3 -4
  9. data/lib/appsignal/config.rb +7 -0
  10. data/lib/appsignal/hooks/at_exit.rb +1 -0
  11. data/lib/appsignal/hooks/puma.rb +5 -1
  12. data/lib/appsignal/integrations/puma.rb +45 -0
  13. data/lib/appsignal/integrations/que.rb +8 -2
  14. data/lib/appsignal/rack/abstract_middleware.rb +3 -47
  15. data/lib/appsignal/rack/event_handler.rb +2 -0
  16. data/lib/appsignal/rack/hanami_middleware.rb +5 -1
  17. data/lib/appsignal/rack.rb +68 -0
  18. data/lib/appsignal/version.rb +1 -1
  19. data/spec/lib/appsignal/check_in/cron_spec.rb +134 -134
  20. data/spec/lib/appsignal/check_in/scheduler_spec.rb +297 -224
  21. data/spec/lib/appsignal/config_spec.rb +13 -0
  22. data/spec/lib/appsignal/hooks/at_exit_spec.rb +11 -0
  23. data/spec/lib/appsignal/hooks/puma_spec.rb +31 -23
  24. data/spec/lib/appsignal/integrations/puma_spec.rb +150 -0
  25. data/spec/lib/appsignal/integrations/que_spec.rb +56 -21
  26. data/spec/lib/appsignal/probes_spec.rb +4 -6
  27. data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +41 -122
  28. data/spec/lib/appsignal/rack_spec.rb +180 -0
  29. data/spec/lib/appsignal/transaction_spec.rb +96 -92
  30. data/spec/spec_helper.rb +6 -7
  31. data/spec/support/helpers/api_request_helper.rb +40 -0
  32. data/spec/support/helpers/config_helpers.rb +2 -1
  33. data/spec/support/helpers/dependency_helper.rb +5 -0
  34. data/spec/support/matchers/contains_log.rb +10 -3
  35. data/spec/support/mocks/hash_like.rb +10 -0
  36. data/spec/support/mocks/puma_mock.rb +43 -0
  37. data/spec/support/testing.rb +9 -0
  38. metadata +8 -3
  39. data/gemfiles/que.gemfile +0 -5
@@ -19,7 +19,6 @@ module Appsignal
19
19
  @app = app
20
20
  @options = options
21
21
  @request_class = options.fetch(:request_class, ::Rack::Request)
22
- @params_method = options.fetch(:params_method, :params)
23
22
  @instrument_event_name = options.fetch(:instrument_event_name, nil)
24
23
  @report_errors = options.fetch(:report_errors, DEFAULT_ERROR_REPORTING)
25
24
  end
@@ -136,52 +135,9 @@ module Appsignal
136
135
  # Override this method to set metadata after the app is called.
137
136
  # Call `super` to also include the default set metadata.
138
137
  def add_transaction_metadata_after(transaction, request)
139
- transaction.set_metadata("path", request.path)
140
-
141
- request_method = request_method_for(request)
142
- transaction.set_metadata("method", request_method) if request_method
143
-
144
- transaction.add_params { params_for(request) }
145
- transaction.add_session_data { session_data_for(request) }
146
- transaction.add_headers do
147
- request.env if request.respond_to?(:env)
148
- end
149
-
150
- queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
151
- transaction.set_queue_start(queue_start) if queue_start
152
- end
153
-
154
- def params_for(request)
155
- return unless request.respond_to?(@params_method)
156
-
157
- request.send(@params_method)
158
- rescue => error
159
- Appsignal.internal_logger.error(
160
- "Exception while fetching params from '#{@request_class}##{@params_method}': " \
161
- "#{error.class} #{error}"
162
- )
163
- nil
164
- end
165
-
166
- def request_method_for(request)
167
- request.request_method
168
- rescue => error
169
- Appsignal.internal_logger.error(
170
- "Exception while fetching the HTTP request method: #{error.class}: #{error}"
171
- )
172
- nil
173
- end
174
-
175
- def session_data_for(request)
176
- return unless request.respond_to?(:session)
177
-
178
- request.session.to_h
179
- rescue => error
180
- Appsignal.internal_logger.error(
181
- "Exception while fetching session data from '#{@request_class}': " \
182
- "#{error.class} #{error}"
183
- )
184
- nil
138
+ Appsignal::Rack::ApplyRackRequest
139
+ .new(request, @options)
140
+ .apply_to(transaction)
185
141
  end
186
142
 
187
143
  def request_for(env)
@@ -79,6 +79,8 @@ module Appsignal
79
79
  #
80
80
  # The EventHandler.on_finish callback should be called first, this is
81
81
  # just a fallback if that doesn't get called.
82
+ #
83
+ # One such scenario is when a Puma "lowlevel_error" occurs.
82
84
  Appsignal::Transaction.complete_current!
83
85
  end
84
86
  transaction.start_event
@@ -5,13 +5,17 @@ module Appsignal
5
5
  # @api private
6
6
  class HanamiMiddleware < AbstractMiddleware
7
7
  def initialize(app, options = {})
8
- options[:params_method] ||= :params
8
+ options[:params_method] = nil
9
9
  options[:instrument_event_name] ||= "process_action.hanami"
10
10
  super
11
11
  end
12
12
 
13
13
  private
14
14
 
15
+ def add_transaction_metadata_after(transaction, request)
16
+ transaction.add_params { params_for(request) }
17
+ end
18
+
15
19
  def params_for(request)
16
20
  ::Hanami::Action.params_class.new(request.env).to_h
17
21
  end
@@ -37,5 +37,73 @@ module Appsignal
37
37
  end
38
38
  end
39
39
  end
40
+
41
+ class ApplyRackRequest
42
+ attr_reader :request, :options
43
+
44
+ def initialize(request, options = {})
45
+ @request = request
46
+ @options = options
47
+ @params_method = options.fetch(:params_method, :params)
48
+ end
49
+
50
+ def apply_to(transaction)
51
+ request_path = request.path
52
+ transaction.set_metadata("request_path", request_path)
53
+ # TODO: Remove in next major/minor version
54
+ transaction.set_metadata("path", request_path)
55
+
56
+ request_method = request_method_for(request)
57
+ if request_method
58
+ transaction.set_metadata("request_method", request_method)
59
+ # TODO: Remove in next major/minor version
60
+ transaction.set_metadata("method", request_method)
61
+ end
62
+
63
+ transaction.add_params { params_for(request) }
64
+ transaction.add_session_data { session_data_for(request) }
65
+ transaction.add_headers do
66
+ request.env if request.respond_to?(:env)
67
+ end
68
+
69
+ queue_start = Appsignal::Rack::Utils.queue_start_from(request.env)
70
+ transaction.set_queue_start(queue_start) if queue_start
71
+ end
72
+
73
+ private
74
+
75
+ def params_for(request)
76
+ return if !@params_method || !request.respond_to?(@params_method)
77
+
78
+ request.send(@params_method)
79
+ rescue => error
80
+ Appsignal.internal_logger.error(
81
+ "Exception while fetching params from '#{request.class}##{@params_method}': " \
82
+ "#{error.class} #{error}"
83
+ )
84
+ nil
85
+ end
86
+
87
+ def request_method_for(request)
88
+ request.request_method
89
+ rescue => error
90
+ Appsignal.internal_logger.error(
91
+ "Exception while fetching the HTTP request method: #{error.class}: #{error}"
92
+ )
93
+ nil
94
+ end
95
+
96
+ def session_data_for(request)
97
+ return unless request.respond_to?(:session)
98
+
99
+ request.session.to_h
100
+ rescue => error
101
+ Appsignal.internal_logger.error(
102
+ "Exception while fetching session data from '#{request.class}': " \
103
+ "#{error.class} #{error}"
104
+ )
105
+ nil
106
+ end
107
+ end
40
108
  end
41
109
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "4.0.4"
4
+ VERSION = "4.0.6"
5
5
  end
@@ -1,193 +1,191 @@
1
1
  describe Appsignal::CheckIn::Cron do
2
+ let(:log_stream) { std_stream }
3
+ let(:logs) { log_contents(log_stream) }
4
+ let(:appsignal_options) { {} }
2
5
  let(:config) { project_fixture_config }
3
6
  let(:cron_checkin) { described_class.new(:identifier => "cron-checkin-name") }
4
- let(:transmitter) { Appsignal::Transmitter.new("https://checkin-endpoint.invalid") }
5
- let(:scheduler) { Appsignal::CheckIn::Scheduler.new }
7
+ let(:scheduler) { Appsignal::CheckIn.scheduler }
6
8
 
7
9
  before do
8
- allow(Appsignal).to receive(:active?).and_return(true)
9
- config.logger = Logger.new(StringIO.new)
10
- allow(Appsignal::CheckIn).to receive(:scheduler).and_return(scheduler)
11
- allow(Appsignal::CheckIn).to receive(:transmitter).and_return(transmitter)
10
+ start_agent(
11
+ :options => appsignal_options,
12
+ :internal_logger => test_logger(log_stream)
13
+ )
12
14
  end
15
+ after { stop_scheduler }
13
16
 
14
- after do
17
+ def stop_scheduler
15
18
  scheduler.stop
16
19
  end
17
20
 
18
21
  describe "when Appsignal is not active" do
19
- it "should not transmit any events" do
20
- allow(Appsignal).to receive(:active?).and_return(false)
22
+ let(:appsignal_options) { { :active => false } }
21
23
 
22
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
23
- message.include?("Cannot transmit cron check-in `cron-checkin-name` start event") &&
24
- message.include?("AppSignal is not active")
25
- end)
24
+ it "does not transmit any events" do
25
+ expect(Appsignal).to_not be_started
26
26
 
27
27
  cron_checkin.start
28
-
29
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
30
- message.include?("Cannot transmit cron check-in `cron-checkin-name` finish event") &&
31
- message.include?("AppSignal is not active")
32
- end)
33
-
34
28
  cron_checkin.finish
35
-
36
- expect(transmitter).not_to receive(:transmit)
37
-
38
- scheduler.stop
29
+ stop_scheduler
30
+
31
+ expect(logs).to contains_log(
32
+ :debug,
33
+ /Cannot transmit cron check-in `cron-checkin-name` start event .+: AppSignal is not active/
34
+ )
35
+ expect(logs).to contains_log(
36
+ :debug,
37
+ /Cannot transmit cron check-in `cron-checkin-name` finish event .+: AppSignal is not active/
38
+ )
39
39
  end
40
40
  end
41
41
 
42
42
  describe "when AppSignal is stopped" do
43
- it "should not transmit any events" do
44
- expect(transmitter).not_to receive(:transmit)
45
-
46
- expect(Appsignal.internal_logger).to receive(:debug).with("Stopping AppSignal")
47
-
43
+ it "does not transmit any events" do
48
44
  Appsignal.stop
49
45
 
50
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
51
- message.include?("Cannot transmit cron check-in `cron-checkin-name` start event") &&
52
- message.include?("AppSignal is stopped")
53
- end)
54
-
55
46
  cron_checkin.start
56
-
57
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
58
- message.include?("Cannot transmit cron check-in `cron-checkin-name` finish event") &&
59
- message.include?("AppSignal is stopped")
60
- end)
61
-
62
47
  cron_checkin.finish
63
48
 
64
- expect(Appsignal.internal_logger).to receive(:debug).with("Stopping AppSignal")
65
-
66
- Appsignal.stop
49
+ expect(logs).to contains_log(
50
+ :debug,
51
+ "Cannot transmit cron check-in `cron-checkin-name` start event"
52
+ )
53
+ expect(logs).to contains_log(
54
+ :debug,
55
+ "Cannot transmit cron check-in `cron-checkin-name` finish event"
56
+ )
67
57
  end
68
58
  end
69
59
 
70
60
  describe "#start" do
71
- it "should send a cron check-in start" do
72
- expect(Appsignal.internal_logger).not_to receive(:error)
73
-
74
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
75
- message.include?("Scheduling cron check-in `cron-checkin-name` start event")
76
- end)
77
-
61
+ it "sends a cron check-in start" do
78
62
  cron_checkin.start
79
63
 
80
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
81
- message.include?("Transmitted cron check-in `cron-checkin-name` start event")
82
- end)
83
-
84
- expect(transmitter).to receive(:transmit).with([hash_including(
85
- :identifier => "cron-checkin-name",
86
- :kind => "start",
87
- :check_in_type => "cron"
88
- )], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "200", nil))
64
+ stub_check_in_request(
65
+ :events => [
66
+ "identifier" => "cron-checkin-name",
67
+ "kind" => "start",
68
+ "check_in_type" => "cron"
69
+ ]
70
+ )
89
71
 
90
72
  scheduler.stop
91
- end
92
73
 
93
- it "should log an error if it fails" do
94
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
95
- message.include?("Scheduling cron check-in `cron-checkin-name` start event")
96
- end)
74
+ expect(logs).to_not contains_log(:error)
75
+ expect(logs).to contains_log(
76
+ :debug,
77
+ "Scheduling cron check-in `cron-checkin-name` start event"
78
+ )
79
+ expect(logs).to contains_log(
80
+ :debug,
81
+ "Transmitted cron check-in `cron-checkin-name` start event"
82
+ )
83
+ end
97
84
 
85
+ it "logs an error if it fails" do
98
86
  cron_checkin.start
99
87
 
100
- expect(Appsignal.internal_logger).to receive(:error).with(satisfy do |message|
101
- message.include?("Failed to transmit cron check-in `cron-checkin-name` start event") &&
102
- message.include?("499 status code")
103
- end)
104
-
105
- expect(transmitter).to receive(:transmit).with([hash_including(
106
- :identifier => "cron-checkin-name",
107
- :kind => "start",
108
- :check_in_type => "cron"
109
- )], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "499", nil))
88
+ stub_check_in_request(
89
+ :events => [
90
+ "identifier" => "cron-checkin-name",
91
+ "kind" => "start",
92
+ "check_in_type" => "cron"
93
+ ],
94
+ :response => { :status => 499 }
95
+ )
110
96
 
111
97
  scheduler.stop
98
+
99
+ expect(logs).to contains_log(
100
+ :debug,
101
+ "Scheduling cron check-in `cron-checkin-name` start event"
102
+ )
103
+ expect(logs).to contains_log(
104
+ :error,
105
+ /Failed to transmit cron check-in `cron-checkin-name` start event .+: 499 status code/
106
+ )
112
107
  end
113
108
  end
114
109
 
115
110
  describe "#finish" do
116
- it "should send a cron check-in finish" do
117
- expect(Appsignal.internal_logger).not_to receive(:error)
118
-
119
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
120
- message.include?("Scheduling cron check-in `cron-checkin-name` finish event")
121
- end)
122
-
111
+ it "sends a cron check-in finish" do
123
112
  cron_checkin.finish
124
113
 
125
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
126
- message.include?("Transmitted cron check-in `cron-checkin-name` finish event")
127
- end)
128
-
129
- expect(transmitter).to receive(:transmit).with([hash_including(
130
- :identifier => "cron-checkin-name",
131
- :kind => "finish",
132
- :check_in_type => "cron"
133
- )], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "200", nil))
114
+ stub_check_in_request(
115
+ :events => [
116
+ "identifier" => "cron-checkin-name",
117
+ "kind" => "finish",
118
+ "check_in_type" => "cron"
119
+ ]
120
+ )
134
121
 
135
122
  scheduler.stop
123
+ expect(logs).to_not contains_log(:error)
124
+ expect(logs).to contains_log(
125
+ :debug,
126
+ "Scheduling cron check-in `cron-checkin-name` finish event"
127
+ )
128
+ expect(logs).to contains_log(
129
+ :debug,
130
+ "Transmitted cron check-in `cron-checkin-name` finish event"
131
+ )
136
132
  end
137
133
 
138
- it "should log an error if it fails" do
139
- expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
140
- message.include?("Scheduling cron check-in `cron-checkin-name` finish event")
141
- end)
142
-
134
+ it "logs an error if it fails" do
143
135
  cron_checkin.finish
144
136
 
145
- expect(Appsignal.internal_logger).to receive(:error).with(satisfy do |message|
146
- message.include?("Failed to transmit cron check-in `cron-checkin-name` finish event") &&
147
- message.include?("499 status code")
148
- end)
149
-
150
- expect(transmitter).to receive(:transmit).with([hash_including(
151
- :identifier => "cron-checkin-name",
152
- :kind => "finish",
153
- :check_in_type => "cron"
154
- )], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "499", nil))
137
+ stub_check_in_request(
138
+ :events => [
139
+ "identifier" => "cron-checkin-name",
140
+ "kind" => "finish",
141
+ "check_in_type" => "cron"
142
+ ],
143
+ :response => { :status => 499 }
144
+ )
155
145
 
156
146
  scheduler.stop
147
+
148
+ expect(logs).to contains_log(
149
+ :debug,
150
+ "Scheduling cron check-in `cron-checkin-name` finish event"
151
+ )
152
+ expect(logs).to contains_log(
153
+ :error,
154
+ /Failed to transmit cron check-in `cron-checkin-name` finish event .+: 499 status code/
155
+ )
157
156
  end
158
157
  end
159
158
 
160
159
  describe ".cron" do
161
160
  describe "when a block is given" do
162
- it "should send a cron check-in start and finish and return the block output" do
163
- expect(scheduler).to receive(:schedule).with(hash_including(
164
- :kind => "start",
165
- :identifier => "cron-checkin-with-block",
166
- :check_in_type => "cron"
167
- ))
168
-
169
- expect(scheduler).to receive(:schedule).with(hash_including(
170
- :kind => "finish",
171
- :identifier => "cron-checkin-with-block",
172
- :check_in_type => "cron"
173
- ))
161
+ it "sends a cron check-in start and finish and return the block output" do
162
+ stub_check_in_request(
163
+ :events => [
164
+ "identifier" => "cron-checkin-with-block",
165
+ "kind" => "start",
166
+ "check_in_type" => "cron"
167
+ ]
168
+ )
169
+ stub_check_in_request(
170
+ :events => [
171
+ "identifier" => "cron-checkin-with-block",
172
+ "kind" => "finish",
173
+ "check_in_type" => "cron"
174
+ ]
175
+ )
174
176
 
175
177
  output = Appsignal::CheckIn.cron("cron-checkin-with-block") { "output" }
176
178
  expect(output).to eq("output")
177
179
  end
178
180
 
179
- it "should not send a cron check-in finish event when an error is raised" do
180
- expect(scheduler).to receive(:schedule).with(hash_including(
181
- :kind => "start",
182
- :identifier => "cron-checkin-with-block",
183
- :check_in_type => "cron"
184
- ))
185
-
186
- expect(scheduler).not_to receive(:schedule).with(hash_including(
187
- :kind => "finish",
188
- :identifier => "cron-checkin-with-block",
189
- :check_in_type => "cron"
190
- ))
181
+ it "does not send a cron check-in finish event when an error is raised" do
182
+ stub_check_in_request(
183
+ :events => [
184
+ "identifier" => "cron-checkin-with-block",
185
+ "kind" => "start",
186
+ "check_in_type" => "cron"
187
+ ]
188
+ )
191
189
 
192
190
  expect do
193
191
  Appsignal::CheckIn.cron("cron-checkin-with-block") { raise "error" }
@@ -196,12 +194,14 @@ describe Appsignal::CheckIn::Cron do
196
194
  end
197
195
 
198
196
  describe "when no block is given" do
199
- it "should only send a cron check-in finish event" do
200
- expect(scheduler).to receive(:schedule).with(hash_including(
201
- :kind => "finish",
202
- :identifier => "cron-checkin-without-block",
203
- :check_in_type => "cron"
204
- ))
197
+ it "only sends a cron check-in finish event" do
198
+ stub_check_in_request(
199
+ :events => [
200
+ "identifier" => "cron-checkin-without-block",
201
+ "kind" => "finish",
202
+ "check_in_type" => "cron"
203
+ ]
204
+ )
205
205
 
206
206
  Appsignal::CheckIn.cron("cron-checkin-without-block")
207
207
  end