Merge branch 'main' into dependabot/go_modules/google.golang.org/protobuf-1.34.2

This commit is contained in:
Bartlomiej Plotka 2024-07-29 12:27:38 +02:00 committed by GitHub
commit f8718b0c47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 117 additions and 176 deletions

34
.github/labeler.yml vendored
View File

@ -1,34 +0,0 @@
# Config file for pr-auto-label workflow
# enable auto-labeler on issues, prs, or both.
enable:
prs: true
# Labels is an object where:
# - keys are labels
# - values are objects of { include: [ pattern ], exclude: [ pattern ] }
# - pattern must be a valid regex, and is applied globally to
# title + description of issues and/or prs (see enabled config above)
# - 'include' patterns will associate a label if any of these patterns match
# - 'exclude' patterns will ignore this label if any of these patterns match
labels:
'kind/BUGFIX':
include:
- '/kind (fix|bugfix)'
exclude: []
'kind/ENHANCEMENT':
include:
- '/kind enhancement'
exclude: []
'kind/FEATURE':
include:
- '/kind (feat|feature)'
exclude: []
'kind/CHANGE':
include:
- '/kind change'
exclude: []
'release-note-none':
include:
- '/kind (none|NONE|release-note-none|release-notes-none)'
exclude: []

View File

@ -1,4 +0,0 @@
<!-- Refer to CONTRIBUTING.md for more details and examples.
https://github.com/prometheus/client_golang/blob/main/CONTRIBUTING.md#how-to-write-a-pr-description
-->
### Describe your PR

View File

@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Dependabot metadata - name: Dependabot metadata
id: metadata id: metadata
uses: dependabot/fetch-metadata@5e5f99653a5b510e8555840e80cbf1514ad4af38 # v2.1.0 uses: dependabot/fetch-metadata@dbb049abf0d677abbd7f7eee0375145b417fdd34 # v2.2.0
with: with:
github-token: "${{ secrets.GITHUB_TOKEN }}" github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs - name: Enable auto-merge for Dependabot PRs

View File

@ -50,7 +50,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 uses: github/codeql-action/init@2d790406f505036ef40ecba973cc774a50395aac # v3.25.13
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -61,7 +61,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 uses: github/codeql-action/autobuild@2d790406f505036ef40ecba973cc774a50395aac # v3.25.13
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -75,4 +75,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 uses: github/codeql-action/analyze@2d790406f505036ef40ecba973cc774a50395aac # v3.25.13

View File

@ -0,0 +1,57 @@
---
name: Push README to Docker Hub
on:
push:
paths:
- "README.md"
- "README-containers.md"
- ".github/workflows/container_description.yml"
branches: [ main, master ]
permissions:
contents: read
jobs:
PushDockerHubReadme:
runs-on: ubuntu-latest
name: Push README to Docker Hub
if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks.
steps:
- name: git checkout
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set docker hub repo name
run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV
- name: Push README to Dockerhub
uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1
env:
DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }}
DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }}
with:
destination_container_repo: ${{ env.DOCKER_REPO_NAME }}
provider: dockerhub
short_description: ${{ env.DOCKER_REPO_NAME }}
# Empty string results in README-containers.md being pushed if it
# exists. Otherwise, README.md is pushed.
readme_file: ''
PushQuayIoReadme:
runs-on: ubuntu-latest
name: Push README to quay.io
if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks.
steps:
- name: git checkout
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
- name: Set quay.io org name
run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV
- name: Set quay.io repo name
run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV
- name: Push README to quay.io
uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1
env:
DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }}
with:
destination_container_repo: ${{ env.DOCKER_REPO_NAME }}
provider: quay
# Empty string results in README-containers.md being pushed if it
# exists. Otherwise, README.md is pushed.
readme_file: ''

View File

@ -45,7 +45,7 @@ jobs:
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Go ${{ matrix.go_version }} - name: Set up Go ${{ matrix.go_version }}
uses: actions/setup-go@v5.0.0 uses: actions/setup-go@v5.0.2
with: with:
go-version: ${{ matrix.go_version }} go-version: ${{ matrix.go_version }}

View File

@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install Go - name: Install Go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with: with:
go-version: 1.22.x go-version: 1.22.x
- name: Install snmp_exporter/generator dependencies - name: Install snmp_exporter/generator dependencies

View File

@ -1,62 +0,0 @@
---
name: Changelog automation
on:
pull_request_target:
types: [opened, edited]
permissions:
contents: read # the config file
pull-requests: write # for labeling pull requests (on: pull_request_target or on: pull_request)
statuses: write # to generate status
checks: write # to generate status
jobs:
changelog-automation:
name: Changelog automation
runs-on: ubuntu-latest
steps:
- name: Remove old PR labels
uses: mondeja/remove-labels-gh-action@v2.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
labels: |
kind/BUGFIX
kind/ENHANCEMENT
kind/FEATURE
kind/CHANGE
release-note-none
- name: Add label to PR
id: labeler
uses: jimschubert/labeler-action@v1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Adjust the configuration in /.github/labeler.yml
- name: Verify Label
id: preview_label_check
uses: docker://agilepathway/pull-request-label-checker:latest
with:
any_of: kind/BUGFIX,kind/ENHANCEMENT,kind/FEATURE,kind/CHANGE,release-note-none
repo_token: ${{ secrets.GITHUB_TOKEN }}
allow_failure: true
- name: Comment on Label Check Failure
uses: thollander/actions-comment-pull-request@v2.5.0
if: steps.preview_label_check.outputs.label_check == 'failure'
with:
message: |
PR body does not contain a valid type of change. Please refer to [CONTRIBUTING.md](https://github.com/prometheus/client_golang/blob/main/CONTRIBUTING.md#how-to-write-a-pr-description) for more information.
comment_tag: labelfailure
mode: recreate
- name: Remove Label Check Failure Comment
uses: thollander/actions-comment-pull-request@v2.5.0
if: steps.preview_label_check.outputs.label_check == 'success'
with:
message: |
PR body contains a valid type of change.
comment_tag: labelfailure
mode: delete
create_if_not_exists: false
- name: Exit on Failure
if: steps.preview_label_check.outputs.label_check == 'failure'
run: exit 1

View File

@ -20,55 +20,3 @@ Prometheus uses GitHub to manage reviews of pull requests.
Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style).
* Be sure to sign off on the [DCO](https://github.com/probot/dco#how-it-works) * Be sure to sign off on the [DCO](https://github.com/probot/dco#how-it-works)
## How to write a PR description
### Describe your PR
In this section, provide a clear and concise description of what your PR does. This helps reviewers understand the purpose and context of your changes.
### What type of PR is this?
Indicate the type of PR by adding one of the following options:
- `/kind fix`
- `/kind bugfix`
- `/kind enhancement`
- `/kind feature`
- `/kind feat`
- `/kind change`
- `/kind release-note-none`
If this change should not appear in the changelog, use `/kind release-note-none`.
Example 1:
```
### What type of PR is this?
/kind feature
```
Example 2:
```
### What type of PR is this?
/kind release-note-none
```
### Changelog Entry
Include a brief summary of your change for the changelog. This helps users understand what has been modified, added, or fixed in the project. If your change should not appear in the changelog, write `NONE`. Make sure to add only user-facing changes.
Example 1:
```
### Changelog Entry
```release-note
api: Switch to POST for `LabelNames`.
```
```
Example 2:
```
### Changelog Entry
```release-note
NONE
```
```

View File

@ -63,6 +63,7 @@ func withAllMetrics() []string {
"go_godebug_non_default_behavior_multipartmaxheaders_events_total", "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
"go_godebug_non_default_behavior_multipartmaxparts_events_total", "go_godebug_non_default_behavior_multipartmaxparts_events_total",
"go_godebug_non_default_behavior_multipathtcp_events_total", "go_godebug_non_default_behavior_multipathtcp_events_total",
"go_godebug_non_default_behavior_netedns0_events_total",
"go_godebug_non_default_behavior_panicnil_events_total", "go_godebug_non_default_behavior_panicnil_events_total",
"go_godebug_non_default_behavior_randautoseed_events_total", "go_godebug_non_default_behavior_randautoseed_events_total",
"go_godebug_non_default_behavior_tarinsecurepath_events_total", "go_godebug_non_default_behavior_tarinsecurepath_events_total",
@ -158,6 +159,7 @@ func withDebugMetrics() []string {
"go_godebug_non_default_behavior_multipartmaxheaders_events_total", "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
"go_godebug_non_default_behavior_multipartmaxparts_events_total", "go_godebug_non_default_behavior_multipartmaxparts_events_total",
"go_godebug_non_default_behavior_multipathtcp_events_total", "go_godebug_non_default_behavior_multipathtcp_events_total",
"go_godebug_non_default_behavior_netedns0_events_total",
"go_godebug_non_default_behavior_panicnil_events_total", "go_godebug_non_default_behavior_panicnil_events_total",
"go_godebug_non_default_behavior_randautoseed_events_total", "go_godebug_non_default_behavior_randautoseed_events_total",
"go_godebug_non_default_behavior_tarinsecurepath_events_total", "go_godebug_non_default_behavior_tarinsecurepath_events_total",

View File

@ -66,6 +66,7 @@ func withAllMetrics() []string {
"go_godebug_non_default_behavior_multipartmaxheaders_events_total", "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
"go_godebug_non_default_behavior_multipartmaxparts_events_total", "go_godebug_non_default_behavior_multipartmaxparts_events_total",
"go_godebug_non_default_behavior_multipathtcp_events_total", "go_godebug_non_default_behavior_multipathtcp_events_total",
"go_godebug_non_default_behavior_netedns0_events_total",
"go_godebug_non_default_behavior_panicnil_events_total", "go_godebug_non_default_behavior_panicnil_events_total",
"go_godebug_non_default_behavior_randautoseed_events_total", "go_godebug_non_default_behavior_randautoseed_events_total",
"go_godebug_non_default_behavior_tarinsecurepath_events_total", "go_godebug_non_default_behavior_tarinsecurepath_events_total",
@ -176,6 +177,7 @@ func withDebugMetrics() []string {
"go_godebug_non_default_behavior_multipartmaxheaders_events_total", "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
"go_godebug_non_default_behavior_multipartmaxparts_events_total", "go_godebug_non_default_behavior_multipartmaxparts_events_total",
"go_godebug_non_default_behavior_multipathtcp_events_total", "go_godebug_non_default_behavior_multipathtcp_events_total",
"go_godebug_non_default_behavior_netedns0_events_total",
"go_godebug_non_default_behavior_panicnil_events_total", "go_godebug_non_default_behavior_panicnil_events_total",
"go_godebug_non_default_behavior_randautoseed_events_total", "go_godebug_non_default_behavior_randautoseed_events_total",
"go_godebug_non_default_behavior_tarinsecurepath_events_total", "go_godebug_non_default_behavior_tarinsecurepath_events_total",

View File

@ -20,11 +20,12 @@ import (
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
"reflect"
"regexp" "regexp"
"sort" "sort"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
) )
@ -69,8 +70,8 @@ func TestWithGoCollectorMemStatsMetricsDisabled(t *testing.T) {
got = append(got, r.GetName()) got = append(got, r.GetName())
} }
if !reflect.DeepEqual(got, baseMetrics) { if diff := cmp.Diff(got, baseMetrics); diff != "" {
t.Errorf("got %v, want %v", got, baseMetrics) t.Errorf("missmatch (-want +got):\n%s", diff)
} }
} }
@ -127,8 +128,8 @@ func TestGoCollectorAllowList(t *testing.T) {
got = append(got, r.GetName()) got = append(got, r.GetName())
} }
if !reflect.DeepEqual(got, test.expected) { if diff := cmp.Diff(got, test.expected); diff != "" {
t.Errorf("got %v, want %v", got, test.expected) t.Errorf("missmatch (-want +got):\n%s", diff)
} }
}) })
} }
@ -181,8 +182,8 @@ func TestGoCollectorDenyList(t *testing.T) {
got = append(got, r.GetName()) got = append(got, r.GetName())
} }
if !reflect.DeepEqual(got, test.expected) { if diff := cmp.Diff(got, test.expected); diff != "" {
t.Errorf("got %v, want %v", got, test.expected) t.Errorf("missmatch (-want +got):\n%s", diff)
} }
}) })
} }

View File

@ -52,6 +52,7 @@ var expectedRuntimeMetrics = map[string]string{
"/godebug/non-default-behavior/multipartmaxheaders:events": "go_godebug_non_default_behavior_multipartmaxheaders_events_total", "/godebug/non-default-behavior/multipartmaxheaders:events": "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
"/godebug/non-default-behavior/multipartmaxparts:events": "go_godebug_non_default_behavior_multipartmaxparts_events_total", "/godebug/non-default-behavior/multipartmaxparts:events": "go_godebug_non_default_behavior_multipartmaxparts_events_total",
"/godebug/non-default-behavior/multipathtcp:events": "go_godebug_non_default_behavior_multipathtcp_events_total", "/godebug/non-default-behavior/multipathtcp:events": "go_godebug_non_default_behavior_multipathtcp_events_total",
"/godebug/non-default-behavior/netedns0:events": "go_godebug_non_default_behavior_netedns0_events_total",
"/godebug/non-default-behavior/panicnil:events": "go_godebug_non_default_behavior_panicnil_events_total", "/godebug/non-default-behavior/panicnil:events": "go_godebug_non_default_behavior_panicnil_events_total",
"/godebug/non-default-behavior/randautoseed:events": "go_godebug_non_default_behavior_randautoseed_events_total", "/godebug/non-default-behavior/randautoseed:events": "go_godebug_non_default_behavior_randautoseed_events_total",
"/godebug/non-default-behavior/tarinsecurepath:events": "go_godebug_non_default_behavior_tarinsecurepath_events_total", "/godebug/non-default-behavior/tarinsecurepath:events": "go_godebug_non_default_behavior_tarinsecurepath_events_total",
@ -79,4 +80,4 @@ var expectedRuntimeMetrics = map[string]string{
"/sync/mutex/wait/total:seconds": "go_sync_mutex_wait_total_seconds_total", "/sync/mutex/wait/total:seconds": "go_sync_mutex_wait_total_seconds_total",
} }
const expectedRuntimeMetricsCardinality = 114 const expectedRuntimeMetricsCardinality = 115

View File

@ -55,6 +55,7 @@ var expectedRuntimeMetrics = map[string]string{
"/godebug/non-default-behavior/multipartmaxheaders:events": "go_godebug_non_default_behavior_multipartmaxheaders_events_total", "/godebug/non-default-behavior/multipartmaxheaders:events": "go_godebug_non_default_behavior_multipartmaxheaders_events_total",
"/godebug/non-default-behavior/multipartmaxparts:events": "go_godebug_non_default_behavior_multipartmaxparts_events_total", "/godebug/non-default-behavior/multipartmaxparts:events": "go_godebug_non_default_behavior_multipartmaxparts_events_total",
"/godebug/non-default-behavior/multipathtcp:events": "go_godebug_non_default_behavior_multipathtcp_events_total", "/godebug/non-default-behavior/multipathtcp:events": "go_godebug_non_default_behavior_multipathtcp_events_total",
"/godebug/non-default-behavior/netedns0:events": "go_godebug_non_default_behavior_netedns0_events_total",
"/godebug/non-default-behavior/panicnil:events": "go_godebug_non_default_behavior_panicnil_events_total", "/godebug/non-default-behavior/panicnil:events": "go_godebug_non_default_behavior_panicnil_events_total",
"/godebug/non-default-behavior/randautoseed:events": "go_godebug_non_default_behavior_randautoseed_events_total", "/godebug/non-default-behavior/randautoseed:events": "go_godebug_non_default_behavior_randautoseed_events_total",
"/godebug/non-default-behavior/tarinsecurepath:events": "go_godebug_non_default_behavior_tarinsecurepath_events_total", "/godebug/non-default-behavior/tarinsecurepath:events": "go_godebug_non_default_behavior_tarinsecurepath_events_total",
@ -90,4 +91,4 @@ var expectedRuntimeMetrics = map[string]string{
"/sync/mutex/wait/total:seconds": "go_sync_mutex_wait_total_seconds_total", "/sync/mutex/wait/total:seconds": "go_sync_mutex_wait_total_seconds_total",
} }
const expectedRuntimeMetricsCardinality = 161 const expectedRuntimeMetricsCardinality = 162

View File

@ -22,14 +22,15 @@ import (
) )
type processCollector struct { type processCollector struct {
collectFn func(chan<- Metric) collectFn func(chan<- Metric)
pidFn func() (int, error) pidFn func() (int, error)
reportErrors bool reportErrors bool
cpuTotal *Desc cpuTotal *Desc
openFDs, maxFDs *Desc openFDs, maxFDs *Desc
vsize, maxVsize *Desc vsize, maxVsize *Desc
rss *Desc rss *Desc
startTime *Desc startTime *Desc
inBytes, outBytes *Desc
} }
// ProcessCollectorOpts defines the behavior of a process metrics collector // ProcessCollectorOpts defines the behavior of a process metrics collector
@ -100,6 +101,16 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
"Start time of the process since unix epoch in seconds.", "Start time of the process since unix epoch in seconds.",
nil, nil, nil, nil,
), ),
inBytes: NewDesc(
ns+"process_network_receive_bytes_total",
"Number of bytes received by the process over the network.",
nil, nil,
),
outBytes: NewDesc(
ns+"process_network_transmit_bytes_total",
"Number of bytes sent by the process over the network.",
nil, nil,
),
} }
if opts.PidFn == nil { if opts.PidFn == nil {

View File

@ -63,4 +63,18 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
} else { } else {
c.reportError(ch, nil, err) c.reportError(ch, nil, err)
} }
if netstat, err := p.Netstat(); err == nil {
var inOctets, outOctets float64
if netstat.IpExt.InOctets != nil {
inOctets = *netstat.IpExt.InOctets
}
if netstat.IpExt.OutOctets != nil {
outOctets = *netstat.IpExt.OutOctets
}
ch <- MustNewConstMetric(c.inBytes, CounterValue, inOctets)
ch <- MustNewConstMetric(c.outBytes, CounterValue, outOctets)
} else {
c.reportError(ch, nil, err)
}
} }

View File

@ -69,6 +69,8 @@ func TestProcessCollector(t *testing.T) {
regexp.MustCompile("\nprocess_virtual_memory_bytes [1-9]"), regexp.MustCompile("\nprocess_virtual_memory_bytes [1-9]"),
regexp.MustCompile("\nprocess_resident_memory_bytes [1-9]"), regexp.MustCompile("\nprocess_resident_memory_bytes [1-9]"),
regexp.MustCompile("\nprocess_start_time_seconds [0-9.]{10,}"), regexp.MustCompile("\nprocess_start_time_seconds [0-9.]{10,}"),
regexp.MustCompile("\nprocess_network_receive_bytes_total [0-9]+"),
regexp.MustCompile("\nprocess_network_transmit_bytes_total [0-9]+"),
regexp.MustCompile("\nfoobar_process_cpu_seconds_total [0-9]"), regexp.MustCompile("\nfoobar_process_cpu_seconds_total [0-9]"),
regexp.MustCompile("\nfoobar_process_max_fds [1-9]"), regexp.MustCompile("\nfoobar_process_max_fds [1-9]"),
regexp.MustCompile("\nfoobar_process_open_fds [1-9]"), regexp.MustCompile("\nfoobar_process_open_fds [1-9]"),
@ -76,6 +78,8 @@ func TestProcessCollector(t *testing.T) {
regexp.MustCompile("\nfoobar_process_virtual_memory_bytes [1-9]"), regexp.MustCompile("\nfoobar_process_virtual_memory_bytes [1-9]"),
regexp.MustCompile("\nfoobar_process_resident_memory_bytes [1-9]"), regexp.MustCompile("\nfoobar_process_resident_memory_bytes [1-9]"),
regexp.MustCompile("\nfoobar_process_start_time_seconds [0-9.]{10,}"), regexp.MustCompile("\nfoobar_process_start_time_seconds [0-9.]{10,}"),
regexp.MustCompile("\nfoobar_process_network_receive_bytes_total [0-9]+"),
regexp.MustCompile("\nfoobar_process_network_transmit_bytes_total [0-9]+"),
} { } {
if !re.Match(buf.Bytes()) { if !re.Match(buf.Bytes()) {
t.Errorf("want body to match %s\n%s", re, buf.String()) t.Errorf("want body to match %s\n%s", re, buf.String())