Skip to content

Commit 2e386e1

Browse files
committed
feat!: upgrade mailgun-go from v3 to v5
BREAKING CHANGE: MG_DOMAIN environment variable is no longer required. The exporter now uses the new ListMetrics API instead of the deprecated GetStats API. All Prometheus metrics remain unchanged.
1 parent 9ff8803 commit 2e386e1

File tree

4 files changed

+57
-61
lines changed

4 files changed

+57
-61
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ require (
44
github.com/alecthomas/kingpin/v2 v2.4.0
55
github.com/mailgun/mailgun-go/v5 v5.9.0
66
github.com/prometheus/client_golang v1.23.2
7+
github.com/prometheus/common v0.67.4
8+
github.com/rs/zerolog v1.34.0
79
github.com/stretchr/testify v1.11.1
810
)
911

@@ -16,13 +18,14 @@ require (
1618
github.com/json-iterator/go v1.1.12 // indirect
1719
github.com/kr/text v0.2.0 // indirect
1820
github.com/mailgun/errors v0.4.0 // indirect
21+
github.com/mattn/go-colorable v0.1.13 // indirect
22+
github.com/mattn/go-isatty v0.0.20 // indirect
1923
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2024
github.com/modern-go/reflect2 v1.0.2 // indirect
2125
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
2226
github.com/oapi-codegen/runtime v1.1.2 // indirect
2327
github.com/pmezard/go-difflib v1.0.0 // indirect
2428
github.com/prometheus/client_model v0.6.2 // indirect
25-
github.com/prometheus/common v0.67.4 // indirect
2629
github.com/prometheus/procfs v0.16.1 // indirect
2730
github.com/stretchr/objx v0.5.2 // indirect
2831
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect

go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,39 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
66
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
77
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
88
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
9+
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
910
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
1011
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1112
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
1213
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1314
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
1415
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
16+
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
1517
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
1618
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
1719
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
1820
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
1921
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
2022
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
2123
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
24+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
25+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
2226
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
2327
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
2428
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2529
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
30+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
31+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
2632
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
2733
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
2834
github.com/mailgun/mailgun-go/v5 v5.9.0 h1:3nLFNATUjrcDDe4sCuPmhM+lEJcaDCoSUXAvkcVt+0c=
2935
github.com/mailgun/mailgun-go/v5 v5.9.0/go.mod h1:qNTXXuJi9/myqpDLI8Mbn54WCXdto1kEHm6I2/WWYQQ=
36+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
37+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
38+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
39+
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
40+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
41+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
3042
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
3143
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
3244
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -36,6 +48,7 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
3648
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
3749
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
3850
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
51+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
3952
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4053
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4154
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
@@ -48,6 +61,9 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
4861
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
4962
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
5063
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
64+
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
65+
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
66+
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
5167
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
5268
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
5369
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -68,6 +84,9 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
6884
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
6985
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
7086
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
87+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
88+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
89+
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
7190
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
7291
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
7392
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=

main.go

Lines changed: 33 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,16 @@ func prometheusDomainStatsTypeDesc(metric string, help string) *prometheus.Desc
6464

6565
// NewExporter returns an initialized exporter.
6666
func NewExporter() *Exporter {
67-
// NewMailgunFromEnv requires MG_DOMAIN to get set, even though we don't need it for listing all domains
68-
err := os.Setenv("MG_DOMAIN", "dummy")
69-
if err != nil {
70-
log.Fatal().Err(err).Msgf("%v", err)
67+
apiKey := os.Getenv("MG_API_KEY")
68+
if apiKey == "" {
69+
log.Fatal().Msg("MG_API_KEY environment variable is required")
7170
}
7271

73-
mg, err := mailgun.NewMailgunFromEnv()
74-
APIBase, exists := os.LookupEnv("API_BASE")
75-
if exists {
76-
mg.SetAPIBase(APIBase)
77-
}
78-
if err != nil {
79-
log.Fatal().Err(err).Msgf("%v", err)
72+
mg := mailgun.NewMailgun(apiKey)
73+
if apiBase, exists := os.LookupEnv("API_BASE"); exists {
74+
if err := mg.SetAPIBase(apiBase); err != nil {
75+
log.Fatal().Err(err).Msgf("Failed to set API base: %v", err)
76+
}
8077
}
8178

8279
return NewExporterWithClient(NewMailgunClientWrapper(mg))
@@ -187,118 +184,94 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
187184

188185
metrics, err := e.mg.GetMetrics(ctx, domain)
189186
if err != nil {
190-
log.Error().Err(err)
187+
log.Error().Err(err).Msgf("Failed to get metrics for domain %s", domain)
188+
continue
191189
}
192190

193-
var acceptedTotalIncoming = float64(0)
194-
var acceptedTotalOutgoing = float64(0)
195-
var clickedTotal = float64(0)
196-
var complainedTotal = float64(0)
197-
var deliveredHttpTotal = float64(0)
198-
var deliveredSmtpTotal = float64(0)
199-
var failedPermanentBounce = float64(0)
200-
var failedPermanentDelayedBounce = float64(0)
201-
var failedPermanentSuppressBounce = float64(0)
202-
var failedPermanentSuppressComplaint = float64(0)
203-
var failedPermanentSuppressUnsubscribe = float64(0)
204-
var failedTemporaryEspblock = float64(0)
205-
var openedTotal = float64(0)
206-
var storedTotal = float64(0)
207-
var unsubscribedTotal = float64(0)
208-
209-
for _, stat := range stats {
210-
acceptedTotalIncoming += float64(stat.Accepted.Incoming)
211-
acceptedTotalOutgoing += float64(stat.Accepted.Outgoing)
212-
clickedTotal += float64(stat.Clicked.Total)
213-
complainedTotal += float64(stat.Complained.Total)
214-
complainedTotal += float64(stat.Complained.Total)
215-
deliveredHttpTotal += float64(stat.Delivered.Http)
216-
deliveredSmtpTotal += float64(stat.Delivered.Smtp)
217-
failedPermanentBounce += float64(stat.Failed.Permanent.Bounce)
218-
failedPermanentDelayedBounce += float64(stat.Failed.Permanent.DelayedBounce)
219-
failedPermanentSuppressBounce += float64(stat.Failed.Permanent.SuppressBounce)
220-
failedPermanentSuppressComplaint += float64(stat.Failed.Permanent.SuppressComplaint)
221-
failedPermanentSuppressUnsubscribe += float64(stat.Failed.Permanent.SuppressUnsubscribe)
222-
failedTemporaryEspblock += float64(stat.Failed.Temporary.Espblock)
223-
openedTotal += float64(stat.Opened.Total)
224-
storedTotal += float64(stat.Stored.Total)
225-
unsubscribedTotal += float64(stat.Unsubscribed.Total)
191+
// Helper function to safely get uint64 pointer value
192+
getVal := func(p *uint64) float64 {
193+
if p == nil {
194+
return 0
195+
}
196+
return float64(*p)
226197
}
227198

228199
// Begin Accepted Total
229200
ch <- prometheus.MustNewConstMetric(
230201
e.acceptedTotal,
231202
prometheus.CounterValue,
232-
acceptedTotalIncoming,
203+
getVal(metrics.AcceptedIncomingCount),
233204
domain, "incoming",
234205
)
235206
ch <- prometheus.MustNewConstMetric(
236207
e.acceptedTotal,
237208
prometheus.CounterValue,
238-
acceptedTotalOutgoing,
209+
getVal(metrics.AcceptedOutgoingCount),
239210
domain, "outgoing",
240211
)
241212
// End Accepted Total
242213

243-
ch <- prometheus.MustNewConstMetric(e.clickedTotal, prometheus.CounterValue, clickedTotal, domain)
244-
ch <- prometheus.MustNewConstMetric(e.complainedTotal, prometheus.CounterValue, complainedTotal, domain)
214+
ch <- prometheus.MustNewConstMetric(e.clickedTotal, prometheus.CounterValue, getVal(metrics.ClickedCount), domain)
215+
ch <- prometheus.MustNewConstMetric(e.complainedTotal, prometheus.CounterValue, getVal(metrics.ComplainedCount), domain)
245216

246217
// Begin Delivered Total
247218
ch <- prometheus.MustNewConstMetric(
248219
e.deliveredTotal,
249220
prometheus.CounterValue,
250-
deliveredHttpTotal,
221+
getVal(metrics.DeliveredHTTPCount),
251222
domain, "http",
252223
)
253224
ch <- prometheus.MustNewConstMetric(
254225
e.deliveredTotal,
255226
prometheus.CounterValue,
256-
deliveredSmtpTotal,
227+
getVal(metrics.DeliveredSMTPCount),
257228
domain, "smtp",
258229
)
259230
// End Delivered Total
260231

261232
// Begin Failed Permanent Total
233+
// Note: v5 API provides different granularity for permanent failures
234+
// Using hard_bounces as "bounce" and soft_bounces + delayed_bounce combined for other categories
262235
ch <- prometheus.MustNewConstMetric(
263236
e.failedPermanentTotal,
264237
prometheus.CounterValue,
265-
failedPermanentBounce,
238+
getVal(metrics.HardBouncesCount),
266239
domain, "bounce",
267240
)
268241
ch <- prometheus.MustNewConstMetric(
269242
e.failedPermanentTotal,
270243
prometheus.CounterValue,
271-
failedPermanentDelayedBounce,
244+
getVal(metrics.DelayedBounceCount),
272245
domain, "delayed_bounce",
273246
)
274247
ch <- prometheus.MustNewConstMetric(
275248
e.failedPermanentTotal,
276249
prometheus.CounterValue,
277-
failedPermanentSuppressBounce,
250+
getVal(metrics.SuppressedBouncesCount),
278251
domain, "suppress_bounce",
279252
)
280253
ch <- prometheus.MustNewConstMetric(
281254
e.failedPermanentTotal,
282255
prometheus.CounterValue,
283-
failedPermanentSuppressComplaint,
256+
getVal(metrics.SuppressedComplaintsCount),
284257
domain, "suppress_complaint",
285258
)
286259
ch <- prometheus.MustNewConstMetric(e.failedPermanentTotal, prometheus.CounterValue,
287-
failedPermanentSuppressUnsubscribe,
260+
getVal(metrics.SuppressedUnsubscribedCount),
288261
domain, "suppress_unsubscribe",
289262
)
290263
// End Failed Permanent Total
291264

292265
ch <- prometheus.MustNewConstMetric(
293266
e.failedTemporaryTotal,
294267
prometheus.CounterValue,
295-
failedTemporaryEspblock,
268+
getVal(metrics.TemporaryFailedESPBlockCount),
296269
domain, "esp_block",
297270
)
298271

299-
ch <- prometheus.MustNewConstMetric(e.openedTotal, prometheus.CounterValue, openedTotal, domain)
300-
ch <- prometheus.MustNewConstMetric(e.storedTotal, prometheus.CounterValue, storedTotal, domain)
301-
ch <- prometheus.MustNewConstMetric(e.unsubscribedTotal, prometheus.CounterValue, unsubscribedTotal, domain)
272+
ch <- prometheus.MustNewConstMetric(e.openedTotal, prometheus.CounterValue, getVal(metrics.OpenedCount), domain)
273+
ch <- prometheus.MustNewConstMetric(e.storedTotal, prometheus.CounterValue, getVal(metrics.StoredCount), domain)
274+
ch <- prometheus.MustNewConstMetric(e.unsubscribedTotal, prometheus.CounterValue, getVal(metrics.UnsubscribedCount), domain)
302275
}
303276

304277
ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, 1)

0 commit comments

Comments
 (0)