Skip to content

Commit 52b20dc

Browse files
nssherpaclaude
andcommitted
feat: Add OTLP exporter support for OSS compatibility
This commit extends the metrics implementation to support both Prometheus and OTLP exporters, making it compatible with OSS environments while maintaining backward compatibility with existing EKS clusters that use Prometheus scrapers. Changes: - Add configurable metrics exporters (Prometheus, OTLP, or both) - Support for OTLP HTTP endpoint configuration - Maintain backward compatibility with Prometheus-only setup (default) - Add comprehensive tests for all exporter configurations - Allow both exporters to run simultaneously Configuration options: - EnablePrometheus (default: true) - Enable Prometheus exporter - EnableOTLP (default: false) - Enable OTLP exporter - OTLPEndpoint - OTLP collector endpoint - OTLPInsecure - Use insecure connection for OTLP - OTLPExportInterval - Export interval in seconds Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 1f86f57 commit 52b20dc

4 files changed

Lines changed: 207 additions & 61 deletions

File tree

go.mod

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@ require (
1111
github.com/prometheus/client_golang v1.23.2
1212
go.etcd.io/bbolt v1.4.3
1313
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0
14-
go.opentelemetry.io/otel v1.39.0
14+
go.opentelemetry.io/otel v1.40.0
15+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0
1516
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
19-
golang.org/x/mod v0.31.0
17+
go.opentelemetry.io/otel/metric v1.40.0
18+
go.opentelemetry.io/otel/sdk v1.40.0
19+
go.opentelemetry.io/otel/sdk/metric v1.40.0
2020
)
2121

2222
require (
2323
github.com/aofei/backoff v1.1.0 // indirect
2424
github.com/beorn7/perks v1.0.1 // indirect
25+
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
2526
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2627
github.com/dlclark/regexp2 v1.11.5 // indirect
2728
github.com/dustin/go-humanize v1.0.1 // indirect
@@ -30,6 +31,7 @@ require (
3031
github.com/go-logr/logr v1.4.3 // indirect
3132
github.com/go-logr/stdr v1.2.2 // indirect
3233
github.com/google/uuid v1.6.0 // indirect
34+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
3335
github.com/hexops/gotextdiff v1.0.3 // indirect
3436
github.com/klauspost/compress v1.18.0 // indirect
3537
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
@@ -44,14 +46,18 @@ require (
4446
github.com/rs/xid v1.6.0 // indirect
4547
github.com/tinylib/msgp v1.3.0 // indirect
4648
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
47-
go.opentelemetry.io/otel/trace v1.39.0 // indirect
49+
go.opentelemetry.io/otel/trace v1.40.0 // indirect
50+
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
4851
go.yaml.in/yaml/v2 v2.4.2 // indirect
49-
golang.org/x/crypto v0.44.0 // indirect
52+
golang.org/x/crypto v0.47.0 // indirect
5053
golang.org/x/mod v0.31.0 // indirect
51-
golang.org/x/net v0.47.0 // indirect
52-
golang.org/x/sys v0.39.0 // indirect
53-
golang.org/x/text v0.32.0 // indirect
54-
google.golang.org/protobuf v1.36.8 // indirect
54+
golang.org/x/net v0.49.0 // indirect
55+
golang.org/x/sys v0.40.0 // indirect
56+
golang.org/x/text v0.33.0 // indirect
57+
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
58+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
59+
google.golang.org/grpc v1.78.0 // indirect
60+
google.golang.org/protobuf v1.36.11 // indirect
5561
gopkg.in/yaml.v3 v3.0.1 // indirect
5662
)
5763

go.sum

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ github.com/aofei/backoff v1.1.0 h1:7ey7Ydpx/eFIyyrBNKPbgvTzvIuUOHcwkR3gPjjY9ag=
1616
github.com/aofei/backoff v1.1.0/go.mod h1:IHCkMdd5vGP6dcDHD+uLn6lVuBw7+rKYaS7e7QIQwYA=
1717
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
1818
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
19+
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
20+
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
1921
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
2022
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
2123
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -33,12 +35,16 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
3335
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
3436
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
3537
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
38+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
39+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
3640
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
3741
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
3842
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
3943
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
4044
github.com/goproxy/goproxy v0.25.0 h1:TujZjUbKCwpFYrm+j04HACs1EAcBbFSGLwLMn8ynTys=
4145
github.com/goproxy/goproxy v0.25.0/go.mod h1:6RIssMPDpQ0IHZus17gPUyBtU62RoqblQDYWx2sz/qs=
46+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
47+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
4248
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
4349
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
4450
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -90,38 +96,50 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
9096
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
9197
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
9298
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=
99+
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
100+
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
101+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE=
102+
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE=
95103
go.opentelemetry.io/otel/exporters/prometheus v0.51.0 h1:G7uexXb/K3T+T9fNLCCKncweEtNEBMTO+46hKX5EdKw=
96104
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.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
106+
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
107+
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
108+
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
109+
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
110+
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
111+
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
112+
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
113+
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
114+
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
105115
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
106116
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
107117
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
108118
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
109-
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
110-
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
119+
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
120+
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
111121
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
112122
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
113-
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
114-
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
123+
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
124+
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
115125
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
116126
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
117-
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
118-
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
119-
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
120-
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
121-
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
122-
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=
127+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
128+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
129+
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
130+
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
131+
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
132+
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
133+
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
134+
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
135+
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
136+
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
137+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
138+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
139+
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
140+
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
141+
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
142+
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
125143
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
126144
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
127145
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

internal/metrics/metrics.go

Lines changed: 82 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package metrics
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"net/http"
78
"time"
@@ -10,6 +11,7 @@ import (
1011
"github.com/prometheus/client_golang/prometheus/promhttp"
1112
"go.opentelemetry.io/otel"
1213
"go.opentelemetry.io/otel/attribute"
14+
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
1315
prometheusexporter "go.opentelemetry.io/otel/exporters/prometheus"
1416
"go.opentelemetry.io/otel/metric"
1517
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
@@ -21,23 +23,34 @@ import (
2123

2224
// Config holds metrics configuration.
2325
type Config struct {
24-
ServiceName string `help:"Service name for metrics." default:"cachew"`
25-
Port int `help:"Port for metrics server." default:"9102"`
26+
ServiceName string `help:"Service name for metrics." default:"cachew"`
27+
Port int `help:"Port for Prometheus metrics server." default:"9102"`
28+
EnablePrometheus bool `help:"Enable Prometheus exporter." default:"true"`
29+
EnableOTLP bool `help:"Enable OTLP exporter." default:"false"`
30+
OTLPEndpoint string `help:"OTLP endpoint URL." default:"http://localhost:4318"`
31+
OTLPInsecure bool `help:"Use insecure connection for OTLP." default:"false"`
32+
OTLPExportInterval int `help:"OTLP export interval in seconds." default:"60"`
2633
}
2734

28-
// Client provides OpenTelemetry metrics with Prometheus exporter.
35+
// Client provides OpenTelemetry metrics with configurable exporters.
2936
type Client struct {
30-
provider metric.MeterProvider
31-
exporter *prometheusexporter.Exporter
32-
registry *prometheus.Registry
33-
serviceName string
34-
port int
37+
provider metric.MeterProvider
38+
prometheusEnabled bool
39+
exporter *prometheusexporter.Exporter
40+
registry *prometheus.Registry
41+
serviceName string
42+
port int
3543
}
3644

37-
// New creates a new OpenTelemetry metrics client with Prometheus exporter.
45+
// New creates a new OpenTelemetry metrics client with configurable exporters.
3846
func New(ctx context.Context, cfg Config) (*Client, error) {
3947
logger := logging.FromContext(ctx)
4048

49+
// Validate that at least one exporter is enabled
50+
if !cfg.EnablePrometheus && !cfg.EnableOTLP {
51+
return nil, errors.New("at least one exporter (Prometheus or OTLP) must be enabled")
52+
}
53+
4154
attrs := []attribute.KeyValue{
4255
semconv.ServiceName(cfg.ServiceName),
4356
}
@@ -51,31 +64,70 @@ func New(ctx context.Context, cfg Config) (*Client, error) {
5164
return nil, fmt.Errorf("failed to create resource: %w", err)
5265
}
5366

54-
registry := prometheus.NewRegistry()
67+
var readers []sdkmetric.Reader
68+
var registry *prometheus.Registry
69+
var prometheusExporter *prometheusexporter.Exporter
70+
exporters := []string{}
71+
72+
// Configure Prometheus exporter if enabled
73+
if cfg.EnablePrometheus {
74+
registry = prometheus.NewRegistry()
75+
prometheusExporter, err = prometheusexporter.New(prometheusexporter.WithRegisterer(registry))
76+
if err != nil {
77+
return nil, fmt.Errorf("failed to create Prometheus exporter: %w", err)
78+
}
79+
readers = append(readers, prometheusExporter)
80+
exporters = append(exporters, "prometheus")
81+
}
82+
83+
// Configure OTLP exporter if enabled
84+
if cfg.EnableOTLP {
85+
opts := []otlpmetrichttp.Option{
86+
otlpmetrichttp.WithEndpointURL(cfg.OTLPEndpoint),
87+
}
88+
if cfg.OTLPInsecure {
89+
opts = append(opts, otlpmetrichttp.WithInsecure())
90+
}
5591

56-
exporter, err := prometheusexporter.New(prometheusexporter.WithRegisterer(registry))
57-
if err != nil {
58-
return nil, fmt.Errorf("failed to create Prometheus exporter: %w", err)
92+
otlpExporter, err := otlpmetrichttp.New(ctx, opts...)
93+
if err != nil {
94+
return nil, fmt.Errorf("failed to create OTLP exporter: %w", err)
95+
}
96+
97+
// Create periodic reader for OTLP
98+
reader := sdkmetric.NewPeriodicReader(
99+
otlpExporter,
100+
sdkmetric.WithInterval(time.Duration(cfg.OTLPExportInterval)*time.Second),
101+
)
102+
readers = append(readers, reader)
103+
exporters = append(exporters, "otlp")
59104
}
60105

61-
provider := sdkmetric.NewMeterProvider(
106+
// Create meter provider with all configured readers
107+
providerOpts := []sdkmetric.Option{
62108
sdkmetric.WithResource(res),
63-
sdkmetric.WithReader(exporter),
64-
)
109+
}
110+
for _, reader := range readers {
111+
providerOpts = append(providerOpts, sdkmetric.WithReader(reader))
112+
}
65113

114+
provider := sdkmetric.NewMeterProvider(providerOpts...)
66115
otel.SetMeterProvider(provider)
67116

68117
client := &Client{
69-
provider: provider,
70-
exporter: exporter,
71-
registry: registry,
72-
serviceName: cfg.ServiceName,
73-
port: cfg.Port,
118+
provider: provider,
119+
prometheusEnabled: cfg.EnablePrometheus,
120+
exporter: prometheusExporter,
121+
registry: registry,
122+
serviceName: cfg.ServiceName,
123+
port: cfg.Port,
74124
}
75125

76-
logger.InfoContext(ctx, "OpenTelemetry metrics initialized with Prometheus exporter",
126+
logger.InfoContext(ctx, "OpenTelemetry metrics initialized",
77127
"service", cfg.ServiceName,
78-
"port", cfg.Port,
128+
"exporters", exporters,
129+
"prometheus_port", cfg.Port,
130+
"otlp_endpoint", cfg.OTLPEndpoint,
79131
)
80132

81133
return client, nil
@@ -107,7 +159,13 @@ func (c *Client) Handler() http.Handler {
107159
}
108160

109161
// ServeMetrics starts a dedicated HTTP server for Prometheus metrics scraping.
162+
// This is only started if Prometheus exporter is enabled.
110163
func (c *Client) ServeMetrics(ctx context.Context) error {
164+
// Only start metrics server if Prometheus is enabled
165+
if !c.prometheusEnabled {
166+
return nil
167+
}
168+
111169
logger := logging.FromContext(ctx)
112170

113171
mux := http.NewServeMux()
@@ -127,7 +185,7 @@ func (c *Client) ServeMetrics(ctx context.Context) error {
127185
}
128186

129187
go func() {
130-
logger.InfoContext(ctx, "Starting metrics server", "port", c.port)
188+
logger.InfoContext(ctx, "Starting Prometheus metrics server", "port", c.port)
131189
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
132190
logger.ErrorContext(ctx, "Metrics server error", "error", err)
133191
}

0 commit comments

Comments
 (0)