Skip to content

Commit 8e2160b

Browse files
committed
feat: Plumb in otel metrics
1 parent ecf5911 commit 8e2160b

File tree

5 files changed

+283
-14
lines changed

5 files changed

+283
-14
lines changed

cmd/cachewd/main.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ import (
1414
"github.com/alecthomas/chroma/v2/quick"
1515
"github.com/alecthomas/hcl/v2"
1616
"github.com/alecthomas/kong"
17+
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
18+
"go.opentelemetry.io/otel"
1719

1820
"github.com/block/cachew/internal/config"
1921
"github.com/block/cachew/internal/httputil"
2022
"github.com/block/cachew/internal/jobscheduler"
2123
"github.com/block/cachew/internal/logging"
24+
"github.com/block/cachew/internal/metrics"
2225
)
2326

2427
var cli struct {
@@ -62,11 +65,32 @@ func main() {
6265
err := config.Load(ctx, cli.Config, scheduler, mux, parseEnvars())
6366
kctx.FatalIfErrorf(err)
6467

68+
metricsClient, err := metrics.New(ctx)
69+
kctx.FatalIfErrorf(err, "failed to create metrics client")
70+
defer func() {
71+
if err := metricsClient.Close(); err != nil {
72+
logger.ErrorContext(ctx, "failed to close metrics client", "error", err)
73+
}
74+
}()
75+
76+
if err := metricsClient.ServeMetrics(ctx); err != nil {
77+
kctx.FatalIfErrorf(err, "failed to start metrics server")
78+
}
79+
6580
logger.InfoContext(ctx, "Starting cachewd", slog.String("bind", cli.Bind))
6681

82+
var handler http.Handler = mux
83+
84+
handler = otelhttp.NewMiddleware(metrics.ServiceName,
85+
otelhttp.WithMeterProvider(otel.GetMeterProvider()),
86+
otelhttp.WithTracerProvider(otel.GetTracerProvider()),
87+
)(handler)
88+
89+
handler = httputil.LoggingMiddleware(handler)
90+
6791
server := &http.Server{
6892
Addr: cli.Bind,
69-
Handler: httputil.LoggingMiddleware(mux),
93+
Handler: handler,
7094
ReadTimeout: 30 * time.Minute,
7195
WriteTimeout: 30 * time.Minute,
7296
ReadHeaderTimeout: 30 * time.Second,

go.mod

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,49 @@ require (
88
github.com/goproxy/goproxy v0.25.0
99
github.com/lmittmann/tint v1.1.2
1010
github.com/minio/minio-go/v7 v7.0.97
11+
github.com/prometheus/client_golang v1.23.2
1112
go.etcd.io/bbolt v1.4.3
13+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0
14+
go.opentelemetry.io/otel v1.39.0
15+
go.opentelemetry.io/otel/exporters/prometheus v0.51.0
16+
go.opentelemetry.io/otel/metric v1.39.0
17+
go.opentelemetry.io/otel/sdk v1.39.0
18+
go.opentelemetry.io/otel/sdk/metric v1.39.0
1219
)
1320

1421
require (
1522
github.com/aofei/backoff v1.1.0 // indirect
23+
github.com/beorn7/perks v1.0.1 // indirect
24+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1625
github.com/dlclark/regexp2 v1.11.5 // indirect
1726
github.com/dustin/go-humanize v1.0.1 // indirect
27+
github.com/felixge/httpsnoop v1.0.4 // indirect
1828
github.com/go-ini/ini v1.67.0 // indirect
29+
github.com/go-logr/logr v1.4.3 // indirect
30+
github.com/go-logr/stdr v1.2.2 // indirect
1931
github.com/google/uuid v1.6.0 // indirect
2032
github.com/hexops/gotextdiff v1.0.3 // indirect
2133
github.com/klauspost/compress v1.18.0 // indirect
2234
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
2335
github.com/klauspost/crc32 v1.3.0 // indirect
24-
github.com/kr/pretty v0.3.1 // indirect
2536
github.com/minio/crc64nvme v1.1.0 // indirect
2637
github.com/minio/md5-simd v1.1.2 // indirect
38+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
2739
github.com/philhofer/fwd v1.2.0 // indirect
28-
github.com/rogpeppe/go-internal v1.14.1 // indirect
40+
github.com/prometheus/client_model v0.6.2 // indirect
41+
github.com/prometheus/common v0.66.1 // indirect
42+
github.com/prometheus/procfs v0.16.1 // indirect
2943
github.com/rs/xid v1.6.0 // indirect
30-
github.com/stretchr/testify v1.11.1 // indirect
3144
github.com/tinylib/msgp v1.3.0 // indirect
45+
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
46+
go.opentelemetry.io/otel/trace v1.39.0 // indirect
47+
go.yaml.in/yaml/v2 v2.4.2 // indirect
3248
golang.org/x/crypto v0.44.0 // indirect
3349
golang.org/x/mod v0.31.0 // indirect
3450
golang.org/x/net v0.47.0 // indirect
35-
golang.org/x/sys v0.38.0 // indirect
51+
golang.org/x/sys v0.39.0 // indirect
3652
golang.org/x/text v0.32.0 // indirect
37-
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
53+
google.golang.org/protobuf v1.36.8 // indirect
3854
gopkg.in/yaml.v3 v3.0.1 // indirect
3955
)
4056

go.sum

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,27 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs
1414
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
1515
github.com/aofei/backoff v1.1.0 h1:7ey7Ydpx/eFIyyrBNKPbgvTzvIuUOHcwkR3gPjjY9ag=
1616
github.com/aofei/backoff v1.1.0/go.mod h1:IHCkMdd5vGP6dcDHD+uLn6lVuBw7+rKYaS7e7QIQwYA=
17-
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
17+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
18+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
19+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
20+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
1821
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1922
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2023
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
2124
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
2225
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
2326
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
27+
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
28+
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
2429
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
2530
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
31+
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
32+
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
33+
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
34+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
35+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
36+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
37+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
2638
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2739
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
2840
github.com/goproxy/goproxy v0.25.0 h1:TujZjUbKCwpFYrm+j04HACs1EAcBbFSGLwLMn8ynTys=
@@ -36,13 +48,12 @@ github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4O
3648
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
3749
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
3850
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
39-
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
4051
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
4152
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
42-
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
43-
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
4453
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
4554
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
55+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
56+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
4657
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
4758
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
4859
github.com/minio/crc64nvme v1.1.0 h1:e/tAguZ+4cw32D+IO/8GSf5UVr9y+3eJcxZI2WOO/7Q=
@@ -51,12 +62,20 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
5162
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
5263
github.com/minio/minio-go/v7 v7.0.97 h1:lqhREPyfgHTB/ciX8k2r8k0D93WaFqxbJX36UZq5occ=
5364
github.com/minio/minio-go/v7 v7.0.97/go.mod h1:re5VXuo0pwEtoNLsNuSr0RrLfT/MBtohwdaSmPPSRSk=
65+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
66+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
5467
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
5568
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
56-
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
5769
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5870
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
59-
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
71+
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
72+
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
73+
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
74+
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
75+
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
76+
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
77+
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
78+
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
6079
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
6180
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
6281
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@@ -67,6 +86,26 @@ github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
6786
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
6887
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
6988
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
89+
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
90+
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
91+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
92+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
93+
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
94+
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
95+
go.opentelemetry.io/otel/exporters/prometheus v0.51.0 h1:G7uexXb/K3T+T9fNLCCKncweEtNEBMTO+46hKX5EdKw=
96+
go.opentelemetry.io/otel/exporters/prometheus v0.51.0/go.mod h1:v0mFe5Kk7woIh938mrZBJBmENYquyA0IICrlYm4Y0t4=
97+
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
98+
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
99+
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
100+
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
101+
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
102+
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
103+
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
104+
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
105+
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
106+
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
107+
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
108+
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
70109
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
71110
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
72111
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
@@ -75,12 +114,14 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
75114
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
76115
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
77116
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
78-
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
79-
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
117+
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
118+
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
80119
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
81120
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
82121
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
83122
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
123+
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
124+
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
84125
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
85126
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
86127
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

internal/metrics/metrics.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package metrics
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"time"
8+
9+
"github.com/prometheus/client_golang/prometheus"
10+
"github.com/prometheus/client_golang/prometheus/promhttp"
11+
"go.opentelemetry.io/otel"
12+
"go.opentelemetry.io/otel/attribute"
13+
prometheusexporter "go.opentelemetry.io/otel/exporters/prometheus"
14+
"go.opentelemetry.io/otel/metric"
15+
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
16+
"go.opentelemetry.io/otel/sdk/resource"
17+
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
18+
19+
"github.com/block/cachew/internal/logging"
20+
)
21+
22+
const (
23+
ServiceName = "cachewd"
24+
MetricsPort = 9102
25+
)
26+
27+
// Client provides OpenTelemetry metrics with Prometheus exporter.
28+
type Client struct {
29+
provider metric.MeterProvider
30+
exporter *prometheusexporter.Exporter
31+
registry *prometheus.Registry
32+
}
33+
34+
// New creates a new OpenTelemetry metrics client with Prometheus exporter.
35+
func New(ctx context.Context) (*Client, error) {
36+
logger := logging.FromContext(ctx)
37+
38+
attrs := []attribute.KeyValue{
39+
semconv.ServiceName(ServiceName),
40+
}
41+
42+
res, err := resource.New(ctx,
43+
resource.WithAttributes(attrs...),
44+
resource.WithProcess(),
45+
resource.WithHost(),
46+
)
47+
if err != nil {
48+
return nil, fmt.Errorf("failed to create resource: %w", err)
49+
}
50+
51+
registry := prometheus.NewRegistry()
52+
53+
exporter, err := prometheusexporter.New(prometheusexporter.WithRegisterer(registry))
54+
if err != nil {
55+
return nil, fmt.Errorf("failed to create Prometheus exporter: %w", err)
56+
}
57+
58+
provider := sdkmetric.NewMeterProvider(
59+
sdkmetric.WithResource(res),
60+
sdkmetric.WithReader(exporter),
61+
)
62+
63+
otel.SetMeterProvider(provider)
64+
65+
client := &Client{
66+
provider: provider,
67+
exporter: exporter,
68+
registry: registry,
69+
}
70+
71+
logger.InfoContext(ctx, "OpenTelemetry metrics initialized with Prometheus exporter",
72+
"service", ServiceName,
73+
"port", MetricsPort,
74+
)
75+
76+
return client, nil
77+
}
78+
79+
// Close shuts down the meter provider.
80+
func (c *Client) Close() error {
81+
if c.provider == nil {
82+
return nil
83+
}
84+
if provider, ok := c.provider.(*sdkmetric.MeterProvider); ok {
85+
if err := provider.Shutdown(context.Background()); err != nil {
86+
return fmt.Errorf("failed to shutdown meter provider: %w", err)
87+
}
88+
}
89+
return nil
90+
}
91+
92+
// Handler returns the HTTP handler for the /metrics endpoint.
93+
func (c *Client) Handler() http.Handler {
94+
if c.registry == nil {
95+
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
96+
w.WriteHeader(http.StatusNotFound)
97+
})
98+
}
99+
return promhttp.HandlerFor(c.registry, promhttp.HandlerOpts{
100+
ErrorHandling: promhttp.ContinueOnError,
101+
})
102+
}
103+
104+
// ServeMetrics starts a dedicated HTTP server for Prometheus metrics scraping.
105+
func (c *Client) ServeMetrics(ctx context.Context) error {
106+
logger := logging.FromContext(ctx)
107+
108+
mux := http.NewServeMux()
109+
mux.Handle("/metrics", c.Handler())
110+
111+
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
112+
w.WriteHeader(http.StatusOK)
113+
if _, err := w.Write([]byte("OK")); err != nil {
114+
logger.ErrorContext(ctx, "failed to write health check response", "error", err)
115+
}
116+
})
117+
118+
server := &http.Server{
119+
Addr: fmt.Sprintf(":%d", MetricsPort),
120+
Handler: mux,
121+
ReadHeaderTimeout: 5 * time.Second,
122+
}
123+
124+
go func() {
125+
logger.InfoContext(ctx, "Starting metrics server", "port", MetricsPort)
126+
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
127+
logger.ErrorContext(ctx, "Metrics server error", "error", err)
128+
}
129+
}()
130+
131+
go func() {
132+
<-ctx.Done()
133+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
134+
defer cancel()
135+
if err := server.Shutdown(shutdownCtx); err != nil {
136+
logger.ErrorContext(shutdownCtx, "Metrics server shutdown error", "error", err)
137+
}
138+
}()
139+
140+
return nil
141+
}

0 commit comments

Comments
 (0)