appsignal 4.0.3-java → 4.0.5-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -0
- data/ext/agent.rb +27 -27
- data/lib/appsignal/check_in/cron.rb +2 -34
- data/lib/appsignal/check_in/scheduler.rb +192 -0
- data/lib/appsignal/check_in.rb +18 -0
- data/lib/appsignal/cli/diagnose.rb +1 -1
- data/lib/appsignal/config.rb +7 -0
- data/lib/appsignal/hooks/at_exit.rb +3 -1
- data/lib/appsignal/hooks/puma.rb +5 -1
- data/lib/appsignal/integrations/puma.rb +45 -0
- data/lib/appsignal/rack/abstract_middleware.rb +3 -47
- data/lib/appsignal/rack/body_wrapper.rb +15 -0
- data/lib/appsignal/rack/event_handler.rb +2 -0
- data/lib/appsignal/rack/hanami_middleware.rb +5 -1
- data/lib/appsignal/rack.rb +68 -0
- data/lib/appsignal/transmitter.rb +30 -7
- data/lib/appsignal/utils/ndjson.rb +15 -0
- data/lib/appsignal/utils.rb +1 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +1 -0
- data/spec/lib/appsignal/check_in/cron_spec.rb +202 -0
- data/spec/lib/appsignal/check_in/scheduler_spec.rb +443 -0
- data/spec/lib/appsignal/config_spec.rb +13 -0
- data/spec/lib/appsignal/environment_spec.rb +1 -1
- data/spec/lib/appsignal/hooks/at_exit_spec.rb +22 -0
- data/spec/lib/appsignal/hooks/puma_spec.rb +31 -23
- data/spec/lib/appsignal/integrations/puma_spec.rb +150 -0
- data/spec/lib/appsignal/probes_spec.rb +1 -6
- data/spec/lib/appsignal/rack/abstract_middleware_spec.rb +41 -122
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +29 -21
- data/spec/lib/appsignal/rack_spec.rb +180 -0
- data/spec/lib/appsignal/transmitter_spec.rb +48 -2
- data/spec/lib/appsignal_spec.rb +5 -0
- data/spec/spec_helper.rb +0 -7
- data/spec/support/helpers/config_helpers.rb +2 -1
- data/spec/support/helpers/take_at_most_helper.rb +21 -0
- data/spec/support/matchers/contains_log.rb +10 -3
- data/spec/support/mocks/hash_like.rb +10 -0
- data/spec/support/mocks/puma_mock.rb +43 -0
- metadata +11 -3
- data/spec/lib/appsignal/check_in_spec.rb +0 -136
@@ -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
|
-
|
140
|
-
|
141
|
-
|
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)
|
@@ -57,6 +57,21 @@ module Appsignal
|
|
57
57
|
@transaction.set_error(error)
|
58
58
|
raise error
|
59
59
|
end
|
60
|
+
|
61
|
+
# Return whether the wrapped body responds to the method if this class does not.
|
62
|
+
# Based on:
|
63
|
+
# https://github.com/rack/rack/blob/0ed580bbe3858ffe5d530adf1bdad9ef9c03407c/lib/rack/body_proxy.rb#L16-L24
|
64
|
+
def respond_to_missing?(method_name, include_all = false)
|
65
|
+
super || @body.respond_to?(method_name, include_all)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Delegate missing methods to the wrapped body.
|
69
|
+
# Based on:
|
70
|
+
# https://github.com/rack/rack/blob/0ed580bbe3858ffe5d530adf1bdad9ef9c03407c/lib/rack/body_proxy.rb#L44-L61
|
71
|
+
def method_missing(method_name, *args, &block)
|
72
|
+
@body.__send__(method_name, *args, &block)
|
73
|
+
end
|
74
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
60
75
|
end
|
61
76
|
|
62
77
|
# The standard Rack body wrapper which exposes "each" for iterating
|
@@ -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]
|
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
|
data/lib/appsignal/rack.rb
CHANGED
@@ -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
|
@@ -9,7 +9,8 @@ require "json"
|
|
9
9
|
module Appsignal
|
10
10
|
# @api private
|
11
11
|
class Transmitter
|
12
|
-
|
12
|
+
JSON_CONTENT_TYPE = "application/json; charset=UTF-8"
|
13
|
+
NDJSON_CONTENT_TYPE = "application/x-ndjson; charset=UTF-8"
|
13
14
|
|
14
15
|
HTTP_ERRORS = [
|
15
16
|
EOFError,
|
@@ -53,17 +54,39 @@ module Appsignal
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
56
|
-
def transmit(payload)
|
57
|
-
|
58
|
-
http_client.request(http_post(payload))
|
57
|
+
def transmit(payload, format: :json)
|
58
|
+
Appsignal.internal_logger.debug "Transmitting payload to #{uri}"
|
59
|
+
http_client.request(http_post(payload, :format => format))
|
59
60
|
end
|
60
61
|
|
61
62
|
private
|
62
63
|
|
63
|
-
def http_post(payload)
|
64
|
+
def http_post(payload, format: :json)
|
64
65
|
Net::HTTP::Post.new(uri.request_uri).tap do |request|
|
65
|
-
request["Content-Type"] =
|
66
|
-
request.body =
|
66
|
+
request["Content-Type"] = content_type_for(format)
|
67
|
+
request.body = generate_body_for(format, payload)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def content_type_for(format)
|
72
|
+
case format
|
73
|
+
when :json
|
74
|
+
JSON_CONTENT_TYPE
|
75
|
+
when :ndjson
|
76
|
+
NDJSON_CONTENT_TYPE
|
77
|
+
else
|
78
|
+
raise ArgumentError, "Unknown Content-Type header for format: #{format}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def generate_body_for(format, payload)
|
83
|
+
case format
|
84
|
+
when :json
|
85
|
+
Appsignal::Utils::JSON.generate(payload)
|
86
|
+
when :ndjson
|
87
|
+
Appsignal::Utils::NDJSON.generate(payload)
|
88
|
+
else
|
89
|
+
raise ArgumentError, "Unknown body generator for format: #{format}"
|
67
90
|
end
|
68
91
|
end
|
69
92
|
|
data/lib/appsignal/utils.rb
CHANGED
data/lib/appsignal/version.rb
CHANGED
data/lib/appsignal.rb
CHANGED
@@ -0,0 +1,202 @@
|
|
1
|
+
describe Appsignal::CheckIn::Cron do
|
2
|
+
let(:log_stream) { std_stream }
|
3
|
+
let(:logs) { log_contents(log_stream) }
|
4
|
+
let(:appsignal_options) { {} }
|
5
|
+
let(:config) { project_fixture_config }
|
6
|
+
let(:cron_checkin) { described_class.new(:identifier => "cron-checkin-name") }
|
7
|
+
let(:transmitter) { Appsignal::Transmitter.new("https://checkin-endpoint.invalid") }
|
8
|
+
let(:scheduler) { Appsignal::CheckIn::Scheduler.new }
|
9
|
+
|
10
|
+
before do
|
11
|
+
start_agent(
|
12
|
+
:options => appsignal_options,
|
13
|
+
:internal_logger => test_logger(log_stream)
|
14
|
+
)
|
15
|
+
allow(Appsignal::CheckIn).to receive(:scheduler).and_return(scheduler)
|
16
|
+
allow(Appsignal::CheckIn).to receive(:transmitter).and_return(transmitter)
|
17
|
+
end
|
18
|
+
after { stop_scheduler }
|
19
|
+
|
20
|
+
def stop_scheduler
|
21
|
+
scheduler.stop
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "when Appsignal is not active" do
|
25
|
+
let(:appsignal_options) { { :active => false } }
|
26
|
+
|
27
|
+
it "does not transmit any events" do
|
28
|
+
expect(Appsignal).to_not be_started
|
29
|
+
|
30
|
+
cron_checkin.start
|
31
|
+
cron_checkin.finish
|
32
|
+
stop_scheduler
|
33
|
+
|
34
|
+
expect(logs).to contains_log(
|
35
|
+
:debug,
|
36
|
+
/Cannot transmit cron check-in `cron-checkin-name` start event .+: AppSignal is not active/
|
37
|
+
)
|
38
|
+
expect(logs).to contains_log(
|
39
|
+
:debug,
|
40
|
+
/Cannot transmit cron check-in `cron-checkin-name` finish event .+: AppSignal is not active/
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "when AppSignal is stopped" do
|
46
|
+
it "does not transmit any events" do
|
47
|
+
Appsignal.stop
|
48
|
+
|
49
|
+
cron_checkin.start
|
50
|
+
cron_checkin.finish
|
51
|
+
|
52
|
+
expect(logs).to contains_log(
|
53
|
+
:debug,
|
54
|
+
"Cannot transmit cron check-in `cron-checkin-name` start event"
|
55
|
+
)
|
56
|
+
expect(logs).to contains_log(
|
57
|
+
:debug,
|
58
|
+
"Cannot transmit cron check-in `cron-checkin-name` finish event"
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#start" do
|
64
|
+
it "sends a cron check-in start" do
|
65
|
+
cron_checkin.start
|
66
|
+
|
67
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
68
|
+
:identifier => "cron-checkin-name",
|
69
|
+
:kind => "start",
|
70
|
+
:check_in_type => "cron"
|
71
|
+
)], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "200", nil))
|
72
|
+
|
73
|
+
scheduler.stop
|
74
|
+
|
75
|
+
expect(logs).to_not contains_log(:error)
|
76
|
+
expect(logs).to contains_log(
|
77
|
+
:debug,
|
78
|
+
"Scheduling cron check-in `cron-checkin-name` start event"
|
79
|
+
)
|
80
|
+
expect(logs).to contains_log(
|
81
|
+
:debug,
|
82
|
+
"Transmitted cron check-in `cron-checkin-name` start event"
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "logs an error if it fails" do
|
87
|
+
cron_checkin.start
|
88
|
+
|
89
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
90
|
+
:identifier => "cron-checkin-name",
|
91
|
+
:kind => "start",
|
92
|
+
:check_in_type => "cron"
|
93
|
+
)], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "499", nil))
|
94
|
+
|
95
|
+
scheduler.stop
|
96
|
+
|
97
|
+
expect(logs).to contains_log(
|
98
|
+
:debug,
|
99
|
+
"Scheduling cron check-in `cron-checkin-name` start event"
|
100
|
+
)
|
101
|
+
expect(logs).to contains_log(
|
102
|
+
:error,
|
103
|
+
/Failed to transmit cron check-in `cron-checkin-name` start event .+: 499 status code/
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#finish" do
|
109
|
+
it "sends a cron check-in finish" do
|
110
|
+
cron_checkin.finish
|
111
|
+
|
112
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
113
|
+
:identifier => "cron-checkin-name",
|
114
|
+
:kind => "finish",
|
115
|
+
:check_in_type => "cron"
|
116
|
+
)], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "200", nil))
|
117
|
+
|
118
|
+
scheduler.stop
|
119
|
+
expect(logs).to_not contains_log(:error)
|
120
|
+
expect(logs).to contains_log(
|
121
|
+
:debug,
|
122
|
+
"Scheduling cron check-in `cron-checkin-name` finish event"
|
123
|
+
)
|
124
|
+
expect(logs).to contains_log(
|
125
|
+
:debug,
|
126
|
+
"Transmitted cron check-in `cron-checkin-name` finish event"
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "logs an error if it fails" do
|
131
|
+
cron_checkin.finish
|
132
|
+
|
133
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
134
|
+
:identifier => "cron-checkin-name",
|
135
|
+
:kind => "finish",
|
136
|
+
:check_in_type => "cron"
|
137
|
+
)], :format => :ndjson).and_return(Net::HTTPResponse.new(nil, "499", nil))
|
138
|
+
|
139
|
+
scheduler.stop
|
140
|
+
|
141
|
+
expect(logs).to contains_log(
|
142
|
+
:debug,
|
143
|
+
"Scheduling cron check-in `cron-checkin-name` finish event"
|
144
|
+
)
|
145
|
+
expect(logs).to contains_log(
|
146
|
+
:error,
|
147
|
+
/Failed to transmit cron check-in `cron-checkin-name` finish event .+: 499 status code/
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe ".cron" do
|
153
|
+
describe "when a block is given" do
|
154
|
+
it "sends a cron check-in start and finish and return the block output" do
|
155
|
+
expect(scheduler).to receive(:schedule).with(hash_including(
|
156
|
+
:kind => "start",
|
157
|
+
:identifier => "cron-checkin-with-block",
|
158
|
+
:check_in_type => "cron"
|
159
|
+
))
|
160
|
+
|
161
|
+
expect(scheduler).to receive(:schedule).with(hash_including(
|
162
|
+
:kind => "finish",
|
163
|
+
:identifier => "cron-checkin-with-block",
|
164
|
+
:check_in_type => "cron"
|
165
|
+
))
|
166
|
+
|
167
|
+
output = Appsignal::CheckIn.cron("cron-checkin-with-block") { "output" }
|
168
|
+
expect(output).to eq("output")
|
169
|
+
end
|
170
|
+
|
171
|
+
it "does not send a cron check-in finish event when an error is raised" do
|
172
|
+
expect(scheduler).to receive(:schedule).with(hash_including(
|
173
|
+
:kind => "start",
|
174
|
+
:identifier => "cron-checkin-with-block",
|
175
|
+
:check_in_type => "cron"
|
176
|
+
))
|
177
|
+
|
178
|
+
expect(scheduler).not_to receive(:schedule).with(hash_including(
|
179
|
+
:kind => "finish",
|
180
|
+
:identifier => "cron-checkin-with-block",
|
181
|
+
:check_in_type => "cron"
|
182
|
+
))
|
183
|
+
|
184
|
+
expect do
|
185
|
+
Appsignal::CheckIn.cron("cron-checkin-with-block") { raise "error" }
|
186
|
+
end.to raise_error(RuntimeError, "error")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "when no block is given" do
|
191
|
+
it "only sends a cron check-in finish event" do
|
192
|
+
expect(scheduler).to receive(:schedule).with(hash_including(
|
193
|
+
:kind => "finish",
|
194
|
+
:identifier => "cron-checkin-without-block",
|
195
|
+
:check_in_type => "cron"
|
196
|
+
))
|
197
|
+
|
198
|
+
Appsignal::CheckIn.cron("cron-checkin-without-block")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|