Skip to content

Commit a468004

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

File tree

6 files changed

+299
-14
lines changed

6 files changed

+299
-14
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44

55
block-test.hcl
66
/*-source/
7+
8+
# Binaries
9+
/cachew
10+
/cachewd

cmd/cachewd/main.go

Lines changed: 26 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 {
@@ -28,6 +31,7 @@ var cli struct {
2831
Bind string `hcl:"bind" default:"127.0.0.1:8080" help:"Bind address for the server."`
2932
SchedulerConfig jobscheduler.Config `embed:"" prefix:"scheduler-"`
3033
LoggingConfig logging.Config `embed:"" prefix:"log-"`
34+
MetricsConfig metrics.Config `embed:"" prefix:"metrics-"`
3135
}
3236

3337
func main() {
@@ -62,11 +66,32 @@ func main() {
6266
err := config.Load(ctx, cli.Config, scheduler, mux, parseEnvars())
6367
kctx.FatalIfErrorf(err)
6468

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

83+
var handler http.Handler = mux
84+
85+
handler = otelhttp.NewMiddleware(cli.MetricsConfig.ServiceName,
86+
otelhttp.WithMeterProvider(otel.GetMeterProvider()),
87+
otelhttp.WithTracerProvider(otel.GetTracerProvider()),
88+
)(handler)
89+
90+
handler = httputil.LoggingMiddleware(handler)
91+
6792
server := &http.Server{
6893
Addr: cli.Bind,
69-
Handler: httputil.LoggingMiddleware(mux),
94+
Handler: handler,
7095
ReadTimeout: 30 * time.Minute,
7196
WriteTimeout: 30 * time.Minute,
7297
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: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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+
// Config holds metrics configuration.
23+
type Config struct {
24+
ServiceName string `help:"Service name for metrics." default:"cachew"`
25+
Port int `help:"Port for metrics server." default:"9102"`
26+
}
27+
28+
// Client provides OpenTelemetry metrics with Prometheus exporter.
29+
type Client struct {
30+
provider metric.MeterProvider
31+
exporter *prometheusexporter.Exporter
32+
registry *prometheus.Registry
33+
serviceName string
34+
port int
35+
}
36+
37+
// New creates a new OpenTelemetry metrics client with Prometheus exporter.
38+
func New(ctx context.Context, cfg Config) (*Client, error) {
39+
logger := logging.FromContext(ctx)
40+
41+
attrs := []attribute.KeyValue{
42+
semconv.ServiceName(cfg.ServiceName),
43+
}
44+
45+
res, err := resource.New(ctx,
46+
resource.WithAttributes(attrs...),
47+
resource.WithProcess(),
48+
resource.WithHost(),
49+
)
50+
if err != nil {
51+
return nil, fmt.Errorf("failed to create resource: %w", err)
52+
}
53+
54+
registry := prometheus.NewRegistry()
55+
56+
exporter, err := prometheusexporter.New(prometheusexporter.WithRegisterer(registry))
57+
if err != nil {
58+
return nil, fmt.Errorf("failed to create Prometheus exporter: %w", err)
59+
}
60+
61+
provider := sdkmetric.NewMeterProvider(
62+
sdkmetric.WithResource(res),
63+
sdkmetric.WithReader(exporter),
64+
)
65+
66+
otel.SetMeterProvider(provider)
67+
68+
client := &Client{
69+
provider: provider,
70+
exporter: exporter,
71+
registry: registry,
72+
serviceName: cfg.ServiceName,
73+
port: cfg.Port,
74+
}
75+
76+
logger.InfoContext(ctx, "OpenTelemetry metrics initialized with Prometheus exporter",
77+
"service", cfg.ServiceName,
78+
"port", cfg.Port,
79+
)
80+
81+
return client, nil
82+
}
83+
84+
// Close shuts down the meter provider.
85+
func (c *Client) Close() error {
86+
if c.provider == nil {
87+
return nil
88+
}
89+
if provider, ok := c.provider.(*sdkmetric.MeterProvider); ok {
90+
if err := provider.Shutdown(context.Background()); err != nil {
91+
return fmt.Errorf("failed to shutdown meter provider: %w", err)
92+
}
93+
}
94+
return nil
95+
}
96+
97+
// Handler returns the HTTP handler for the /metrics endpoint.
98+
func (c *Client) Handler() http.Handler {
99+
if c.registry == nil {
100+
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
101+
w.WriteHeader(http.StatusNotFound)
102+
})
103+
}
104+
return promhttp.HandlerFor(c.registry, promhttp.HandlerOpts{
105+
ErrorHandling: promhttp.ContinueOnError,
106+
})
107+
}
108+
109+
// ServeMetrics starts a dedicated HTTP server for Prometheus metrics scraping.
110+
func (c *Client) ServeMetrics(ctx context.Context) error {
111+
logger := logging.FromContext(ctx)
112+
113+
mux := http.NewServeMux()
114+
mux.Handle("/metrics", c.Handler())
115+
116+
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
117+
w.WriteHeader(http.StatusOK)
118+
if _, err := w.Write([]byte("OK")); err != nil {
119+
logger.ErrorContext(ctx, "failed to write health check response", "error", err)
120+
}
121+
})
122+
123+
server := &http.Server{
124+
Addr: fmt.Sprintf(":%d", c.port),
125+
Handler: mux,
126+
ReadHeaderTimeout: 5 * time.Second,
127+
}
128+
129+
go func() {
130+
logger.InfoContext(ctx, "Starting metrics server", "port", c.port)
131+
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
132+
logger.ErrorContext(ctx, "Metrics server error", "error", err)
133+
}
134+
}()
135+
136+
go func() {
137+
<-ctx.Done()
138+
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
139+
defer cancel()
140+
if err := server.Shutdown(shutdownCtx); err != nil {
141+
logger.ErrorContext(shutdownCtx, "Metrics server shutdown error", "error", err)
142+
}
143+
}()
144+
145+
return nil
146+
}

0 commit comments

Comments
 (0)