Skip to content

Commit ee86b4a

Browse files
authored
Add http debug listener (#57)
This adds a debugAddr config, with the address to listen for metrics and pprof. This code is modelled after Boulder's newStatsRegistry. This just registers basic go stats as well as the promhttp stats. We want to add more stats in the future, but this seemed like a good starting point. The integration test just check that metrics is reachable and has an expected substring. Part of #51
1 parent 37de24b commit ee86b4a

13 files changed

Lines changed: 196 additions & 38 deletions

File tree

config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ type Config struct {
8080
// ListenAddr for the demo site to listen on. Eg, ":443".
8181
ListenAddr string
8282

83+
// DebugAddr is the listen address for metrics and pprof
84+
DebugAddr string
85+
8386
// Sites is a list of sites to host.
8487
Sites []Site
8588

config/config_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func TestLoadConfig(t *testing.T) {
1414
t.Parallel()
1515
expected := config.Config{
1616
ListenAddr: "localhost:8443",
17+
DebugAddr: "localhost:9876",
1718

1819
Sites: []config.Site{
1920
{

config/testdata/test.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"listenAddr": "localhost:8443",
3+
"debugAddr": "localhost:9876",
34

45
"sites": [
56
{

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ services:
1111
- pebble
1212
ports:
1313
- "5001:5001"
14+
- "9001:9001"
1415
networks:
1516
testnet:
1617
aliases:

go.mod

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,27 @@ go 1.25.0
44

55
require (
66
github.com/go-acme/lego/v4 v4.33.0
7-
golang.org/x/crypto/x509roots/fallback v0.0.0-20260311141749-982eaa62dfb7
7+
github.com/prometheus/client_golang v1.23.2
8+
golang.org/x/crypto/x509roots/fallback v0.0.0-20260323153451-8400f4a93807
89
)
910

1011
require (
12+
github.com/beorn7/perks v1.0.1 // indirect
1113
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
12-
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
14+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
15+
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
1316
github.com/miekg/dns v1.1.72 // indirect
14-
golang.org/x/crypto v0.48.0 // indirect
15-
golang.org/x/mod v0.32.0 // indirect
16-
golang.org/x/net v0.50.0 // indirect
17-
golang.org/x/sync v0.19.0 // indirect
18-
golang.org/x/sys v0.41.0 // indirect
19-
golang.org/x/text v0.34.0 // indirect
20-
golang.org/x/tools v0.41.0 // indirect
17+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
18+
github.com/prometheus/client_model v0.6.2 // indirect
19+
github.com/prometheus/common v0.67.5 // indirect
20+
github.com/prometheus/procfs v0.20.1 // indirect
21+
go.yaml.in/yaml/v2 v2.4.4 // indirect
22+
golang.org/x/crypto v0.49.0 // indirect
23+
golang.org/x/mod v0.34.0 // indirect
24+
golang.org/x/net v0.52.0 // indirect
25+
golang.org/x/sync v0.20.0 // indirect
26+
golang.org/x/sys v0.42.0 // indirect
27+
golang.org/x/text v0.35.0 // indirect
28+
golang.org/x/tools v0.43.0 // indirect
29+
google.golang.org/protobuf v1.36.11 // indirect
2130
)

go.sum

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,58 @@
1+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
13
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
24
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
5+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
6+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
37
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
48
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
59
github.com/go-acme/lego/v4 v4.33.0 h1:2KrRKieG+VczT4zvVXKAY7Tp/S3XLvh/QImofBALRAM=
610
github.com/go-acme/lego/v4 v4.33.0/go.mod h1:lI2fZNdgeM/ymf9xQ9YKbgZm6MeDuf91UrohMQE4DhI=
7-
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
8-
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
11+
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
12+
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
913
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
1014
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
15+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
16+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
17+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
18+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
1119
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
1220
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
21+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
22+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
1323
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
1424
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
25+
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
26+
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
27+
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
28+
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
29+
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
30+
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
31+
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
32+
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
1533
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
1634
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
17-
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
18-
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
19-
golang.org/x/crypto/x509roots/fallback v0.0.0-20260311141749-982eaa62dfb7 h1:uX5F+sUnmp3J14eF1J92KuF4wi4GP4lnnEbFfwuNVVU=
20-
golang.org/x/crypto/x509roots/fallback v0.0.0-20260311141749-982eaa62dfb7/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs=
21-
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
22-
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
23-
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
24-
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
25-
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
26-
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
27-
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
28-
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
29-
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
30-
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
31-
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
32-
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
35+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
36+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
37+
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
38+
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
39+
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
40+
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
41+
golang.org/x/crypto/x509roots/fallback v0.0.0-20260323153451-8400f4a93807 h1:sQVhWLXbNsa8CTzHOX3IHc7C4Q2JyxI5AweuMQZ/5H0=
42+
golang.org/x/crypto/x509roots/fallback v0.0.0-20260323153451-8400f4a93807/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs=
43+
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
44+
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
45+
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
46+
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
47+
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
48+
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
49+
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
50+
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
51+
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
52+
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
53+
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
54+
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
55+
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
56+
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
3357
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
3458
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

integration/integration_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,40 @@ func waitRenew(root []byte) error {
174174
}
175175
}
176176

177+
// checkMetrics verifies the debug server's /metrics endpoint is reachable
178+
// and exposes Go runtime metrics.
179+
func checkMetrics() error {
180+
resp, err := http.Get("http://localhost:9001/metrics")
181+
if err != nil {
182+
return err
183+
}
184+
defer resp.Body.Close()
185+
186+
if resp.StatusCode != http.StatusOK {
187+
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
188+
}
189+
190+
body, err := io.ReadAll(resp.Body)
191+
if err != nil {
192+
return err
193+
}
194+
195+
if !bytes.Contains(body, []byte("go_goroutines")) {
196+
return fmt.Errorf("metrics body missing go_goroutines: %s", string(body))
197+
}
198+
199+
return nil
200+
}
201+
177202
// TestIntegration verifies that test-certs-site is working properly.
178203
// It assumes that test-certs-site and pebble are running with the configurations
179204
// in this directory.
180205
func TestPebbleIntegration(t *testing.T) {
206+
err := checkMetrics()
207+
if err != nil {
208+
t.Fatal(err)
209+
}
210+
181211
root, err := getPebbleRoot()
182212
if err != nil {
183213
t.Fatal(err)

integration/test-certs-site-config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"listenAddr": ":5001",
3+
"debugAddr": ":9001",
34
"sites": [
45
{
56
"issuerCN": "Pebble Root CA",

main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/letsencrypt/test-certs-site/config"
1616
"github.com/letsencrypt/test-certs-site/scheduler"
1717
"github.com/letsencrypt/test-certs-site/server"
18+
"github.com/letsencrypt/test-certs-site/stats"
1819
"github.com/letsencrypt/test-certs-site/storage"
1920

2021
_ "golang.org/x/crypto/x509roots/fallback" // Include fallback roots for talking to ACME server
@@ -60,14 +61,16 @@ func run(args []string) error {
6061
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
6162
defer cancel()
6263

64+
registry := stats.New(ctx, cfg.DebugAddr)
65+
6366
schedule := scheduler.New(ctx)
6467

6568
err = acme.New(cfg, store, schedule, certManager)
6669
if err != nil {
6770
return err
6871
}
6972

70-
return server.Run(ctx, cfg, certManager.GetCertificate)
73+
return server.Run(ctx, cfg, registry, certManager.GetCertificate)
7174
}
7275

7376
func main() {

server/page.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import (
99
"os"
1010
"strings"
1111

12+
"github.com/prometheus/client_golang/prometheus"
13+
"github.com/prometheus/client_golang/prometheus/promauto"
14+
"github.com/prometheus/client_golang/prometheus/promhttp"
15+
1216
"github.com/letsencrypt/test-certs-site/config"
1317
)
1418

@@ -29,7 +33,7 @@ type info struct {
2933
State string
3034
}
3135

32-
func newHandler(cfg *config.Config) (handler, error) {
36+
func newHandler(cfg *config.Config, registry prometheus.Registerer) (http.HandlerFunc, error) {
3337
domains := make(map[string]info)
3438

3539
for _, site := range cfg.Sites {
@@ -49,19 +53,27 @@ func newHandler(cfg *config.Config) (handler, error) {
4953

5054
html, err := loadTemplate(cfg.HTMLTemplate, htmlTemplate)
5155
if err != nil {
52-
return handler{}, err
56+
return nil, err
5357
}
5458

5559
text, err := loadTemplate(cfg.TextTemplate, textTemplate)
5660
if err != nil {
57-
return handler{}, err
61+
return nil, err
5862
}
5963

60-
return handler{
64+
counter := promauto.With(registry).NewCounterVec(
65+
prometheus.CounterOpts{
66+
Name: "http_requests_total",
67+
Help: "HTTP Requests by status",
68+
},
69+
[]string{"code"},
70+
)
71+
72+
return promhttp.InstrumentHandlerCounter(counter, handler{
6173
htmlTemplate: html,
6274
textTemplate: text,
6375
domains: domains,
64-
}, nil
76+
}), nil
6577
}
6678

6779
func loadTemplate(filename string, defaultTemplate string) (*template.Template, error) {

0 commit comments

Comments
 (0)