diff --git a/email/exporter.go b/email/exporter.go index c054c836c..58c6d4022 100644 --- a/email/exporter.go +++ b/email/exporter.go @@ -41,6 +41,7 @@ type ExporterImpl struct { limiter *rate.Limiter client PardotClient emailsHandledCounter prometheus.Counter + pardotErrorCounter prometheus.Counter log blog.Logger } @@ -62,12 +63,19 @@ func NewExporterImpl(client PardotClient, perDayLimit float64, maxConcurrentRequ }) scope.MustRegister(emailsHandledCounter) + pardotErrorCounter := prometheus.NewCounter(prometheus.CounterOpts{ + Name: "email_exporter_errors", + Help: "Total number of Pardot API errors encountered by the email exporter", + }) + scope.MustRegister(pardotErrorCounter) + impl := &ExporterImpl{ maxConcurrentRequests: maxConcurrentRequests, limiter: limiter, toSend: make([]string, 0, contactsQueueCap), client: client, emailsHandledCounter: emailsHandledCounter, + pardotErrorCounter: pardotErrorCounter, log: logger, } impl.wake = sync.NewCond(&impl.Mutex) @@ -145,6 +153,7 @@ func (impl *ExporterImpl) Start(daemonCtx context.Context) { err = impl.client.SendContact(email) if err != nil { + impl.pardotErrorCounter.Inc() impl.log.Errf("Sending Contact to Pardot: %s", err) } impl.emailsHandledCounter.Inc() diff --git a/email/exporter_test.go b/email/exporter_test.go index cccf6af0d..993195899 100644 --- a/email/exporter_test.go +++ b/email/exporter_test.go @@ -12,6 +12,8 @@ import ( blog "github.com/letsencrypt/boulder/log" "github.com/letsencrypt/boulder/metrics" "github.com/letsencrypt/boulder/test" + + "github.com/prometheus/client_golang/prometheus" ) var ctx = context.Background() @@ -88,6 +90,9 @@ func TestSendContacts(t *testing.T) { } test.AssertSliceContains(t, gotContacts, wantContacts[0]) test.AssertSliceContains(t, gotContacts, wantContacts[1]) + + // Check that the error counter was not incremented. + test.AssertMetricWithLabelsEquals(t, exporter.pardotErrorCounter, prometheus.Labels{}, 0) } func TestSendContactsQueueFull(t *testing.T) { @@ -130,3 +135,31 @@ func TestSendContactsQueueDrains(t *testing.T) { test.AssertEquals(t, 100, len(clientImpl.getCreatedContacts())) } + +type mockAlwaysFailClient struct{} + +func (m *mockAlwaysFailClient) SendContact(email string) error { + return fmt.Errorf("simulated failure") +} + +func TestSendContactsErrorMetrics(t *testing.T) { + t.Parallel() + + mockClient := &mockAlwaysFailClient{} + exporter := NewExporterImpl(mockClient, 1000000, 5, metrics.NoopRegisterer, blog.NewMock()) + + daemonCtx, cancel := context.WithCancel(context.Background()) + exporter.Start(daemonCtx) + + _, err := exporter.SendContacts(ctx, &emailpb.SendContactsRequest{ + Emails: []string{"test@example.com"}, + }) + test.AssertNotError(t, err, "Error creating contacts") + + // Drain the queue. + cancel() + exporter.Drain() + + // Check that the error counter was incremented. + test.AssertMetricWithLabelsEquals(t, exporter.pardotErrorCounter, prometheus.Labels{}, 1) +}