2017-02-17 14:29:37 +00:00
// Copyright 2017 Kumina, https://kumina.nl/
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"bufio"
2017-03-17 15:42:54 +00:00
"bytes"
2020-02-18 14:31:07 +00:00
"context"
2017-03-17 15:42:54 +00:00
"errors"
"fmt"
2017-02-17 14:29:37 +00:00
"io"
"log"
"net"
"regexp"
"strconv"
2017-05-02 13:07:19 +00:00
"strings"
2017-02-17 14:29:37 +00:00
"time"
2018-09-30 22:16:10 +00:00
"github.com/hpcloud/tail"
2017-02-17 14:29:37 +00:00
"github.com/prometheus/client_golang/prometheus"
)
var (
postfixUpDesc = prometheus . NewDesc (
2017-02-17 14:50:47 +00:00
prometheus . BuildFQName ( "postfix" , "" , "up" ) ,
2017-02-17 14:29:37 +00:00
"Whether scraping Postfix's metrics was successful." ,
2017-05-02 13:07:19 +00:00
[ ] string { "path" } , nil )
2017-02-17 14:29:37 +00:00
)
2017-05-02 13:07:19 +00:00
// PostfixExporter holds the state that should be preserved by the
// Postfix Prometheus metrics exporter across scrapes.
type PostfixExporter struct {
2020-02-18 15:54:25 +00:00
showqPath string
journal * Journal
tailer * tail . Tail
logUnsupportedLines bool
2017-05-02 13:07:19 +00:00
// Metrics that should persist after refreshes, based on logs.
cleanupProcesses prometheus . Counter
cleanupRejects prometheus . Counter
2019-03-07 16:59:04 +00:00
cleanupNotAccepted prometheus . Counter
2017-05-02 13:07:19 +00:00
lmtpDelays * prometheus . HistogramVec
pipeDelays * prometheus . HistogramVec
qmgrInsertsNrcpt prometheus . Histogram
qmgrInsertsSize prometheus . Histogram
qmgrRemoves prometheus . Counter
smtpDelays * prometheus . HistogramVec
smtpTLSConnects * prometheus . CounterVec
2020-02-18 15:54:25 +00:00
smtpConnectionTimedOut prometheus . Counter
2019-03-07 16:59:04 +00:00
smtpDeferreds prometheus . Counter
2017-05-02 13:07:19 +00:00
smtpdConnects prometheus . Counter
smtpdDisconnects prometheus . Counter
smtpdFCrDNSErrors prometheus . Counter
smtpdLostConnections * prometheus . CounterVec
smtpdProcesses * prometheus . CounterVec
smtpdRejects * prometheus . CounterVec
smtpdSASLAuthenticationFailures prometheus . Counter
smtpdTLSConnects * prometheus . CounterVec
unsupportedLogEntries * prometheus . CounterVec
2019-04-15 17:48:49 +00:00
smtpStatusDeferred prometheus . Counter
2020-02-18 15:54:25 +00:00
opendkimSignatureAdded * prometheus . CounterVec
2017-05-02 13:07:19 +00:00
}
// CollectShowqFromReader parses the output of Postfix's 'showq' command
// and turns it into metrics.
2017-02-17 14:31:04 +00:00
//
2017-03-17 15:42:54 +00:00
// The output format of this command depends on the version of Postfix
// used. Postfix 2.x uses a textual format, identical to the output of
// the 'mailq' command. Postfix 3.x uses a binary format, where entries
// are terminated using null bytes. Auto-detect the format by scanning
// for null bytes in the first 128 bytes of output.
2017-02-17 14:29:37 +00:00
func CollectShowqFromReader ( file io . Reader , ch chan <- prometheus . Metric ) error {
2017-03-17 15:42:54 +00:00
reader := bufio . NewReader ( file )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
buf , err := reader . Peek ( 128 )
2018-12-17 16:01:01 +00:00
if err != nil && err != io . EOF {
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
log . Printf ( "Could not read postfix output, %v" , err )
}
2017-03-17 15:42:54 +00:00
if bytes . IndexByte ( buf , 0 ) >= 0 {
return CollectBinaryShowqFromReader ( reader , ch )
}
2017-05-02 13:07:19 +00:00
return CollectTextualShowqFromReader ( reader , ch )
2017-03-17 15:42:54 +00:00
}
2017-05-02 13:07:19 +00:00
// CollectTextualShowqFromReader parses Postfix's textual showq output.
2017-03-17 15:42:54 +00:00
func CollectTextualShowqFromReader ( file io . Reader , ch chan <- prometheus . Metric ) error {
2017-02-17 14:29:37 +00:00
scanner := bufio . NewScanner ( file )
scanner . Split ( bufio . ScanLines )
// Regular expression for matching postqueue's output. Example:
// "A07A81514 5156 Tue Feb 14 13:13:54 MAILER-DAEMON"
2018-12-16 09:55:34 +00:00
messageLine := regexp . MustCompile ( ` ^[0-9A-F]+([\*!]?) +(\d+) (\w { 3} \w { 3} +\d+ +\d+:\d { 2}:\d { 2}) + ` )
2017-02-17 14:29:37 +00:00
// Histograms tracking the messages by size and age.
2017-04-18 14:03:53 +00:00
sizeHistogram := prometheus . NewHistogramVec (
2017-02-17 14:29:37 +00:00
prometheus . HistogramOpts {
2017-02-17 14:50:47 +00:00
Namespace : "postfix" ,
2017-06-01 12:26:00 +00:00
Name : "showq_message_size_bytes" ,
2017-02-17 14:29:37 +00:00
Help : "Size of messages in Postfix's message queue, in bytes" ,
Buckets : [ ] float64 { 1e3 , 1e4 , 1e5 , 1e6 , 1e7 , 1e8 , 1e9 } ,
2017-04-18 14:03:53 +00:00
} ,
[ ] string { "queue" } )
ageHistogram := prometheus . NewHistogramVec (
2017-02-17 14:29:37 +00:00
prometheus . HistogramOpts {
2017-02-17 14:50:47 +00:00
Namespace : "postfix" ,
2017-06-01 12:26:00 +00:00
Name : "showq_message_age_seconds" ,
2017-02-17 14:29:37 +00:00
Help : "Age of messages in Postfix's message queue, in seconds" ,
Buckets : [ ] float64 { 1e1 , 1e2 , 1e3 , 1e4 , 1e5 , 1e6 , 1e7 , 1e8 } ,
2017-04-18 14:03:53 +00:00
} ,
[ ] string { "queue" } )
2017-02-17 14:29:37 +00:00
2018-02-07 14:12:35 +00:00
// Initialize all queue buckets to zero.
2018-02-07 13:24:08 +00:00
for _ , q := range [ ] string { "active" , "hold" , "other" } {
sizeHistogram . WithLabelValues ( q )
ageHistogram . WithLabelValues ( q )
}
2017-02-17 14:29:37 +00:00
now := time . Now ( )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
location , err := time . LoadLocation ( "Local" )
if err != nil {
log . Println ( err )
}
2017-02-17 14:29:37 +00:00
for scanner . Scan ( ) {
matches := messageLine . FindStringSubmatch ( scanner . Text ( ) )
if matches != nil {
2019-01-28 19:40:52 +00:00
continue
}
2017-04-18 14:03:53 +00:00
2019-01-28 19:40:52 +00:00
// Derive the name of the message queue.
queue := "other"
if matches [ 1 ] == "*" {
queue = "active"
} else if matches [ 1 ] == "!" {
queue = "hold"
}
2017-02-17 14:29:37 +00:00
2019-01-28 19:40:52 +00:00
// Parse the message size.
size , err := strconv . ParseFloat ( matches [ 2 ] , 64 )
if err != nil {
return err
}
2017-02-17 14:29:37 +00:00
2019-01-28 19:40:52 +00:00
// Parse the message date. Unfortunately, the
// output contains no year number. Assume it
// applies to the last year for which the
// message date doesn't exceed time.Now().
date , err := time . ParseInLocation ( "Mon Jan 2 15:04:05" ,
matches [ 3 ] , location )
if err != nil {
return err
}
date = date . AddDate ( now . Year ( ) , 0 , 0 )
if date . After ( now ) {
date = date . AddDate ( - 1 , 0 , 0 )
2017-02-17 14:29:37 +00:00
}
2019-01-28 19:40:52 +00:00
sizeHistogram . WithLabelValues ( queue ) . Observe ( size )
ageHistogram . WithLabelValues ( queue ) . Observe ( now . Sub ( date ) . Seconds ( ) )
2017-02-17 14:29:37 +00:00
}
2017-04-18 14:03:53 +00:00
sizeHistogram . Collect ( ch )
ageHistogram . Collect ( ch )
2017-02-17 14:29:37 +00:00
return scanner . Err ( )
}
2017-05-02 13:07:19 +00:00
// ScanNullTerminatedEntries is a splitting function for bufio.Scanner
// to split entries by null bytes.
2017-03-17 15:42:54 +00:00
func ScanNullTerminatedEntries ( data [ ] byte , atEOF bool ) ( advance int , token [ ] byte , err error ) {
if i := bytes . IndexByte ( data , 0 ) ; i >= 0 {
// Valid record found.
return i + 1 , data [ 0 : i ] , nil
} else if atEOF && len ( data ) != 0 {
// Data at the end of the file without a null terminator.
return 0 , nil , errors . New ( "Expected null byte terminator" )
} else {
// Request more data.
return 0 , nil , nil
}
}
2017-05-02 13:07:19 +00:00
// CollectBinaryShowqFromReader parses Postfix's binary showq format.
2017-03-17 15:42:54 +00:00
func CollectBinaryShowqFromReader ( file io . Reader , ch chan <- prometheus . Metric ) error {
scanner := bufio . NewScanner ( file )
scanner . Split ( ScanNullTerminatedEntries )
// Histograms tracking the messages by size and age.
2017-04-18 14:03:53 +00:00
sizeHistogram := prometheus . NewHistogramVec (
2017-03-17 15:42:54 +00:00
prometheus . HistogramOpts {
Namespace : "postfix" ,
2017-06-01 12:26:00 +00:00
Name : "showq_message_size_bytes" ,
2017-03-17 15:42:54 +00:00
Help : "Size of messages in Postfix's message queue, in bytes" ,
Buckets : [ ] float64 { 1e3 , 1e4 , 1e5 , 1e6 , 1e7 , 1e8 , 1e9 } ,
2017-04-18 14:03:53 +00:00
} ,
[ ] string { "queue" } )
ageHistogram := prometheus . NewHistogramVec (
2017-03-17 15:42:54 +00:00
prometheus . HistogramOpts {
Namespace : "postfix" ,
2017-06-01 12:26:00 +00:00
Name : "showq_message_age_seconds" ,
2017-03-17 15:42:54 +00:00
Help : "Age of messages in Postfix's message queue, in seconds" ,
Buckets : [ ] float64 { 1e1 , 1e2 , 1e3 , 1e4 , 1e5 , 1e6 , 1e7 , 1e8 } ,
2017-04-18 14:03:53 +00:00
} ,
[ ] string { "queue" } )
2017-03-17 15:42:54 +00:00
2018-02-07 14:12:35 +00:00
// Initialize all queue buckets to zero.
2018-02-07 13:24:08 +00:00
for _ , q := range [ ] string { "active" , "deferred" , "hold" , "incoming" , "maildrop" } {
sizeHistogram . WithLabelValues ( q )
ageHistogram . WithLabelValues ( q )
}
2017-03-17 15:42:54 +00:00
now := float64 ( time . Now ( ) . UnixNano ( ) ) / 1e9
2017-04-18 14:03:53 +00:00
queue := "unknown"
2017-03-17 15:42:54 +00:00
for scanner . Scan ( ) {
// Parse a key/value entry.
key := scanner . Text ( )
if len ( key ) == 0 {
2017-04-18 14:03:53 +00:00
// Empty key means a record separator.
2017-04-18 14:09:22 +00:00
queue = "unknown"
2017-03-17 15:42:54 +00:00
continue
}
if ! scanner . Scan ( ) {
2017-05-02 13:07:19 +00:00
return fmt . Errorf ( "key %q does not have a value" , key )
2017-03-17 15:42:54 +00:00
}
value := scanner . Text ( )
2017-04-18 14:03:53 +00:00
if key == "queue_name" {
// The name of the message queue.
queue = value
} else if key == "size" {
2017-03-17 15:42:54 +00:00
// Message size in bytes.
size , err := strconv . ParseFloat ( value , 64 )
if err != nil {
return err
}
2017-04-18 14:03:53 +00:00
sizeHistogram . WithLabelValues ( queue ) . Observe ( size )
2017-03-17 15:42:54 +00:00
} else if key == "time" {
// Message time as a UNIX timestamp.
2019-02-15 09:41:53 +00:00
utime , err := strconv . ParseFloat ( value , 64 )
2017-03-17 15:42:54 +00:00
if err != nil {
return err
}
2019-02-15 09:41:53 +00:00
ageHistogram . WithLabelValues ( queue ) . Observe ( now - utime )
2017-03-17 15:42:54 +00:00
}
}
2017-04-18 14:03:53 +00:00
sizeHistogram . Collect ( ch )
ageHistogram . Collect ( ch )
2017-03-17 15:42:54 +00:00
return scanner . Err ( )
}
2017-05-02 13:07:19 +00:00
// CollectShowqFromFile collects Postfix queue statistics from a file.
2018-12-04 15:50:01 +00:00
//func CollectShowqFromFile(path string, ch chan<- prometheus.Metric) error {
// fd, err := os.Open(path)
// if err != nil {
// return err
// }
// defer fd.Close()
// return CollectShowqFromReader(fd, ch)
//}
2017-02-17 14:29:37 +00:00
2017-05-02 13:07:19 +00:00
// CollectShowqFromSocket collects Postfix queue statistics from a socket.
2017-02-17 14:29:37 +00:00
func CollectShowqFromSocket ( path string , ch chan <- prometheus . Metric ) error {
2017-05-02 13:07:19 +00:00
fd , err := net . Dial ( "unix" , path )
2017-02-17 14:29:37 +00:00
if err != nil {
return err
}
2017-05-02 13:07:19 +00:00
defer fd . Close ( )
return CollectShowqFromReader ( fd , ch )
2017-02-17 14:29:37 +00:00
}
2018-01-28 13:13:49 +00:00
// Patterns for parsing log messages.
var (
2020-02-18 15:54:25 +00:00
logLine = regexp . MustCompile ( ` ?(postfix|opendkim)(/(\w+))?\[\d+\]: (.*) ` )
2018-12-16 09:55:34 +00:00
lmtpPipeSMTPLine = regexp . MustCompile ( ` , relay=(\S+), .*, delays=([0-9\.]+)/([0-9\.]+)/([0-9\.]+)/([0-9\.]+), ` )
qmgrInsertLine = regexp . MustCompile ( ` :.*, size=(\d+), nrcpt=(\d+) ` )
2019-04-15 17:48:49 +00:00
smtpStatusDeferredLine = regexp . MustCompile ( ` , status=deferred ` )
2018-12-16 09:55:34 +00:00
smtpTLSLine = regexp . MustCompile ( ` ^(\S+) TLS connection established to \S+: (\S+) with cipher (\S+) \((\d+)/(\d+) bits\)$ ` )
2020-02-18 15:54:25 +00:00
smtpConnectionTimedOut = regexp . MustCompile ( ` ^connect\s+to\s+(.*)\[(.*)\]:(\d+):\s+(Connection timed out)$ ` )
2018-12-16 09:55:34 +00:00
smtpdFCrDNSErrorsLine = regexp . MustCompile ( ` ^warning: hostname \S+ does not resolve to address ` )
smtpdProcessesSASLLine = regexp . MustCompile ( ` : client=.*, sasl_username=(\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: ` )
smtpdTLSLine = regexp . MustCompile ( ` ^(\S+) TLS connection established from \S+: (\S+) with cipher (\S+) \((\d+)/(\d+) bits\)$ ` )
2020-02-18 15:54:25 +00:00
opendkimSignatureAdded = regexp . MustCompile ( ` ^[\w\d]+: DKIM-Signature field added \(s=(\w+), d=(.*)\)$ ` )
2018-01-28 13:13:49 +00:00
)
// CollectFromLogline collects metrict from a Postfix log line.
2020-02-18 14:31:07 +00:00
func ( e * PostfixExporter ) CollectFromLogLine ( line string ) {
2018-01-28 13:13:49 +00:00
// Strip off timestamp, hostname, etc.
2019-01-28 19:40:52 +00:00
logMatches := logLine . FindStringSubmatch ( line )
if logMatches == nil {
// Unknown log entry format.
e . unsupportedLogEntries . WithLabelValues ( "" ) . Inc ( )
return
}
2020-02-19 16:57:34 +00:00
process := logMatches [ 1 ]
subprocess := logMatches [ 3 ]
remainder := logMatches [ 4 ]
switch process {
case "postfix" :
// Group patterns to check by Postfix service.
switch subprocess {
case "cleanup" :
if strings . Contains ( remainder , ": message-id=<" ) {
e . cleanupProcesses . Inc ( )
} else if strings . Contains ( remainder , ": reject: " ) {
e . cleanupRejects . Inc ( )
} else {
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
}
2020-02-19 16:57:34 +00:00
e . unsupportedLogEntries . WithLabelValues ( subprocess ) . Inc ( )
}
case "lmtp" :
if lmtpMatches := lmtpPipeSMTPLine . FindStringSubmatch ( remainder ) ; lmtpMatches != nil {
addToHistogramVec ( e . lmtpDelays , lmtpMatches [ 2 ] , "LMTP pdelay" , "before_queue_manager" )
addToHistogramVec ( e . lmtpDelays , lmtpMatches [ 3 ] , "LMTP adelay" , "queue_manager" )
addToHistogramVec ( e . lmtpDelays , lmtpMatches [ 4 ] , "LMTP sdelay" , "connection_setup" )
addToHistogramVec ( e . lmtpDelays , lmtpMatches [ 5 ] , "LMTP xdelay" , "transmission" )
} else {
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
}
2020-02-19 16:57:34 +00:00
e . unsupportedLogEntries . WithLabelValues ( subprocess ) . Inc ( )
}
case "pipe" :
if pipeMatches := lmtpPipeSMTPLine . FindStringSubmatch ( remainder ) ; pipeMatches != nil {
addToHistogramVec ( e . pipeDelays , pipeMatches [ 2 ] , "PIPE pdelay" , pipeMatches [ 1 ] , "before_queue_manager" )
addToHistogramVec ( e . pipeDelays , pipeMatches [ 3 ] , "PIPE adelay" , pipeMatches [ 1 ] , "queue_manager" )
addToHistogramVec ( e . pipeDelays , pipeMatches [ 4 ] , "PIPE sdelay" , pipeMatches [ 1 ] , "connection_setup" )
addToHistogramVec ( e . pipeDelays , pipeMatches [ 5 ] , "PIPE xdelay" , pipeMatches [ 1 ] , "transmission" )
} else {
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
}
2020-02-19 16:57:34 +00:00
e . unsupportedLogEntries . WithLabelValues ( subprocess ) . Inc ( )
}
case "qmgr" :
if qmgrInsertMatches := qmgrInsertLine . FindStringSubmatch ( remainder ) ; qmgrInsertMatches != nil {
addToHistogram ( e . qmgrInsertsSize , qmgrInsertMatches [ 1 ] , "QMGR size" )
addToHistogram ( e . qmgrInsertsNrcpt , qmgrInsertMatches [ 2 ] , "QMGR nrcpt" )
} else if strings . HasSuffix ( remainder , ": removed" ) {
e . qmgrRemoves . Inc ( )
} else {
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
}
2020-02-19 16:57:34 +00:00
e . unsupportedLogEntries . WithLabelValues ( subprocess ) . Inc ( )
}
case "smtp" :
if smtpMatches := lmtpPipeSMTPLine . FindStringSubmatch ( remainder ) ; smtpMatches != nil {
addToHistogramVec ( e . smtpDelays , smtpMatches [ 2 ] , "before_queue_manager" )
addToHistogramVec ( e . smtpDelays , smtpMatches [ 3 ] , "queue_manager" )
addToHistogramVec ( e . smtpDelays , smtpMatches [ 4 ] , "connection_setup" )
addToHistogramVec ( e . smtpDelays , smtpMatches [ 5 ] , "transmission" )
} else if smtpTLSMatches := smtpTLSLine . FindStringSubmatch ( remainder ) ; smtpTLSMatches != nil {
e . smtpTLSConnects . WithLabelValues ( smtpTLSMatches [ 1 : ] ... ) . Inc ( )
2018-01-28 13:13:49 +00:00
} else {
2020-02-18 15:54:25 +00:00
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
}
2020-02-18 15:54:25 +00:00
e . unsupportedLogEntries . WithLabelValues ( subprocess ) . Inc ( )
2018-01-28 13:13:49 +00:00
}
2020-02-19 16:57:34 +00:00
case "smtpd" :
if strings . HasPrefix ( remainder , "connect from " ) {
e . smtpdConnects . Inc ( )
} else if strings . HasPrefix ( remainder , "disconnect from " ) {
e . smtpdDisconnects . Inc ( )
} else if smtpdFCrDNSErrorsLine . MatchString ( remainder ) {
e . smtpdFCrDNSErrors . Inc ( )
} else if smtpdLostConnectionMatches := smtpdLostConnectionLine . FindStringSubmatch ( remainder ) ; smtpdLostConnectionMatches != nil {
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 smtpdRejectsMatches := smtpdRejectsLine . FindStringSubmatch ( remainder ) ; smtpdRejectsMatches != nil {
e . smtpdRejects . WithLabelValues ( smtpdRejectsMatches [ 1 ] ) . Inc ( )
} else if smtpdSASLAuthenticationFailuresLine . MatchString ( remainder ) {
e . smtpdSASLAuthenticationFailures . Inc ( )
} else if smtpdTLSMatches := smtpdTLSLine . FindStringSubmatch ( remainder ) ; smtpdTLSMatches != nil {
e . smtpdTLSConnects . WithLabelValues ( smtpdTLSMatches [ 1 : ] ... ) . Inc ( )
2018-01-28 13:13:49 +00:00
} else {
2020-02-18 15:54:25 +00:00
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
}
2020-02-19 16:57:34 +00:00
e . unsupportedLogEntries . WithLabelValues ( subprocess ) . Inc ( )
2018-01-28 13:13:49 +00:00
}
2020-02-18 15:54:25 +00:00
default :
2020-02-19 16:57:34 +00:00
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
}
// Unknown Postfix service.
e . unsupportedLogEntries . WithLabelValues ( subprocess ) . Inc ( )
2019-01-28 19:40:52 +00:00
}
2020-02-19 16:57:34 +00:00
case "opendkim" :
if opendkimMatches := opendkimSignatureAdded . FindStringSubmatch ( remainder ) ; opendkimMatches != nil {
e . opendkimSignatureAdded . WithLabelValues ( opendkimMatches [ 1 ] , opendkimMatches [ 2 ] ) . Inc ( )
2019-01-28 19:40:52 +00:00
} else {
2020-02-19 12:08:47 +00:00
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
}
e . unsupportedLogEntries . WithLabelValues ( process ) . Inc ( )
2018-01-28 13:13:49 +00:00
}
2019-01-28 19:40:52 +00:00
default :
2018-01-28 13:13:49 +00:00
// Unknown log entry format.
2020-02-18 15:54:25 +00:00
if e . logUnsupportedLines {
log . Printf ( "Unsupported Line: %v" , line )
}
2018-01-28 13:13:49 +00:00
e . unsupportedLogEntries . WithLabelValues ( "" ) . Inc ( )
}
}
2019-01-28 20:13:37 +00:00
func addToHistogram ( h prometheus . Histogram , value , fieldName string ) {
float , err := strconv . ParseFloat ( value , 64 )
if err != nil {
log . Printf ( "Couldn't convert value '%s' for %v: %v" , value , fieldName , err )
}
h . Observe ( float )
}
func addToHistogramVec ( h * prometheus . HistogramVec , value , fieldName string , labels ... string ) {
float , err := strconv . ParseFloat ( value , 64 )
if err != nil {
log . Printf ( "Couldn't convert value '%s' for %v: %v" , value , fieldName , err )
}
h . WithLabelValues ( labels ... ) . Observe ( float )
}
2018-01-28 13:13:49 +00:00
2018-09-30 22:16:10 +00:00
// CollectLogfileFromFile tails a Postfix log file and collects entries from it.
2020-02-18 14:31:07 +00:00
func ( e * PostfixExporter ) CollectLogfileFromFile ( ctx context . Context ) {
gaugeVec := prometheus . NewGaugeVec (
prometheus . GaugeOpts {
Namespace : "postfix" ,
Subsystem : "" ,
Name : "up" ,
Help : "Whether scraping Postfix's metrics was successful." ,
} ,
[ ] string { "path" } )
2020-02-18 15:54:25 +00:00
gauge := gaugeVec . WithLabelValues ( e . tailer . Filename )
2018-09-30 22:16:10 +00:00
for {
select {
case line := <- e . tailer . Lines :
2020-02-18 14:31:07 +00:00
e . CollectFromLogLine ( line . Text )
case <- ctx . Done ( ) :
gauge . Set ( 0 )
return
2018-09-30 22:16:10 +00:00
}
2020-02-18 14:31:07 +00:00
gauge . Set ( 1 )
2017-05-02 13:07:19 +00:00
}
2017-02-17 14:29:37 +00:00
}
2017-05-02 13:07:19 +00:00
// NewPostfixExporter creates a new Postfix exporter instance.
2020-02-18 15:54:25 +00:00
func NewPostfixExporter ( showqPath string , logfilePath string , journal * Journal , logUnsupportedLines bool ) ( * PostfixExporter , error ) {
2018-09-30 22:16:10 +00:00
var tailer * tail . Tail
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
} )
if err != nil {
return nil , err
}
}
2017-02-17 14:29:37 +00:00
return & PostfixExporter {
2020-02-18 15:54:25 +00:00
logUnsupportedLines : logUnsupportedLines ,
showqPath : showqPath ,
tailer : tailer ,
journal : journal ,
2017-05-02 13:07:19 +00:00
cleanupProcesses : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "cleanup_messages_processed_total" ,
Help : "Total number of messages processed by cleanup." ,
} ) ,
cleanupRejects : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "cleanup_messages_rejected_total" ,
Help : "Total number of messages rejected by cleanup." ,
} ) ,
2019-03-07 16:59:04 +00:00
cleanupNotAccepted : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "cleanup_messages_not_accepted_total" ,
Help : "Total number of messages not accepted by cleanup." ,
} ) ,
2017-05-02 13:07:19 +00:00
lmtpDelays : prometheus . NewHistogramVec (
prometheus . HistogramOpts {
Namespace : "postfix" ,
Name : "lmtp_delivery_delay_seconds" ,
Help : "LMTP message processing time in seconds." ,
Buckets : [ ] float64 { 1e-3 , 1e-2 , 1e-1 , 1e0 , 1e1 , 1e2 , 1e3 } ,
} ,
[ ] string { "stage" } ) ,
pipeDelays : prometheus . NewHistogramVec (
prometheus . HistogramOpts {
Namespace : "postfix" ,
Name : "pipe_delivery_delay_seconds" ,
Help : "Pipe message processing time in seconds." ,
Buckets : [ ] float64 { 1e-3 , 1e-2 , 1e-1 , 1e0 , 1e1 , 1e2 , 1e3 } ,
} ,
[ ] string { "relay" , "stage" } ) ,
qmgrInsertsNrcpt : prometheus . NewHistogram ( prometheus . HistogramOpts {
Namespace : "postfix" ,
Name : "qmgr_messages_inserted_receipients" ,
Help : "Number of receipients per message inserted into the mail queues." ,
Buckets : [ ] float64 { 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 } ,
} ) ,
qmgrInsertsSize : prometheus . NewHistogram ( prometheus . HistogramOpts {
Namespace : "postfix" ,
Name : "qmgr_messages_inserted_size_bytes" ,
Help : "Size of messages inserted into the mail queues in bytes." ,
Buckets : [ ] float64 { 1e3 , 1e4 , 1e5 , 1e6 , 1e7 , 1e8 , 1e9 } ,
} ) ,
qmgrRemoves : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "qmgr_messages_removed_total" ,
Help : "Total number of messages removed from mail queues." ,
} ) ,
smtpDelays : prometheus . NewHistogramVec (
prometheus . HistogramOpts {
Namespace : "postfix" ,
Name : "smtp_delivery_delay_seconds" ,
Help : "SMTP message processing time in seconds." ,
Buckets : [ ] float64 { 1e-3 , 1e-2 , 1e-1 , 1e0 , 1e1 , 1e2 , 1e3 } ,
} ,
[ ] string { "stage" } ) ,
smtpTLSConnects : prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtp_tls_connections_total" ,
Help : "Total number of outgoing TLS connections." ,
} ,
[ ] string { "trust" , "protocol" , "cipher" , "secret_bits" , "algorithm_bits" } ) ,
2019-03-07 16:59:04 +00:00
smtpDeferreds : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtp_deferred_messages_total" ,
Help : "Total number of messages that have been deferred on SMTP." ,
} ) ,
2020-02-18 15:54:25 +00:00
smtpConnectionTimedOut : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtp_connection_timed_out_total" ,
Help : "Total number of messages that have been deferred on SMTP." ,
} ) ,
2017-05-02 13:07:19 +00:00
smtpdConnects : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtpd_connects_total" ,
Help : "Total number of incoming connections." ,
} ) ,
smtpdDisconnects : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtpd_disconnects_total" ,
Help : "Total number of incoming disconnections." ,
} ) ,
smtpdFCrDNSErrors : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
2017-06-01 12:30:55 +00:00
Name : "smtpd_forward_confirmed_reverse_dns_errors_total" ,
2017-05-02 13:07:19 +00:00
Help : "Total number of connections for which forward-confirmed DNS cannot be resolved." ,
} ) ,
smtpdLostConnections : prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtpd_connections_lost_total" ,
Help : "Total number of connections lost." ,
} ,
[ ] string { "after_stage" } ) ,
smtpdProcesses : prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtpd_messages_processed_total" ,
Help : "Total number of messages processed." ,
} ,
[ ] string { "sasl_username" } ) ,
smtpdRejects : prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtpd_messages_rejected_total" ,
Help : "Total number of NOQUEUE rejects." ,
} ,
[ ] string { "code" } ) ,
smtpdSASLAuthenticationFailures : prometheus . NewCounter ( prometheus . CounterOpts {
Namespace : "postfix" ,
2017-06-01 12:26:00 +00:00
Name : "smtpd_sasl_authentication_failures_total" ,
2017-05-02 13:07:19 +00:00
Help : "Total number of SASL authentication failures." ,
} ) ,
smtpdTLSConnects : prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "smtpd_tls_connections_total" ,
Help : "Total number of incoming TLS connections." ,
} ,
[ ] string { "trust" , "protocol" , "cipher" , "secret_bits" , "algorithm_bits" } ) ,
unsupportedLogEntries : prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : "postfix" ,
Name : "unsupported_log_entries_total" ,
Help : "Log entries that could not be processed." ,
} ,
[ ] string { "service" } ) ,
2019-04-15 17:48:49 +00:00
smtpStatusDeferred : prometheus . NewCounter ( prometheus . CounterOpts {
2019-04-15 16:46:11 +00:00
Namespace : "postfix" ,
2019-04-15 17:48:49 +00:00
Name : "smtp_status_deferred" ,
2019-04-15 16:46:11 +00:00
Help : "Total number of messages deferred." ,
} ) ,
2020-02-18 15:54:25 +00:00
opendkimSignatureAdded : prometheus . NewCounterVec (
prometheus . CounterOpts {
Namespace : "opendkim" ,
Name : "signatures_added_total" ,
Help : "Total number of messages signed." ,
} ,
[ ] string { "subject" , "domain" } ,
) ,
2017-02-17 14:29:37 +00:00
} , nil
}
2017-05-02 13:07:19 +00:00
// Describe the Prometheus metrics that are going to be exported.
2017-02-17 14:29:37 +00:00
func ( e * PostfixExporter ) Describe ( ch chan <- * prometheus . Desc ) {
ch <- postfixUpDesc
2017-05-02 13:07:19 +00:00
ch <- e . cleanupProcesses . Desc ( )
ch <- e . cleanupRejects . Desc ( )
2019-03-07 16:59:04 +00:00
ch <- e . cleanupNotAccepted . Desc ( )
2017-05-02 13:07:19 +00:00
e . lmtpDelays . Describe ( ch )
e . pipeDelays . Describe ( ch )
ch <- e . qmgrInsertsNrcpt . Desc ( )
ch <- e . qmgrInsertsSize . Desc ( )
ch <- e . qmgrRemoves . Desc ( )
e . smtpDelays . Describe ( ch )
e . smtpTLSConnects . Describe ( ch )
2019-03-07 16:59:04 +00:00
ch <- e . smtpDeferreds . Desc ( )
2017-05-02 13:07:19 +00:00
ch <- e . smtpdConnects . Desc ( )
ch <- e . smtpdDisconnects . Desc ( )
ch <- e . smtpdFCrDNSErrors . Desc ( )
e . smtpdLostConnections . Describe ( ch )
e . smtpdProcesses . Describe ( ch )
e . smtpdRejects . Describe ( ch )
ch <- e . smtpdSASLAuthenticationFailures . Desc ( )
e . smtpdTLSConnects . Describe ( ch )
2019-04-15 17:48:49 +00:00
ch <- e . smtpStatusDeferred . Desc ( )
2017-05-02 13:07:19 +00:00
e . unsupportedLogEntries . Describe ( ch )
2017-02-17 14:29:37 +00:00
}
2020-02-18 14:31:07 +00:00
func ( e * PostfixExporter ) foreverCollectFromJournal ( ctx context . Context ) {
gauge := prometheus . NewGaugeVec (
prometheus . GaugeOpts {
Namespace : "postfix" ,
Subsystem : "" ,
Name : "up" ,
Help : "Whether scraping Postfix's metrics was successful." ,
} ,
2020-02-18 15:54:25 +00:00
[ ] string { "path" } ) . WithLabelValues ( e . journal . Path )
2020-02-18 14:31:07 +00:00
select {
case <- ctx . Done ( ) :
gauge . Set ( 0 )
return
default :
err := e . CollectLogfileFromJournal ( )
if err != nil {
log . Printf ( "Couldn't read journal: %v" , err )
gauge . Set ( 0 )
} else {
gauge . Set ( 1 )
}
}
}
func ( e * PostfixExporter ) StartMetricCollection ( ctx context . Context ) {
if e . journal != nil {
e . foreverCollectFromJournal ( ctx )
} else {
e . CollectLogfileFromFile ( ctx )
}
prometheus . NewGaugeVec (
prometheus . GaugeOpts {
Namespace : "postfix" ,
Subsystem : "" ,
Name : "up" ,
Help : "Whether scraping Postfix's metrics was successful." ,
} ,
[ ] string { "path" } )
return
}
2017-05-02 13:07:19 +00:00
// Collect metrics from Postfix's showq socket and its log file.
2017-02-17 14:29:37 +00:00
func ( e * PostfixExporter ) Collect ( ch chan <- prometheus . Metric ) {
err := CollectShowqFromSocket ( e . showqPath , ch )
if err == nil {
ch <- prometheus . MustNewConstMetric (
postfixUpDesc ,
prometheus . GaugeValue ,
2017-05-02 13:07:19 +00:00
1.0 ,
e . showqPath )
2017-02-17 14:29:37 +00:00
} else {
log . Printf ( "Failed to scrape showq socket: %s" , err )
ch <- prometheus . MustNewConstMetric (
postfixUpDesc ,
prometheus . GaugeValue ,
2017-05-02 13:07:19 +00:00
0.0 ,
e . showqPath )
2017-02-17 14:29:37 +00:00
}
2017-05-02 13:07:19 +00:00
ch <- e . cleanupProcesses
ch <- e . cleanupRejects
2019-03-07 16:59:04 +00:00
ch <- e . cleanupNotAccepted
2017-05-02 13:07:19 +00:00
e . lmtpDelays . Collect ( ch )
e . pipeDelays . Collect ( ch )
ch <- e . qmgrInsertsNrcpt
ch <- e . qmgrInsertsSize
ch <- e . qmgrRemoves
e . smtpDelays . Collect ( ch )
e . smtpTLSConnects . Collect ( ch )
2019-03-07 16:59:04 +00:00
ch <- e . smtpDeferreds
2017-05-02 13:07:19 +00:00
ch <- e . smtpdConnects
ch <- e . smtpdDisconnects
ch <- e . smtpdFCrDNSErrors
e . smtpdLostConnections . Collect ( ch )
e . smtpdProcesses . Collect ( ch )
e . smtpdRejects . Collect ( ch )
ch <- e . smtpdSASLAuthenticationFailures
e . smtpdTLSConnects . Collect ( ch )
2019-04-15 17:48:49 +00:00
ch <- e . smtpStatusDeferred
2017-05-02 13:07:19 +00:00
e . unsupportedLogEntries . Collect ( ch )
2020-02-18 15:54:25 +00:00
ch <- e . smtpConnectionTimedOut
e . opendkimSignatureAdded . Collect ( ch )
2017-02-17 14:29:37 +00:00
}
func main ( ) {
var (
2019-02-15 09:41:53 +00:00
app = kingpin . New ( "postfix_exporter" , "Prometheus metrics exporter for postfix" )
listenAddress = app . Flag ( "web.listen-address" , "Address to listen on for web interface and telemetry." ) . Default ( ":9154" ) . String ( )
metricsPath = app . Flag ( "web.telemetry-path" , "Path under which to expose metrics." ) . Default ( "/metrics" ) . String ( )
postfixShowqPath = app . Flag ( "postfix.showq_path" , "Path at which Postfix places its showq socket." ) . Default ( "/var/spool/postfix/public/showq" ) . String ( )
postfixLogfilePath = app . Flag ( "postfix.logfile_path" , "Path where Postfix writes log entries. This file will be truncated by this exporter." ) . Default ( "/var/log/postfix_exporter_input.log" ) . String ( )
2020-02-18 15:54:25 +00:00
logUnsupportedLines = app . Flag ( "log.unsupported" , "Log all unsupported lines." ) . Bool ( )
2018-04-15 13:03:36 +00:00
systemdEnable bool
systemdUnit , systemdSlice , systemdJournalPath string
2017-02-17 14:29:37 +00:00
)
2018-12-17 16:01:01 +00:00
systemdFlags ( & systemdEnable , & systemdUnit , & systemdSlice , & systemdJournalPath , app )
kingpin . MustParse ( app . Parse ( os . Args [ 1 : ] ) )
2017-02-17 14:29:37 +00:00
2018-01-28 13:13:49 +00:00
var journal * Journal
2018-04-15 13:03:36 +00:00
if systemdEnable {
2018-01-28 13:13:49 +00:00
var err error
2018-04-15 13:03:36 +00:00
journal , err = NewJournal ( systemdUnit , systemdSlice , systemdJournalPath )
2018-01-28 13:13:49 +00:00
if err != nil {
log . Fatalf ( "Error opening systemd journal: %s" , err )
}
defer journal . Close ( )
2020-02-12 12:39:13 +00:00
log . Println ( "Reading log events from systemd" )
} else {
log . Printf ( "Reading log events from %v" , * postfixLogfilePath )
2018-01-28 13:13:49 +00:00
}
exporter , err := NewPostfixExporter (
* postfixShowqPath ,
* postfixLogfilePath ,
journal ,
2020-02-18 15:54:25 +00:00
* logUnsupportedLines ,
2018-01-28 13:13:49 +00:00
)
2017-02-17 14:29:37 +00:00
if err != nil {
2018-09-30 22:16:10 +00:00
log . Fatalf ( "Failed to create PostfixExporter: %s" , err )
2017-02-17 14:29:37 +00:00
}
prometheus . MustRegister ( exporter )
2019-06-20 14:45:51 +00:00
http . Handle ( * metricsPath , promhttp . Handler ( ) )
2017-02-17 14:29:37 +00:00
http . HandleFunc ( "/" , func ( w http . ResponseWriter , r * http . Request ) {
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
_ , err = w . Write ( [ ] byte ( `
2017-02-17 14:29:37 +00:00
< html >
< head > < title > Postfix Exporter < / title > < / head >
< body >
< h1 > Postfix Exporter < / h1 >
< p > < a href = ' ` + *metricsPath + ` ' > Metrics < / a > < / p >
< / body >
< / html > ` ) )
postfix_exporter.go: Fix some gosec issues.
See,
$ gometalinter --vendor ./...
postfix_exporter.go:249::warning: Potential file inclusion via variable,MEDIUM,HIGH (gosec)
postfix_exporter.go:80::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:121::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:296::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:298::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:300::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:302::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:309::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:311::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:313::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:315::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:322::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:324::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:333::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:335::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:337::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:339::warning: Errors unhandled.,LOW,HIGH (gosec)
postfix_exporter.go:650::warning: Errors unhandled.,LOW,HIGH (gosec)
2018-12-04 15:10:28 +00:00
if err != nil {
panic ( err )
}
2017-02-17 14:29:37 +00:00
} )
2020-02-18 14:31:07 +00:00
ctx , cancelFunc := context . WithCancel ( context . Background ( ) )
defer cancelFunc ( )
go exporter . StartMetricCollection ( ctx )
2018-01-28 13:13:49 +00:00
log . Print ( "Listening on " , * listenAddress )
2017-02-17 14:29:37 +00:00
log . Fatal ( http . ListenAndServe ( * listenAddress , nil ) )
}