pact_broker 2.32.0 → 2.33.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +3 -0
- data/CHANGELOG.md +18 -0
- data/config/database.yml +5 -8
- data/db/migrations/20190510_set_version_sequence.rb +1 -0
- data/db/migrations/20190602_add_headers_column_to_webhooks.rb +6 -0
- data/db/migrations/20190603_migrate_webhook_headers.rb +10 -0
- data/lib/pact_broker/api/resources/index.rb +12 -0
- data/lib/pact_broker/api/resources/pact.rb +1 -1
- data/lib/pact_broker/api/resources/verifications.rb +1 -1
- data/lib/pact_broker/api/resources/webhook_execution.rb +1 -1
- data/lib/pact_broker/certificates/certificate.rb +1 -1
- data/lib/pact_broker/config/setting.rb +1 -1
- data/lib/pact_broker/db/data_migrations/migrate_webhook_headers.rb +30 -0
- data/lib/pact_broker/domain/pacticipant.rb +1 -1
- data/lib/pact_broker/domain/verification.rb +1 -1
- data/lib/pact_broker/domain/version.rb +1 -1
- data/lib/pact_broker/domain/webhook.rb +27 -1
- data/lib/pact_broker/domain/webhook_request.rb +9 -146
- data/lib/pact_broker/integrations/integration.rb +7 -0
- data/lib/pact_broker/matrix/repository.rb +0 -1
- data/lib/pact_broker/matrix/row.rb +58 -17
- data/lib/pact_broker/pacts/pact_publication.rb +8 -1
- data/lib/pact_broker/pacts/pact_version.rb +1 -1
- data/lib/pact_broker/pacts/repository.rb +5 -8
- data/lib/pact_broker/repositories/helpers.rb +5 -4
- data/lib/pact_broker/test/test_data_builder.rb +2 -2
- data/lib/pact_broker/version.rb +1 -1
- data/lib/pact_broker/versions/latest_version.rb +10 -0
- data/lib/pact_broker/webhooks/execution.rb +1 -1
- data/lib/pact_broker/webhooks/http_request_with_redacted_headers.rb +21 -0
- data/lib/pact_broker/webhooks/http_response_with_utf_8_safe_body.rb +21 -0
- data/lib/pact_broker/webhooks/job.rb +2 -2
- data/lib/pact_broker/webhooks/pact_and_verification_parameters.rb +86 -0
- data/lib/pact_broker/webhooks/render.rb +10 -71
- data/lib/pact_broker/webhooks/repository.rb +2 -9
- data/lib/pact_broker/webhooks/service.rb +5 -5
- data/lib/pact_broker/webhooks/triggered_webhook.rb +1 -1
- data/lib/pact_broker/webhooks/webhook.rb +9 -30
- data/lib/pact_broker/webhooks/webhook_event.rb +1 -1
- data/lib/pact_broker/{domain → webhooks}/webhook_execution_result.rb +6 -5
- data/lib/pact_broker/webhooks/webhook_request_logger.rb +105 -0
- data/lib/pact_broker/webhooks/webhook_request_template.rb +7 -6
- data/pact_broker.gemspec +1 -0
- data/script/docker/db-restore.sh +5 -0
- data/script/docker/db-rm.sh +3 -0
- data/script/docker/db-start.sh +7 -0
- data/script/import-pg-database.sh +5 -0
- data/script/seed.rb +1 -1
- data/spec/features/execute_webhook_spec.rb +1 -1
- data/spec/features/publish_verification_spec.rb +1 -1
- data/spec/integration/webhooks/certificate_spec.rb +4 -6
- data/spec/lib/pact_broker/api/decorators/webhook_execution_result_decorator_spec.rb +1 -1
- data/spec/lib/pact_broker/api/resources/verifications_spec.rb +1 -1
- data/spec/lib/pact_broker/api/resources/webhook_execution_spec.rb +1 -1
- data/spec/lib/pact_broker/db/data_migrations/migrate_webhook_headers_spec.rb +78 -0
- data/spec/lib/pact_broker/domain/webhook_request_spec.rb +8 -163
- data/spec/lib/pact_broker/domain/webhook_spec.rb +45 -8
- data/spec/lib/pact_broker/matrix/repository_spec.rb +24 -0
- data/spec/lib/pact_broker/pacticipants/find_potential_duplicate_pacticipant_names_spec.rb +4 -4
- data/spec/lib/pact_broker/pacts/pact_publication_spec.rb +60 -0
- data/spec/lib/pact_broker/webhooks/job_spec.rb +3 -3
- data/spec/lib/pact_broker/webhooks/render_spec.rb +22 -7
- data/spec/lib/pact_broker/webhooks/repository_spec.rb +24 -24
- data/spec/lib/pact_broker/webhooks/service_spec.rb +5 -5
- data/spec/lib/pact_broker/webhooks/webhook_request_logger_spec.rb +197 -0
- data/spec/lib/pact_broker/webhooks/webhook_request_template_spec.rb +13 -4
- data/spec/support/database.rb +1 -1
- data/spec/support/ssl_pact_broker_server.rb +46 -0
- data/tasks/database.rb +12 -0
- data/tasks/db.rake +26 -6
- data/tasks/docker_database.rb +25 -0
- metadata +35 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97134f270141a482c9af9641cffd34827002be42
|
4
|
+
data.tar.gz: '058cf5c4bdddd00ffd77b8a366cfdfbf539e40ff'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf993532a2a921c255d35c52918f5aba61f310109f2d0beaacac3abddd16b8a55bcd12b4f21b7ecbade8a9c7e2104dc3198a2efbb1d3cfa3ec45c738e5357385
|
7
|
+
data.tar.gz: d5544a81be59d19d52b906f8a3c320bc25620e9020c9bc7e4d02217c7d3c549719564fa226f2df3ddd15bb8bb0776f8208d502105d0e8088cac3f018beb43804
|
data/.github/ISSUE_TEMPLATE.md
CHANGED
@@ -4,6 +4,9 @@ I have already (please mark the applicable with an `x`):
|
|
4
4
|
|
5
5
|
* [ ] Upgraded to the latest Pact Broker OR
|
6
6
|
* [ ] Checked the CHANGELOG to see if the issue I am about to raise has been fixed
|
7
|
+
* [ ] Created an executable example that demonstrates the issue using either a:
|
8
|
+
* Dockerfile
|
9
|
+
* Git repository with a Travis or Appveyor (or similar) build
|
7
10
|
|
8
11
|
## Software versions
|
9
12
|
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
<a name="v2.33.0"></a>
|
2
|
+
### v2.33.0 (2019-06-07)
|
3
|
+
|
4
|
+
|
5
|
+
#### Features
|
6
|
+
|
7
|
+
* add pb:pacticipant and pb:pacticipant-version-tag relations to the index resource ([2c4c258c](/../../commit/2c4c258c))
|
8
|
+
|
9
|
+
|
10
|
+
#### Bug Fixes
|
11
|
+
|
12
|
+
* correctly remove webhook consumer/provider when update params do not contain a consumer/provider ([118bbee1](/../../commit/118bbee1))
|
13
|
+
* duplicate key value violates unique constraint "cv_prov_revision_unq" error when publishing identical pact resources at the same time ([6c8e38fb](/../../commit/6c8e38fb))
|
14
|
+
|
15
|
+
* **matrix**
|
16
|
+
* ensure unrelated dependencies are not included in a matrix result when three pacticipants each have dependencies on each other ([a086ffec](/../../commit/a086ffec))
|
17
|
+
|
18
|
+
|
1
19
|
<a name="v2.32.0"></a>
|
2
20
|
### v2.32.0 (2019-05-28)
|
3
21
|
|
data/config/database.yml
CHANGED
@@ -18,11 +18,6 @@ test:
|
|
18
18
|
password: postgres
|
19
19
|
host: "localhost"
|
20
20
|
port: "5432"
|
21
|
-
docker_compose_postgres:
|
22
|
-
<<: *default
|
23
|
-
adapter: postgres
|
24
|
-
host: postgres
|
25
|
-
port: "5432"
|
26
21
|
mysql:
|
27
22
|
<<: *default
|
28
23
|
adapter: mysql2
|
@@ -35,10 +30,12 @@ development:
|
|
35
30
|
<<: *default
|
36
31
|
adapter: postgres
|
37
32
|
docker_postgres:
|
38
|
-
<<: *default
|
39
33
|
adapter: postgres
|
40
|
-
|
41
|
-
|
34
|
+
database: postgres
|
35
|
+
username: postgres
|
36
|
+
password: postgres
|
37
|
+
host: "localhost"
|
38
|
+
port: "5432"
|
42
39
|
mysql:
|
43
40
|
<<: *default
|
44
41
|
adapter: mysql2
|
@@ -50,6 +50,12 @@ module PactBroker
|
|
50
50
|
title: 'Pacticipants',
|
51
51
|
templated: false
|
52
52
|
},
|
53
|
+
'pb:pacticipant' =>
|
54
|
+
{
|
55
|
+
href: base_url + '/pacticipants/{pacticipant}',
|
56
|
+
title: 'Fetch pacticipant by name',
|
57
|
+
templated: true
|
58
|
+
},
|
53
59
|
'pb:latest-provider-pacts' =>
|
54
60
|
{
|
55
61
|
href: base_url + '/pacts/provider/{provider}/latest',
|
@@ -94,6 +100,12 @@ module PactBroker
|
|
94
100
|
title: 'Integrations',
|
95
101
|
templated: false
|
96
102
|
},
|
103
|
+
'pb:pacticipant-version-tag' =>
|
104
|
+
{
|
105
|
+
href: base_url + '/pacticipants/{pacticipant}/versions/{version}/tags/{tag}',
|
106
|
+
title: "Get, create or delete a tag for a pacticipant version",
|
107
|
+
templated: true
|
108
|
+
},
|
97
109
|
'beta:pending-provider-pacts' =>
|
98
110
|
{
|
99
111
|
href: base_url + '/pacts/provider/{provider}/pending',
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
|
10
10
|
# Table: certificates
|
11
11
|
# Columns:
|
12
|
-
# id | integer | PRIMARY KEY
|
12
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('certificates_id_seq'::regclass)
|
13
13
|
# uuid | text | NOT NULL
|
14
14
|
# description | text |
|
15
15
|
# content | text | NOT NULL
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'pact_broker/db/data_migrations/helpers'
|
2
|
+
|
3
|
+
module PactBroker
|
4
|
+
module DB
|
5
|
+
module DataMigrations
|
6
|
+
class MigrateWebhookHeaders
|
7
|
+
extend Helpers
|
8
|
+
|
9
|
+
def self.call(connection)
|
10
|
+
if columns_exist?(connection)
|
11
|
+
connection[:webhook_headers].for_update.each do | webhook_header |
|
12
|
+
webhook = connection[:webhooks].for_update.where(id: webhook_header[:webhook_id]).first
|
13
|
+
new_headers = webhook[:headers] ? JSON.parse(webhook[:headers]) : {}
|
14
|
+
new_headers.merge!(webhook_header[:name] => webhook_header[:value])
|
15
|
+
connection[:webhooks].where(id: webhook[:id]).update(headers: new_headers.to_json)
|
16
|
+
connection[:webhook_headers].where(webhook_header).delete
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.columns_exist?(connection)
|
22
|
+
column_exists?(connection, :webhooks, :headers) &&
|
23
|
+
column_exists?(connection, :webhook_headers, :name) &&
|
24
|
+
column_exists?(connection, :webhook_headers, :value) &&
|
25
|
+
column_exists?(connection, :webhook_headers, :webhook_id)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -52,7 +52,7 @@ end
|
|
52
52
|
|
53
53
|
# Table: pacticipants
|
54
54
|
# Columns:
|
55
|
-
# id | integer | PRIMARY KEY
|
55
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('pacticipants_id_seq'::regclass)
|
56
56
|
# name | text |
|
57
57
|
# repository_url | text |
|
58
58
|
# created_at | timestamp without time zone | NOT NULL
|
@@ -95,7 +95,7 @@ end
|
|
95
95
|
|
96
96
|
# Table: verifications
|
97
97
|
# Columns:
|
98
|
-
# id | integer | PRIMARY KEY
|
98
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('verifications_id_seq'::regclass)
|
99
99
|
# number | integer |
|
100
100
|
# success | boolean | NOT NULL
|
101
101
|
# provider_version | text |
|
@@ -48,7 +48,7 @@ end
|
|
48
48
|
|
49
49
|
# Table: versions
|
50
50
|
# Columns:
|
51
|
-
# id | integer | PRIMARY KEY
|
51
|
+
# id | integer | PRIMARY KEY DEFAULT nextval('versions_id_seq'::regclass)
|
52
52
|
# number | text |
|
53
53
|
# repository_ref | text |
|
54
54
|
# pacticipant_id | integer | NOT NULL
|
@@ -2,6 +2,7 @@ require 'pact_broker/domain/webhook_request'
|
|
2
2
|
require 'pact_broker/messages'
|
3
3
|
require 'pact_broker/logging'
|
4
4
|
require 'pact_broker/api/contracts/webhook_contract'
|
5
|
+
require 'pact_broker/webhooks/http_request_with_redacted_headers'
|
5
6
|
|
6
7
|
module PactBroker
|
7
8
|
module Domain
|
@@ -10,6 +11,7 @@ module PactBroker
|
|
10
11
|
include Messages
|
11
12
|
include Logging
|
12
13
|
|
14
|
+
# request is actually a request_template
|
13
15
|
attr_accessor :uuid, :consumer, :provider, :request, :created_at, :updated_at, :events, :enabled, :description
|
14
16
|
attr_reader :attributes
|
15
17
|
|
@@ -52,7 +54,31 @@ module PactBroker
|
|
52
54
|
|
53
55
|
def execute pact, verification, options
|
54
56
|
logger.info "Executing #{self}"
|
55
|
-
request.build(pact: pact, verification: verification, webhook_context: options.fetch(:webhook_context))
|
57
|
+
webhook_request = request.build(pact: pact, verification: verification, webhook_context: options.fetch(:webhook_context))
|
58
|
+
http_response = nil
|
59
|
+
error = nil
|
60
|
+
begin
|
61
|
+
http_response = webhook_request.execute
|
62
|
+
rescue StandardError => e
|
63
|
+
error = e
|
64
|
+
end
|
65
|
+
|
66
|
+
PactBroker::Webhooks::WebhookExecutionResult.new(
|
67
|
+
webhook_request.http_request,
|
68
|
+
http_response,
|
69
|
+
generate_logs(webhook_request, http_response, error, options.fetch(:logging_options)),
|
70
|
+
error
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_logs(webhook_request, http_response, error, logging_options)
|
75
|
+
webhook_request_logger = PactBroker::Webhooks::WebhookRequestLogger.new(logging_options)
|
76
|
+
webhook_request_logger.log(
|
77
|
+
uuid,
|
78
|
+
webhook_request,
|
79
|
+
http_response,
|
80
|
+
error
|
81
|
+
)
|
56
82
|
end
|
57
83
|
|
58
84
|
def to_s
|
@@ -1,61 +1,21 @@
|
|
1
1
|
require 'pact_broker/build_http_options'
|
2
2
|
require 'pact_broker/domain/webhook_request_header'
|
3
|
-
require 'pact_broker/
|
3
|
+
require 'pact_broker/webhooks/webhook_execution_result'
|
4
4
|
require 'pact_broker/logging'
|
5
5
|
require 'pact_broker/messages'
|
6
6
|
require 'net/http'
|
7
|
-
require 'pact_broker/webhooks/render'
|
8
|
-
require 'pact_broker/api/pact_broker_urls'
|
9
7
|
require 'pact_broker/build_http_options'
|
10
8
|
require 'cgi'
|
11
9
|
require 'delegate'
|
12
10
|
require 'rack/utils'
|
11
|
+
require 'pact_broker/webhooks/webhook_request_logger'
|
13
12
|
|
14
13
|
module PactBroker
|
15
|
-
|
16
14
|
module Domain
|
17
|
-
|
18
|
-
class WebhookRequestError < StandardError
|
19
|
-
def initialize message, response
|
20
|
-
super message
|
21
|
-
@response = response
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class WebhookResponseWithUtf8SafeBody < SimpleDelegator
|
26
|
-
def body
|
27
|
-
if unsafe_body
|
28
|
-
unsafe_body.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
|
29
|
-
else
|
30
|
-
unsafe_body
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def unsafe_body
|
35
|
-
__getobj__().body
|
36
|
-
end
|
37
|
-
|
38
|
-
def unsafe_body?
|
39
|
-
unsafe_body != body
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
class WebhookRequestWithRedactedHeaders < SimpleDelegator
|
44
|
-
def to_hash
|
45
|
-
__getobj__().to_hash.each_with_object({}) do | (key, values), new_hash |
|
46
|
-
new_hash[key] = redact?(key) ? ["**********"] : values
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def redact? name
|
51
|
-
WebhookRequest::HEADERS_TO_REDACT.any?{ | pattern | name =~ pattern }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
15
|
class WebhookRequest
|
56
16
|
|
57
17
|
include PactBroker::Logging
|
58
|
-
|
18
|
+
|
59
19
|
HEADERS_TO_REDACT = [/authorization/i, /token/i]
|
60
20
|
|
61
21
|
attr_accessor :method, :url, :headers, :body, :username, :password, :uuid
|
@@ -89,36 +49,14 @@ module PactBroker
|
|
89
49
|
end
|
90
50
|
end
|
91
51
|
|
92
|
-
def execute
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
execute_and_build_result
|
98
|
-
rescue StandardError => e
|
99
|
-
handle_error_and_build_result(e)
|
52
|
+
def execute
|
53
|
+
options = PactBroker::BuildHttpOptions.call(uri)
|
54
|
+
req = http_request
|
55
|
+
Net::HTTP.start(uri.hostname, uri.port, :ENV, options) do |http|
|
56
|
+
http.request req
|
100
57
|
end
|
101
58
|
end
|
102
59
|
|
103
|
-
private
|
104
|
-
|
105
|
-
attr_reader :options, :execution_logger, :logs
|
106
|
-
|
107
|
-
def execute_and_build_result
|
108
|
-
log_request
|
109
|
-
response = do_request
|
110
|
-
log_response(response)
|
111
|
-
result = WebhookExecutionResult.new(WebhookRequestWithRedactedHeaders.new(http_request), response, logs.string)
|
112
|
-
log_completion_message(result.success?)
|
113
|
-
result
|
114
|
-
end
|
115
|
-
|
116
|
-
def handle_error_and_build_result e
|
117
|
-
log_error(e)
|
118
|
-
log_completion_message(false)
|
119
|
-
WebhookExecutionResult.new(WebhookRequestWithRedactedHeaders.new(http_request), nil, logs.string, e)
|
120
|
-
end
|
121
|
-
|
122
60
|
def http_request
|
123
61
|
@http_request ||= begin
|
124
62
|
req = Net::HTTP.const_get(method.capitalize).new(url)
|
@@ -129,82 +67,7 @@ module PactBroker
|
|
129
67
|
end
|
130
68
|
end
|
131
69
|
|
132
|
-
|
133
|
-
options = PactBroker::BuildHttpOptions.call(uri)
|
134
|
-
req = http_request
|
135
|
-
response = Net::HTTP.start(uri.hostname, uri.port, :ENV, options) do |http|
|
136
|
-
http.request req
|
137
|
-
end
|
138
|
-
WebhookResponseWithUtf8SafeBody.new(response)
|
139
|
-
end
|
140
|
-
|
141
|
-
def log_request
|
142
|
-
redacted_request = WebhookRequestWithRedactedHeaders.new(http_request)
|
143
|
-
logger.info "Making webhook #{uuid} request #{http_method.upcase} URI=#{uri} (headers and body in debug logs)"
|
144
|
-
logger.debug "Webhook #{uuid} request headers=#{redacted_request.to_hash}"
|
145
|
-
logger.debug "Webhook #{uuid} request body=#{redacted_request.body}"
|
146
|
-
|
147
|
-
execution_logger.info "HTTP/1.1 #{http_method.upcase} #{url}"
|
148
|
-
redacted_request.to_hash.each do | name, value |
|
149
|
-
execution_logger.info "#{name}: #{value.join(", ")}"
|
150
|
-
end
|
151
|
-
execution_logger.info(body) if body
|
152
|
-
end
|
153
|
-
|
154
|
-
def log_response response
|
155
|
-
log_response_to_application_logger(response)
|
156
|
-
if options.fetch(:show_response)
|
157
|
-
log_response_to_execution_logger(response)
|
158
|
-
else
|
159
|
-
execution_logger.info response_body_hidden_message
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def response_body_hidden_message
|
164
|
-
PactBroker::Messages.message('messages.response_body_hidden')
|
165
|
-
end
|
166
|
-
|
167
|
-
def log_response_to_application_logger response
|
168
|
-
logger.info "Received response for webhook #{uuid} status=#{response.code} (headers and body in debug logs)"
|
169
|
-
logger.debug "Webhook #{uuid} response headers=#{response.to_hash} "
|
170
|
-
logger.debug "Webhook #{uuid} response body=#{response.unsafe_body}"
|
171
|
-
end
|
172
|
-
|
173
|
-
def log_response_to_execution_logger response
|
174
|
-
execution_logger.info "HTTP/#{response.http_version} #{response.code} #{response.message}"
|
175
|
-
response.each_header do | name, value |
|
176
|
-
execution_logger.info "#{name}: #{value}"
|
177
|
-
end
|
178
|
-
|
179
|
-
if response.body
|
180
|
-
if response.unsafe_body?
|
181
|
-
execution_logger.debug "Note that invalid UTF-8 byte sequences were removed from response body before saving the logs"
|
182
|
-
end
|
183
|
-
execution_logger.info response.body
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
def log_completion_message success
|
188
|
-
if options[:success_log_message] && success
|
189
|
-
execution_logger.info(options[:success_log_message])
|
190
|
-
logger.info(options[:success_log_message])
|
191
|
-
end
|
192
|
-
|
193
|
-
if options[:failure_log_message] && !success
|
194
|
-
execution_logger.info(options[:failure_log_message])
|
195
|
-
logger.info(options[:failure_log_message])
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
def log_error e
|
200
|
-
logger.info "Error executing webhook #{uuid} #{e.class.name} - #{e.message} #{e.backtrace.join("\n")}"
|
201
|
-
|
202
|
-
if options[:show_response]
|
203
|
-
execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
|
204
|
-
else
|
205
|
-
execution_logger.error "Error executing webhook #{uuid}. #{response_body_hidden_message}"
|
206
|
-
end
|
207
|
-
end
|
70
|
+
private
|
208
71
|
|
209
72
|
def to_s
|
210
73
|
"#{method.upcase} #{url}, username=#{username}, password=#{display_password}, headers=#{redacted_headers}, body=#{body}"
|