diff --git a/postfix_exporter.go b/postfix_exporter.go index a5e1acd..46714d7 100644 --- a/postfix_exporter.go +++ b/postfix_exporter.go @@ -282,10 +282,10 @@ var ( lmtpPipeSMTPLine = regexp.MustCompile(`, relay=(\S+), .*, delays=([0-9\.]+)/([0-9\.]+)/([0-9\.]+)/([0-9\.]+), `) qmgrInsertLine = regexp.MustCompile(`:.*, size=(\d+), nrcpt=(\d+) `) smtpStatusDeferredLine = regexp.MustCompile(`, status=deferred`) - smtpTLSLine = regexp.MustCompile(`^(\S+) TLS connection established to \S+: (\S+) with cipher (\S+) \((\d+)/(\d+) bits\)$`) + 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 `) - smtpdProcessesSASLLine = regexp.MustCompile(`: client=.*, sasl_username=(\S+)`) + smtpdProcessesSASLLine = regexp.MustCompile(`: client=.*, sasl_method=(\S+)`) smtpdRejectsLine = regexp.MustCompile(`^NOQUEUE: reject: RCPT from \S+: ([0-9]+) `) smtpdLostConnectionLine = regexp.MustCompile(`^lost connection after (\w+) from `) smtpdSASLAuthenticationFailuresLine = regexp.MustCompile(`^warning: \S+: SASL \S+ authentication failed: `) @@ -372,8 +372,8 @@ func (e *PostfixExporter) CollectFromLogLine(line string) { e.smtpdLostConnections.WithLabelValues(smtpdLostConnectionMatches[1]).Inc() } else if smtpdProcessesSASLMatches := smtpdProcessesSASLLine.FindStringSubmatch(remainder); smtpdProcessesSASLMatches != nil { e.smtpdProcesses.WithLabelValues(smtpdProcessesSASLMatches[1]).Inc() - } else if strings.Contains(logMatches[2], ": client=") { - e.smtpdProcesses.WithLabelValues("").Inc() + } else if strings.Contains(remainder, ": client=") { + e.smtpdProcesses.WithLabelValues(smtpdProcessesSASLMatches[1]).Inc() } else if smtpdRejectsMatches := smtpdRejectsLine.FindStringSubmatch(remainder); smtpdRejectsMatches != nil { e.smtpdRejects.WithLabelValues(smtpdRejectsMatches[1]).Inc() } else if smtpdSASLAuthenticationFailuresLine.MatchString(remainder) { @@ -449,9 +449,10 @@ func NewPostfixExporter(showqPath string, logfilePath string, journal *Journal, if logfilePath != "" { var err error tailer, err = tail.TailFile(logfilePath, tail.Config{ - ReOpen: true, // reopen the file if it's rotated - MustExist: true, // fail immediately if the file is missing or has incorrect permissions - Follow: true, // run in follow mode + ReOpen: true, // reopen the file if it's rotated + MustExist: true, // fail immediately if the file is missing or has incorrect permissions + Follow: true, // run in follow mode + Location: &tail.SeekInfo{Whence: io.SeekEnd}, // seek to end of file }) if err != nil { return nil, err @@ -564,7 +565,7 @@ func NewPostfixExporter(showqPath string, logfilePath string, journal *Journal, Name: "smtpd_messages_processed_total", Help: "Total number of messages processed.", }, - []string{"sasl_username"}), + []string{"sasl_method"}), smtpdRejects: prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: "postfix", diff --git a/postfix_exporter_test.go b/postfix_exporter_test.go index f38d29e..a56309d 100644 --- a/postfix_exporter_test.go +++ b/postfix_exporter_test.go @@ -35,9 +35,11 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { unsupportedLogEntries *prometheus.CounterVec } type args struct { - line []string - removedCount int - saslFailedCount int + line []string + removedCount int + saslFailedCount int + outgoingTLS int + smtpdMessagesProcessed int } tests := []struct { name string @@ -54,7 +56,7 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { saslFailedCount: 0, }, fields: fields{ - qmgrRemoves: &testCounter{count: 0}, + qmgrRemoves: prometheus.NewCounter(prometheus.CounterOpts{}), unsupportedLogEntries: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"process"}), }, }, @@ -98,7 +100,7 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { saslFailedCount: 0, }, fields: fields{ - qmgrRemoves: &testCounter{count: 0}, + qmgrRemoves: prometheus.NewCounter(prometheus.CounterOpts{}), unsupportedLogEntries: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"process"}), }, }, @@ -114,10 +116,42 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { removedCount: 0, }, fields: fields{ - smtpdSASLAuthenticationFailures: &testCounter{count: 0}, + smtpdSASLAuthenticationFailures: prometheus.NewCounter(prometheus.CounterOpts{}), unsupportedLogEntries: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"process"}), }, }, + { + name: "SASL login", + args: args{ + line: []string{ + "Oct 30 13:19:26 mailgw-out1 postfix/smtpd[27530]: EB4B2C19E2: client=xxx[1.2.3.4], sasl_method=PLAIN, sasl_username=user@domain", + }, + removedCount: 0, + saslFailedCount: 0, + outgoingTLS: 0, + smtpdMessagesProcessed: 1, + }, + fields: fields{ + unsupportedLogEntries: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"process"}), + smtpdProcesses: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"sasl_method"}), + }, + }, + { + name: "Issue #35", + args: args{ + line: []string{ + "Jul 24 04:38:17 mail postfix/smtp[30582]: Verified TLS connection established to gmail-smtp-in.l.google.com[108.177.14.26]:25: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256", + "Jul 24 03:28:15 mail postfix/smtp[24052]: Verified TLS connection established to mx2.comcast.net[2001:558:fe21:2a::6]:25: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)", + }, + removedCount: 0, + saslFailedCount: 0, + outgoingTLS: 2, + }, + fields: fields{ + unsupportedLogEntries: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"process"}), + smtpTLSConnects: prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"Verified", "TLSv1.2", "ECDHE-RSA-AES256-GCM-SHA384", "256", "256"}), + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -145,52 +179,51 @@ func TestPostfixExporter_CollectFromLogline(t *testing.T) { smtpdSASLAuthenticationFailures: tt.fields.smtpdSASLAuthenticationFailures, smtpdTLSConnects: tt.fields.smtpdTLSConnects, unsupportedLogEntries: tt.fields.unsupportedLogEntries, + logUnsupportedLines: true, } for _, line := range tt.args.line { e.CollectFromLogLine(line) } assertCounterEquals(t, e.qmgrRemoves, tt.args.removedCount, "Wrong number of lines counted") assertCounterEquals(t, e.smtpdSASLAuthenticationFailures, tt.args.saslFailedCount, "Wrong number of Sasl counter counted") + 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") }) } } -func assertCounterEquals(t *testing.T, counter prometheus.Counter, expected int, message string) { +func assertCounterEquals(t *testing.T, counter prometheus.Collector, expected int, message string) { if counter != nil && expected > 0 { switch counter.(type) { - case *testCounter: - counter := counter.(*testCounter) - assert.Equal(t, expected, counter.Count(), message) + case *prometheus.CounterVec: + counter := counter.(*prometheus.CounterVec) + metricsChan := make(chan prometheus.Metric) + go func() { + counter.Collect(metricsChan) + close(metricsChan) + }() + var count int = 0 + for metric := range metricsChan { + metricDto := io_prometheus_client.Metric{} + metric.Write(&metricDto) + count += int(*metricDto.Counter.Value) + } + assert.Equal(t, expected, count, message) + case prometheus.Counter: + metricsChan := make(chan prometheus.Metric) + go func() { + counter.Collect(metricsChan) + close(metricsChan) + }() + var count int = 0 + for metric := range metricsChan { + metricDto := io_prometheus_client.Metric{} + metric.Write(&metricDto) + count += int(*metricDto.Counter.Value) + } + assert.Equal(t, expected, count, message) default: t.Fatal("Type not implemented") } } } - -type testCounter struct { - count int -} - -func (t *testCounter) setCount(count int) { - t.count = count -} - -func (t *testCounter) Count() int { - return t.count -} - -func (t *testCounter) Add(_ float64) { -} -func (t *testCounter) Collect(_ chan<- prometheus.Metric) { -} -func (t *testCounter) Describe(_ chan<- *prometheus.Desc) { -} -func (t *testCounter) Desc() *prometheus.Desc { - return nil -} -func (t *testCounter) Inc() { - t.count++ -} -func (t *testCounter) Write(_ *io_prometheus_client.Metric) error { - return nil -}