Skip to content

Commit 5d16330

Browse files
committed
feat(webhook status): log number of seconds until next webhook attempt in webhook logs
1 parent 648e1c3 commit 5d16330

File tree

9 files changed

+156
-90
lines changed

9 files changed

+156
-90
lines changed

lib/pact_broker/domain/webhook.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ def request_description
3131
request && request.description
3232
end
3333

34-
def execute
34+
def execute options
3535
logger.info "Executing #{self}"
36-
request.execute
36+
request.execute options
3737
end
3838

3939
def to_s

lib/pact_broker/domain/webhook_request.rb

+59-37
Original file line numberDiff line numberDiff line change
@@ -47,58 +47,80 @@ def display_password
4747
password.nil? ? nil : "**********"
4848
end
4949

50-
def execute
51-
50+
def execute options = {}
5251
logs = StringIO.new
5352
execution_logger = Logger.new(logs)
54-
5553
begin
56-
req = http_request
57-
execution_logger.info "HTTP/1.1 #{method.upcase} #{url_with_credentials}"
54+
execute_and_build_result(options, logs, execution_logger)
55+
rescue StandardError => e
56+
handle_error_and_build_result(e, options, logs, execution_logger)
57+
end
58+
end
5859

59-
headers.each_pair do | name, value |
60-
execution_logger.info Webhooks::RedactLogs.call("#{name}: #{value}")
61-
req[name] = value
62-
end
60+
private
6361

64-
req.basic_auth(username, password) if username
62+
def execute_and_build_result options, logs, execution_logger
63+
req = build_request(execution_logger)
64+
response = do_request(req)
65+
log_response(response, execution_logger)
66+
result = WebhookExecutionResult.new(response, logs.string)
67+
log_completion_message(options, execution_logger, result.success?)
68+
result
69+
end
6570

66-
unless body.nil?
67-
if String === body
68-
req.body = body
69-
else
70-
req.body = body.to_json
71-
end
72-
end
71+
def handle_error_and_build_result e, options, logs, execution_logger
72+
logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message} #{e.backtrace.join("\n")}"
73+
execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
74+
log_completion_message(options, execution_logger, false)
75+
WebhookExecutionResult.new(nil, logs.string, e)
76+
end
7377

74-
execution_logger.info req.body
78+
def build_request execution_logger
79+
req = http_request
80+
execution_logger.info "HTTP/1.1 #{method.upcase} #{url_with_credentials}"
7581

76-
logger.info "Making webhook #{uuid} request #{to_s}"
82+
headers.each_pair do | name, value |
83+
execution_logger.info Webhooks::RedactLogs.call("#{name}: #{value}")
84+
req[name] = value
85+
end
7786

78-
response = Net::HTTP.start(uri.hostname, uri.port,
79-
:use_ssl => uri.scheme == 'https') do |http|
80-
http.request req
81-
end
87+
req.basic_auth(username, password) if username
8288

83-
execution_logger.info(" ")
84-
logger.info "Received response for webhook #{uuid} status=#{response.code}"
85-
execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
86-
response.each_header do | header |
87-
execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
89+
unless body.nil?
90+
if String === body
91+
req.body = body
92+
else
93+
req.body = body.to_json
8894
end
89-
logger.debug "body=#{response.body}"
90-
execution_logger.info response.body
91-
WebhookExecutionResult.new(response, logs.string)
95+
end
9296

93-
rescue StandardError => e
94-
logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
95-
logger.error e.backtrace.join("\n")
96-
execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
97-
WebhookExecutionResult.new(nil, logs.string, e)
97+
execution_logger.info req.body
98+
req
99+
end
100+
101+
def do_request req
102+
logger.info "Making webhook #{uuid} request #{to_s}"
103+
Net::HTTP.start(uri.hostname, uri.port,
104+
:use_ssl => uri.scheme == 'https') do |http|
105+
http.request req
98106
end
99107
end
100108

101-
private
109+
def log_response response, execution_logger
110+
execution_logger.info(" ")
111+
logger.info "Received response for webhook #{uuid} status=#{response.code}"
112+
execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
113+
response.each_header do | header |
114+
execution_logger.info "#{header.split("-").collect(&:capitalize).join('-')}: #{response[header]}"
115+
end
116+
logger.debug "body=#{response.body}"
117+
execution_logger.info response.body
118+
end
119+
120+
def log_completion_message options, execution_logger, success
121+
execution_logger.info(options[:success_log_message]) if options[:success_log_message] && success
122+
execution_logger.info(options[:failure_log_message]) if options[:failure_log_message] && !success
123+
end
102124

103125
def to_s
104126
"#{method.upcase} #{url}, username=#{username}, password=#{display_password}, headers=#{headers}, body=#{body}"

lib/pact_broker/webhooks/job.rb

+18-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def perform data
1414
@triggered_webhook = data[:triggered_webhook]
1515
@error_count = data[:error_count] || 0
1616
begin
17-
webhook_execution_result = PactBroker::Webhooks::Service.execute_triggered_webhook_now triggered_webhook
17+
webhook_execution_result = PactBroker::Webhooks::Service.execute_triggered_webhook_now triggered_webhook, execution_options
1818
if webhook_execution_result.success?
1919
handle_success
2020
else
@@ -29,6 +29,21 @@ def perform data
2929

3030
attr_reader :triggered_webhook, :error_count
3131

32+
def execution_options
33+
{
34+
success_log_message: "Successfully executed webhook",
35+
failure_log_message: failure_log_message
36+
}
37+
end
38+
39+
def failure_log_message
40+
if reschedule_job?
41+
"Retrying webhook in #{backoff_time} seconds"
42+
else
43+
"Webhook execution failed after #{retry_schedule.size + 1} attempts"
44+
end
45+
end
46+
3247
def handle_error e
3348
log_error e
3449
handle_failure
@@ -43,7 +58,7 @@ def handle_failure
4358
reschedule_job
4459
update_triggered_webhook_status TriggeredWebhook::STATUS_RETRYING
4560
else
46-
logger.error "Failed to execute webhook #{triggered_webhook.webhook_uuid} after #{retry_schedule.size} times."
61+
logger.error "Failed to execute webhook #{triggered_webhook.webhook_uuid} after #{retry_schedule.size + 1} attempts."
4762
update_triggered_webhook_status TriggeredWebhook::STATUS_FAILURE
4863
end
4964
end
@@ -53,7 +68,7 @@ def reschedule_job?
5368
end
5469

5570
def reschedule_job
56-
logger.debug "Re-enqeuing job for webhook #{triggered_webhook.webhook_uuid} to run in #{retry_schedule[error_count]} seconds"
71+
logger.debug "Re-enqeuing job for webhook #{triggered_webhook.webhook_uuid} to run in #{backoff_time} seconds"
5772
Job.perform_in(backoff_time, @data.merge(error_count: error_count+1))
5873
end
5974

lib/pact_broker/webhooks/service.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def self.find_all
5656

5757
def self.execute_webhook_now webhook, pact
5858
triggered_webhook = webhook_repository.create_triggered_webhook(next_uuid, webhook, pact, USER)
59-
webhook_execution_result = execute_triggered_webhook_now triggered_webhook
59+
webhook_execution_result = execute_triggered_webhook_now triggered_webhook, failure_log_message: "Webhook execution failed"
6060
if webhook_execution_result.success?
6161
webhook_repository.update_triggered_webhook_status triggered_webhook, TriggeredWebhook::STATUS_SUCCESS
6262
else
@@ -65,8 +65,8 @@ def self.execute_webhook_now webhook, pact
6565
webhook_execution_result
6666
end
6767

68-
def self.execute_triggered_webhook_now triggered_webhook
69-
webhook_execution_result = triggered_webhook.execute
68+
def self.execute_triggered_webhook_now triggered_webhook, options
69+
webhook_execution_result = triggered_webhook.execute options
7070
webhook_repository.create_execution triggered_webhook, webhook_execution_result
7171
webhook_execution_result
7272
end

lib/pact_broker/webhooks/triggered_webhook.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ def request_description
2828
webhook.to_domain.request_description
2929
end
3030

31-
def execute
32-
webhook.to_domain.execute
31+
def execute options
32+
webhook.to_domain.execute options
3333
end
3434

3535
def consumer_name

spec/features/execute_webhook_spec.rb

-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
end
2828

2929
it "returns a 200 response" do
30-
puts subject.body
3130
expect(subject.status).to be 200
3231
end
3332

spec/lib/pact_broker/domain/webhook_request_spec.rb

+35-20
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module Domain
1414
let(:body) { 'body' }
1515
let(:logs) { StringIO.new }
1616
let(:execution_logger) { Logger.new(logs) }
17+
let(:options) { {failure_log_message: 'oops'}}
1718

1819
subject do
1920
WebhookRequest.new(
@@ -25,6 +26,8 @@ module Domain
2526
body: body)
2627
end
2728

29+
let(:logs) { subject.execute(options).logs }
30+
2831
describe "description" do
2932
it "returns a brief description of the HTTP request" do
3033
expect(subject.description).to eq 'POST example.org'
@@ -54,28 +57,26 @@ module Domain
5457
end
5558

5659
it "executes the configured request" do
57-
subject.execute
60+
subject.execute(options)
5861
expect(http_request).to have_been_made
5962
end
6063

6164
it "logs the request" do
6265
allow(PactBroker.logger).to receive(:info)
6366
expect(PactBroker.logger).to receive(:info).with(/POST.*example.*text.*body/)
64-
subject.execute
67+
subject.execute(options)
6568
end
6669

6770
it "logs the response" do
6871
allow(PactBroker.logger).to receive(:info)
6972
allow(PactBroker.logger).to receive(:debug)
7073
expect(PactBroker.logger).to receive(:info).with(/response.*302/)
7174
expect(PactBroker.logger).to receive(:debug).with(/respbod/)
72-
subject.execute
75+
subject.execute(options)
7376
end
7477

7578
describe "execution logs" do
7679

77-
let(:logs) { subject.execute.logs }
78-
7980
it "logs the request method and path" do
8081
expect(logs).to include "POST http://example.org/hook"
8182
end
@@ -104,6 +105,20 @@ module Domain
104105
expect(logs).to include "respbod"
105106
end
106107

108+
context "when the response code is a success" do
109+
it "does not log the failure_log_message" do
110+
allow_any_instance_of(WebhookExecutionResult).to receive(:success?).and_return(true)
111+
expect(logs).to_not include "oops"
112+
end
113+
end
114+
115+
context "when the response code is not successful" do
116+
it "logs the failure_log_message" do
117+
allow_any_instance_of(WebhookExecutionResult).to receive(:success?).and_return(false)
118+
expect(logs).to include "oops"
119+
end
120+
end
121+
107122
context "with basic auth" do
108123
let(:username) { 'username' }
109124
let(:password) { 'password' }
@@ -129,7 +144,7 @@ module Domain
129144
end
130145

131146
it "uses the credentials" do
132-
subject.execute
147+
subject.execute(options)
133148
expect(http_request_with_basic_auth).to have_been_made
134149
end
135150
end
@@ -145,7 +160,7 @@ module Domain
145160
end
146161

147162
it "uses SSL" do
148-
subject.execute
163+
subject.execute(options)
149164
expect(https_request).to have_been_made
150165
end
151166
end
@@ -160,7 +175,7 @@ module Domain
160175
end
161176

162177
it "converts the body to JSON before submitting the request" do
163-
subject.execute
178+
subject.execute(options)
164179
expect(http_request).to have_been_made
165180
end
166181
end
@@ -175,18 +190,18 @@ module Domain
175190
end
176191

177192
it "executes the request without a body" do
178-
subject.execute
193+
subject.execute(options)
179194
expect(http_request).to have_been_made
180195
end
181196
end
182197

183198
context "when the request is successful" do
184199
it "returns a WebhookExecutionResult with success=true" do
185-
expect(subject.execute.success?).to be true
200+
expect(subject.execute(options).success?).to be true
186201
end
187202

188203
it "sets the response on the result" do
189-
expect(subject.execute.response).to be_instance_of(Net::HTTPFound)
204+
expect(subject.execute(options).response).to be_instance_of(Net::HTTPFound)
190205
end
191206
end
192207

@@ -199,11 +214,11 @@ module Domain
199214
end
200215

201216
it "returns a WebhookExecutionResult with success=false" do
202-
expect(subject.execute.success?).to be false
217+
expect(subject.execute(options).success?).to be false
203218
end
204219

205220
it "sets the response on the result" do
206-
expect(subject.execute.response).to be_instance_of(Net::HTTPInternalServerError)
221+
expect(subject.execute(options).response).to be_instance_of(Net::HTTPInternalServerError)
207222
end
208223
end
209224

@@ -218,22 +233,22 @@ class WebhookTestError < StandardError; end
218233
it "logs the error" do
219234
allow(PactBroker.logger).to receive(:error)
220235
expect(PactBroker.logger).to receive(:error).with(/Error.*WebhookTestError.*blah/)
221-
subject.execute
236+
subject.execute(options)
222237
end
223238

224239
it "returns a WebhookExecutionResult with success=false" do
225-
expect(subject.execute.success?).to be false
240+
expect(subject.execute(options).success?).to be false
226241
end
227242

228243
it "returns a WebhookExecutionResult with an error" do
229-
expect(subject.execute.error).to be_instance_of WebhookTestError
244+
expect(subject.execute(options).error).to be_instance_of WebhookTestError
230245
end
231-
end
232246

247+
it "logs the failure_log_message" do
248+
expect(logs).to include "oops"
249+
end
250+
end
233251
end
234-
235252
end
236-
237253
end
238-
239254
end

0 commit comments

Comments
 (0)