From 4c867be812dca96549b377a244e209fd4ff9bd70 Mon Sep 17 00:00:00 2001 From: natsuki-hoshino Date: Thu, 28 Dec 2023 05:40:45 +0000 Subject: [PATCH] add metrics for enhanced status codes for deferred and bounced smtp mail --- postfix_exporter.go | 29 +++++++++++++++++++++++++++++ postfix_exporter_test.go | 12 ++++++++++++ 2 files changed, 41 insertions(+) diff --git a/postfix_exporter.go b/postfix_exporter.go index 56691cc..17f6119 100644 --- a/postfix_exporter.go +++ b/postfix_exporter.go @@ -58,6 +58,8 @@ type PostfixExporter struct { smtpTLSConnects *prometheus.CounterVec smtpConnectionTimedOut prometheus.Counter smtpProcesses *prometheus.CounterVec + smtpDeferredDSN *prometheus.CounterVec + smtpBouncedDSN *prometheus.CounterVec // should be the same as smtpProcesses{status=deferred}, kept for compatibility, but this doesn't work ! smtpDeferreds prometheus.Counter smtpdConnects prometheus.Counter @@ -297,6 +299,7 @@ var ( qmgrInsertLine = regexp.MustCompile(`:.*, size=(\d+), nrcpt=(\d+) `) qmgrExpiredLine = regexp.MustCompile(`:.*, status=(expired|force-expired), returned to sender`) smtpStatusLine = regexp.MustCompile(`, status=(\w+) `) + smtpDSNLine = regexp.MustCompile(`, dsn=(\d\.\d+\.\d+)`) smtpTLSLine = regexp.MustCompile(`^(\S+) TLS connection established to \S+: (\S+) with cipher (\S+) \((\d+)/(\d+) bits\)`) smtpConnectionTimedOut = regexp.MustCompile(`^connect\s+to\s+(.*)\[(.*)\]:(\d+):\s+(Connection timed out)$`) smtpdFCrDNSErrorsLine = regexp.MustCompile(`^warning: hostname \S+ does not resolve to address `) @@ -372,8 +375,16 @@ func (e *PostfixExporter) CollectFromLogLine(line string) { addToHistogramVec(e.smtpDelays, smtpMatches[5], "transmission", "") if smtpStatusMatches := smtpStatusLine.FindStringSubmatch(remainder); smtpStatusMatches != nil { e.smtpProcesses.WithLabelValues(smtpStatusMatches[1]).Inc() + dsnMatches := smtpDSNLine.FindStringSubmatch(remainder) if smtpStatusMatches[1] == "deferred" { e.smtpStatusDeferred.Inc() + if dsnMatches != nil { + e.smtpDeferredDSN.WithLabelValues(dsnMatches[1]).Inc() + } + } else if smtpStatusMatches[1] == "bounced" { + if dsnMatches != nil { + e.smtpBouncedDSN.WithLabelValues(dsnMatches[1]).Inc() + } } } } else if smtpTLSMatches := smtpTLSLine.FindStringSubmatch(remainder); smtpTLSMatches != nil { @@ -542,6 +553,20 @@ func NewPostfixExporter(showqPath string, logSrc LogSource, logUnsupportedLines Help: "Total number of messages that have been processed by the smtp process.", }, []string{"status"}), + smtpDeferredDSN: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "postfix", + Name: "smtp_deferred_messages_by_dsn_total", + Help: "Total number of messages that have been deferred on SMTP by DSN.", + }, + []string{"dsn"}), + smtpBouncedDSN: prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "postfix", + Name: "smtp_bounced_messages_by_dsn_total", + Help: "Total number of messages that have been bounced on SMTP by DSN.", + }, + []string{"dsn"}), smtpConnectionTimedOut: prometheus.NewCounter(prometheus.CounterOpts{ Namespace: "postfix", Name: "smtp_connection_timed_out_total", @@ -648,6 +673,8 @@ func (e *PostfixExporter) Describe(ch chan<- *prometheus.Desc) { e.smtpTLSConnects.Describe(ch) ch <- e.smtpDeferreds.Desc() e.smtpProcesses.Describe(ch) + e.smtpDeferredDSN.Describe(ch) + e.smtpBouncedDSN.Describe(ch) ch <- e.smtpdConnects.Desc() ch <- e.smtpdDisconnects.Desc() ch <- e.smtpdFCrDNSErrors.Desc() @@ -732,6 +759,8 @@ func (e *PostfixExporter) Collect(ch chan<- prometheus.Metric) { ch <- e.smtpdFCrDNSErrors e.smtpdLostConnections.Collect(ch) e.smtpdProcesses.Collect(ch) + e.smtpDeferredDSN.Collect(ch) + e.smtpBouncedDSN.Collect(ch) e.smtpdRejects.Collect(ch) ch <- e.smtpdSASLAuthenticationFailures e.smtpdTLSConnects.Collect(ch) diff --git a/postfix_exporter_test.go b/postfix_exporter_test.go index dcae8ca..ec1bd7b 100644 --- a/postfix_exporter_test.go +++ b/postfix_exporter_test.go @@ -27,6 +27,8 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { smtpDeferreds prometheus.Counter smtpStatusDeferred prometheus.Counter smtpProcesses *prometheus.CounterVec + smtpDeferredDSN *prometheus.CounterVec + smtpBouncedDSN *prometheus.CounterVec smtpdConnects prometheus.Counter smtpdDisconnects prometheus.Counter smtpdFCrDNSErrors prometheus.Counter @@ -47,6 +49,8 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { outgoingTLS int smtpdMessagesProcessed int smtpMessagesProcessed int + smtpDeferred int + smtpBounced int bounceNonDelivery int virtualDelivered int unsupportedLogEntries []string @@ -206,6 +210,8 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { "Dec 29 03:03:48 mail postfix/bounce[9321]: 732BB407C3: sender non-delivery notification: 5DE184083C", }, smtpMessagesProcessed: 2, + smtpDeferred: 1, + smtpBounced: 1, bounceNonDelivery: 1, }, fields: fields{ @@ -213,6 +219,8 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { smtpDelays: prometheus.NewHistogramVec(prometheus.HistogramOpts{}, []string{"stage"}), smtpStatusDeferred: prometheus.NewCounter(prometheus.CounterOpts{}), smtpProcesses: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"status"}), + smtpDeferredDSN: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"dsn"}), + smtpBouncedDSN: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"dsn"}), bounceNonDelivery: prometheus.NewCounter(prometheus.CounterOpts{}), }, }, @@ -267,6 +275,8 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { smtpDeferreds: tt.fields.smtpDeferreds, smtpStatusDeferred: tt.fields.smtpStatusDeferred, smtpProcesses: tt.fields.smtpProcesses, + smtpDeferredDSN: tt.fields.smtpDeferredDSN, + smtpBouncedDSN: tt.fields.smtpBouncedDSN, smtpdConnects: tt.fields.smtpdConnects, smtpdDisconnects: tt.fields.smtpdDisconnects, smtpdFCrDNSErrors: tt.fields.smtpdFCrDNSErrors, @@ -289,6 +299,8 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { assertCounterEquals(t, e.smtpTLSConnects, tt.args.outgoingTLS, "Wrong number of TLS connections counted") assertCounterEquals(t, e.smtpdProcesses, tt.args.smtpdMessagesProcessed, "Wrong number of smtpd messages processed") assertCounterEquals(t, e.smtpProcesses, tt.args.smtpMessagesProcessed, "Wrong number of smtp messages processed") + assertCounterEquals(t, e.smtpDeferredDSN, tt.args.smtpDeferred, "Wrong number of smtp deferred") + assertCounterEquals(t, e.smtpBouncedDSN, tt.args.smtpBounced, "Wrong number of smtp bounced") assertCounterEquals(t, e.bounceNonDelivery, tt.args.bounceNonDelivery, "Wrong number of non delivery notifications") assertCounterEquals(t, e.virtualDelivered, tt.args.virtualDelivered, "Wrong number of delivered mails") assertVecMetricsEquals(t, e.unsupportedLogEntries, tt.args.unsupportedLogEntries, "Wrong number of unsupportedLogEntries")