appsignal 4.0.4-java → 4.0.6-java

Sign up to get free protection for your applications and to get access to all the features.
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