Also start scraping Postfix's log file for more relevant stats.
Queue stats on their own are a bit restrictive. Also go ahead and scrape the Postfix logs to get more relevant metrics.
This commit is contained in:
parent
2e75bb4583
commit
9b56cf9737
20
README.md
20
README.md
@ -1,10 +1,20 @@
|
|||||||
# Prometheus Postfix exporter
|
# Prometheus Postfix exporter
|
||||||
|
|
||||||
This repository provides code for a simple Prometheus metrics exporter
|
This repository provides code for a Prometheus metrics exporter
|
||||||
for [the Postfix mail server](http://www.postfix.org/). Right now, this
|
for [the Postfix mail server](http://www.postfix.org/). This exporter
|
||||||
exporter only provides histogram metrics for the size and age of
|
provides histogram metrics for the size and age of messages stored in
|
||||||
messages stored in the mail queue. It extracts these metrics from
|
the mail queue. It extracts these metrics from Postfix by connecting to
|
||||||
Postfix by connecting to a UNIX socket under `/var/spool`.
|
a UNIX socket under `/var/spool`.
|
||||||
|
|
||||||
|
In addition to that, it counts events by parsing Postfix's log file,
|
||||||
|
using regular expression matching. It truncates the log file when
|
||||||
|
processed, so that the next iteration doesn't interpret the same lines
|
||||||
|
twice. It makes sense to configure your syslogger to multiplex log
|
||||||
|
entries to a second file:
|
||||||
|
|
||||||
|
```
|
||||||
|
mail.* -/var/log/postfix_exporter_input.log
|
||||||
|
```
|
||||||
|
|
||||||
Please refer to this utility's `main()` function for a list of supported
|
Please refer to this utility's `main()` function for a list of supported
|
||||||
command line flags.
|
command line flags.
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@ -35,10 +36,38 @@ var (
|
|||||||
postfixUpDesc = prometheus.NewDesc(
|
postfixUpDesc = prometheus.NewDesc(
|
||||||
prometheus.BuildFQName("postfix", "", "up"),
|
prometheus.BuildFQName("postfix", "", "up"),
|
||||||
"Whether scraping Postfix's metrics was successful.",
|
"Whether scraping Postfix's metrics was successful.",
|
||||||
nil, nil)
|
[]string{"path"}, nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parses the output of Postfix's 'showq' command and turns it into metrics.
|
// PostfixExporter holds the state that should be preserved by the
|
||||||
|
// Postfix Prometheus metrics exporter across scrapes.
|
||||||
|
type PostfixExporter struct {
|
||||||
|
showqPath string
|
||||||
|
logfilePath string
|
||||||
|
|
||||||
|
// Metrics that should persist after refreshes, based on logs.
|
||||||
|
cleanupProcesses prometheus.Counter
|
||||||
|
cleanupRejects prometheus.Counter
|
||||||
|
lmtpDelays *prometheus.HistogramVec
|
||||||
|
pipeDelays *prometheus.HistogramVec
|
||||||
|
qmgrInsertsNrcpt prometheus.Histogram
|
||||||
|
qmgrInsertsSize prometheus.Histogram
|
||||||
|
qmgrRemoves prometheus.Counter
|
||||||
|
smtpDelays *prometheus.HistogramVec
|
||||||
|
smtpTLSConnects *prometheus.CounterVec
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectShowqFromReader parses the output of Postfix's 'showq' command
|
||||||
|
// and turns it into metrics.
|
||||||
//
|
//
|
||||||
// The output format of this command depends on the version of Postfix
|
// 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
|
// used. Postfix 2.x uses a textual format, identical to the output of
|
||||||
@ -50,12 +79,11 @@ func CollectShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
|
|||||||
buf, _ := reader.Peek(128)
|
buf, _ := reader.Peek(128)
|
||||||
if bytes.IndexByte(buf, 0) >= 0 {
|
if bytes.IndexByte(buf, 0) >= 0 {
|
||||||
return CollectBinaryShowqFromReader(reader, ch)
|
return CollectBinaryShowqFromReader(reader, ch)
|
||||||
} else {
|
|
||||||
return CollectTextualShowqFromReader(reader, ch)
|
|
||||||
}
|
}
|
||||||
|
return CollectTextualShowqFromReader(reader, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses Postfix's textual showq output.
|
// CollectTextualShowqFromReader parses Postfix's textual showq output.
|
||||||
func CollectTextualShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
|
func CollectTextualShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
scanner.Split(bufio.ScanLines)
|
scanner.Split(bufio.ScanLines)
|
||||||
@ -123,7 +151,8 @@ func CollectTextualShowqFromReader(file io.Reader, ch chan<- prometheus.Metric)
|
|||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splitting function for bufio.Scanner to split entries by null bytes.
|
// ScanNullTerminatedEntries is a splitting function for bufio.Scanner
|
||||||
|
// to split entries by null bytes.
|
||||||
func ScanNullTerminatedEntries(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
func ScanNullTerminatedEntries(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||||
if i := bytes.IndexByte(data, 0); i >= 0 {
|
if i := bytes.IndexByte(data, 0); i >= 0 {
|
||||||
// Valid record found.
|
// Valid record found.
|
||||||
@ -137,7 +166,7 @@ func ScanNullTerminatedEntries(data []byte, atEOF bool) (advance int, token []by
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses Postfix's binary format.
|
// CollectBinaryShowqFromReader parses Postfix's binary showq format.
|
||||||
func CollectBinaryShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
|
func CollectBinaryShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
scanner.Split(ScanNullTerminatedEntries)
|
scanner.Split(ScanNullTerminatedEntries)
|
||||||
@ -171,7 +200,7 @@ func CollectBinaryShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) e
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
return fmt.Errorf("Key %q does not have a value\n", key)
|
return fmt.Errorf("key %q does not have a value", key)
|
||||||
}
|
}
|
||||||
value := scanner.Text()
|
value := scanner.Text()
|
||||||
|
|
||||||
@ -200,50 +229,357 @@ func CollectBinaryShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) e
|
|||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectShowqFromFile collects Postfix queue statistics from a file.
|
||||||
func CollectShowqFromFile(path string, ch chan<- prometheus.Metric) error {
|
func CollectShowqFromFile(path string, ch chan<- prometheus.Metric) error {
|
||||||
conn, err := os.Open(path)
|
fd, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return CollectShowqFromReader(conn, ch)
|
defer fd.Close()
|
||||||
|
return CollectShowqFromReader(fd, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectShowqFromSocket collects Postfix queue statistics from a socket.
|
||||||
func CollectShowqFromSocket(path string, ch chan<- prometheus.Metric) error {
|
func CollectShowqFromSocket(path string, ch chan<- prometheus.Metric) error {
|
||||||
conn, err := net.Dial("unix", path)
|
fd, err := net.Dial("unix", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return CollectShowqFromReader(conn, ch)
|
defer fd.Close()
|
||||||
|
return CollectShowqFromReader(fd, ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostfixExporter struct {
|
// CollectLogfileFromReader collects metrics from a Postfix logfile,
|
||||||
showqPath string
|
// using a reader object.
|
||||||
|
func (e *PostfixExporter) CollectLogfileFromReader(file io.Reader) error {
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
scanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
// Patterns for parsing log messages.
|
||||||
|
logLine := regexp.MustCompile(" postfix/(\\w+)\\[\\d+\\]: (.*)")
|
||||||
|
lmtpPipeSMTPLine := regexp.MustCompile(", relay=(\\S+), .*, delays=([0-9\\.]+)/([0-9\\.]+)/([0-9\\.]+)/([0-9\\.]+), ")
|
||||||
|
qmgrInsertLine := regexp.MustCompile(":.*, size=(\\d+), nrcpt=(\\d+) ")
|
||||||
|
smtpTLSLine := regexp.MustCompile("^(\\S+) TLS connection established to \\S+: (\\S+) with cipher (\\S+) \\((\\d+)/(\\d+) bits\\)$")
|
||||||
|
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\\)$")
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
// Strip off timestamp, hostname, etc.
|
||||||
|
if logMatches := logLine.FindStringSubmatch(scanner.Text()); logMatches != nil {
|
||||||
|
// Group patterns to check by Postfix service.
|
||||||
|
if logMatches[1] == "cleanup" {
|
||||||
|
if strings.Contains(logMatches[2], ": message-id=<") {
|
||||||
|
e.cleanupProcesses.Inc()
|
||||||
|
} else if strings.Contains(logMatches[2], ": reject: ") {
|
||||||
|
e.cleanupRejects.Inc()
|
||||||
|
} else {
|
||||||
|
e.unsupportedLogEntries.WithLabelValues(logMatches[1]).Inc()
|
||||||
|
}
|
||||||
|
} else if logMatches[1] == "lmtp" {
|
||||||
|
if lmtpMatches := lmtpPipeSMTPLine.FindStringSubmatch(logMatches[2]); lmtpMatches != nil {
|
||||||
|
pdelay, _ := strconv.ParseFloat(lmtpMatches[2], 64)
|
||||||
|
e.lmtpDelays.WithLabelValues("before_queue_manager").Observe(pdelay)
|
||||||
|
adelay, _ := strconv.ParseFloat(lmtpMatches[3], 64)
|
||||||
|
e.lmtpDelays.WithLabelValues("queue_manager").Observe(adelay)
|
||||||
|
sdelay, _ := strconv.ParseFloat(lmtpMatches[4], 64)
|
||||||
|
e.lmtpDelays.WithLabelValues("connection_setup").Observe(sdelay)
|
||||||
|
xdelay, _ := strconv.ParseFloat(lmtpMatches[5], 64)
|
||||||
|
e.lmtpDelays.WithLabelValues("transmission").Observe(xdelay)
|
||||||
|
} else {
|
||||||
|
e.unsupportedLogEntries.WithLabelValues(logMatches[1]).Inc()
|
||||||
|
}
|
||||||
|
} else if logMatches[1] == "pipe" {
|
||||||
|
if pipeMatches := lmtpPipeSMTPLine.FindStringSubmatch(logMatches[2]); pipeMatches != nil {
|
||||||
|
pdelay, _ := strconv.ParseFloat(pipeMatches[2], 64)
|
||||||
|
e.pipeDelays.WithLabelValues(pipeMatches[1], "before_queue_manager").Observe(pdelay)
|
||||||
|
adelay, _ := strconv.ParseFloat(pipeMatches[3], 64)
|
||||||
|
e.pipeDelays.WithLabelValues(pipeMatches[1], "queue_manager").Observe(adelay)
|
||||||
|
sdelay, _ := strconv.ParseFloat(pipeMatches[4], 64)
|
||||||
|
e.pipeDelays.WithLabelValues(pipeMatches[1], "connection_setup").Observe(sdelay)
|
||||||
|
xdelay, _ := strconv.ParseFloat(pipeMatches[5], 64)
|
||||||
|
e.pipeDelays.WithLabelValues(pipeMatches[1], "transmission").Observe(xdelay)
|
||||||
|
} else {
|
||||||
|
e.unsupportedLogEntries.WithLabelValues(logMatches[1]).Inc()
|
||||||
|
}
|
||||||
|
} else if logMatches[1] == "qmgr" {
|
||||||
|
if qmgrInsertMatches := qmgrInsertLine.FindStringSubmatch(logMatches[2]); qmgrInsertMatches != nil {
|
||||||
|
size, _ := strconv.ParseFloat(qmgrInsertMatches[1], 64)
|
||||||
|
e.qmgrInsertsSize.Observe(size)
|
||||||
|
nrcpt, _ := strconv.ParseFloat(qmgrInsertMatches[2], 64)
|
||||||
|
e.qmgrInsertsNrcpt.Observe(nrcpt)
|
||||||
|
} else if strings.HasSuffix(logMatches[2], ": removed") {
|
||||||
|
e.qmgrRemoves.Inc()
|
||||||
|
} else {
|
||||||
|
e.unsupportedLogEntries.WithLabelValues(logMatches[1]).Inc()
|
||||||
|
}
|
||||||
|
} else if logMatches[1] == "smtp" {
|
||||||
|
if smtpMatches := lmtpPipeSMTPLine.FindStringSubmatch(logMatches[2]); smtpMatches != nil {
|
||||||
|
pdelay, _ := strconv.ParseFloat(smtpMatches[2], 64)
|
||||||
|
e.smtpDelays.WithLabelValues("before_queue_manager").Observe(pdelay)
|
||||||
|
adelay, _ := strconv.ParseFloat(smtpMatches[3], 64)
|
||||||
|
e.smtpDelays.WithLabelValues("queue_manager").Observe(adelay)
|
||||||
|
sdelay, _ := strconv.ParseFloat(smtpMatches[4], 64)
|
||||||
|
e.smtpDelays.WithLabelValues("connection_setup").Observe(sdelay)
|
||||||
|
xdelay, _ := strconv.ParseFloat(smtpMatches[5], 64)
|
||||||
|
e.smtpDelays.WithLabelValues("transmission").Observe(xdelay)
|
||||||
|
} else if smtpTLSMatches := smtpTLSLine.FindStringSubmatch(logMatches[2]); smtpTLSMatches != nil {
|
||||||
|
e.smtpTLSConnects.WithLabelValues(smtpTLSMatches[1:]...).Inc()
|
||||||
|
} else {
|
||||||
|
e.unsupportedLogEntries.WithLabelValues(logMatches[1]).Inc()
|
||||||
|
}
|
||||||
|
} else if logMatches[1] == "smtpd" {
|
||||||
|
if strings.HasPrefix(logMatches[2], "connect from ") {
|
||||||
|
e.smtpdConnects.Inc()
|
||||||
|
} else if strings.HasPrefix(logMatches[2], "disconnect from ") {
|
||||||
|
e.smtpdDisconnects.Inc()
|
||||||
|
} else if smtpdFCrDNSErrorsLine.MatchString(logMatches[2]) {
|
||||||
|
e.smtpdFCrDNSErrors.Inc()
|
||||||
|
} else if smtpdLostConnectionMatches := smtpdLostConnectionLine.FindStringSubmatch(logMatches[2]); smtpdLostConnectionMatches != nil {
|
||||||
|
e.smtpdLostConnections.WithLabelValues(smtpdLostConnectionMatches[1]).Inc()
|
||||||
|
} else if smtpdProcessesSASLMatches := smtpdProcessesSASLLine.FindStringSubmatch(logMatches[2]); smtpdProcessesSASLMatches != nil {
|
||||||
|
e.smtpdProcesses.WithLabelValues(smtpdProcessesSASLMatches[1]).Inc()
|
||||||
|
} else if strings.Contains(logMatches[2], ": client=") {
|
||||||
|
e.smtpdProcesses.WithLabelValues("").Inc()
|
||||||
|
} else if smtpdRejectsMatches := smtpdRejectsLine.FindStringSubmatch(logMatches[2]); smtpdRejectsMatches != nil {
|
||||||
|
e.smtpdRejects.WithLabelValues(smtpdRejectsMatches[1]).Inc()
|
||||||
|
} else if smtpdSASLAuthenticationFailuresLine.MatchString(logMatches[2]) {
|
||||||
|
e.smtpdSASLAuthenticationFailures.Inc()
|
||||||
|
} else if smtpdTLSMatches := smtpdTLSLine.FindStringSubmatch(logMatches[2]); smtpdTLSMatches != nil {
|
||||||
|
e.smtpdTLSConnects.WithLabelValues(smtpdTLSMatches[1:]...).Inc()
|
||||||
|
} else {
|
||||||
|
e.unsupportedLogEntries.WithLabelValues(logMatches[1]).Inc()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unknown Postfix service.
|
||||||
|
e.unsupportedLogEntries.WithLabelValues(logMatches[1]).Inc()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unknown log entry format.
|
||||||
|
e.unsupportedLogEntries.WithLabelValues("").Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPostfixExporter(showqPath string) (*PostfixExporter, error) {
|
// CollectLogfileFromFile Collects entries from a Postfix log file and
|
||||||
|
// truncates it. Truncation is performed to ensure that the next
|
||||||
|
// iteration doesn't end up processing the same log entry twice.
|
||||||
|
func (e *PostfixExporter) CollectLogfileFromFile(path string) error {
|
||||||
|
fd, err := os.OpenFile(path, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
err = e.CollectLogfileFromReader(fd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fd.Truncate(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPostfixExporter creates a new Postfix exporter instance.
|
||||||
|
func NewPostfixExporter(showqPath string, logfilePath string) (*PostfixExporter, error) {
|
||||||
return &PostfixExporter{
|
return &PostfixExporter{
|
||||||
showqPath: showqPath,
|
showqPath: showqPath,
|
||||||
|
logfilePath: logfilePath,
|
||||||
|
|
||||||
|
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.",
|
||||||
|
}),
|
||||||
|
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"}),
|
||||||
|
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",
|
||||||
|
Name: "smtpd_forward_confirmed_reverse_dns_errors",
|
||||||
|
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",
|
||||||
|
Name: "smtpd_sasl_authentication_failures",
|
||||||
|
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"}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Describe the Prometheus metrics that are going to be exported.
|
||||||
func (e *PostfixExporter) Describe(ch chan<- *prometheus.Desc) {
|
func (e *PostfixExporter) Describe(ch chan<- *prometheus.Desc) {
|
||||||
ch <- postfixUpDesc
|
ch <- postfixUpDesc
|
||||||
|
|
||||||
|
ch <- e.cleanupProcesses.Desc()
|
||||||
|
ch <- e.cleanupRejects.Desc()
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
e.unsupportedLogEntries.Describe(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect metrics from Postfix's showq socket and its log file.
|
||||||
func (e *PostfixExporter) Collect(ch chan<- prometheus.Metric) {
|
func (e *PostfixExporter) Collect(ch chan<- prometheus.Metric) {
|
||||||
err := CollectShowqFromSocket(e.showqPath, ch)
|
err := CollectShowqFromSocket(e.showqPath, ch)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
postfixUpDesc,
|
postfixUpDesc,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
1.0)
|
1.0,
|
||||||
|
e.showqPath)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Failed to scrape showq socket: %s", err)
|
log.Printf("Failed to scrape showq socket: %s", err)
|
||||||
ch <- prometheus.MustNewConstMetric(
|
ch <- prometheus.MustNewConstMetric(
|
||||||
postfixUpDesc,
|
postfixUpDesc,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
0.0)
|
0.0,
|
||||||
|
e.showqPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = e.CollectLogfileFromFile(e.logfilePath)
|
||||||
|
if err == nil {
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
postfixUpDesc,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
1.0,
|
||||||
|
e.logfilePath)
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed to scrape logfile: %s", err)
|
||||||
|
ch <- prometheus.MustNewConstMetric(
|
||||||
|
postfixUpDesc,
|
||||||
|
prometheus.GaugeValue,
|
||||||
|
0.0,
|
||||||
|
e.logfilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- e.cleanupProcesses
|
||||||
|
ch <- e.cleanupRejects
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
e.unsupportedLogEntries.Collect(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -251,10 +587,11 @@ func main() {
|
|||||||
listenAddress = flag.String("web.listen-address", ":9154", "Address to listen on for web interface and telemetry.")
|
listenAddress = flag.String("web.listen-address", ":9154", "Address to listen on for web interface and telemetry.")
|
||||||
metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
|
metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
|
||||||
postfixShowqPath = flag.String("postfix.showq_path", "/var/spool/postfix/public/showq", "Path at which Postfix places its showq socket.")
|
postfixShowqPath = flag.String("postfix.showq_path", "/var/spool/postfix/public/showq", "Path at which Postfix places its showq socket.")
|
||||||
|
postfixLogfilePath = flag.String("postfix.logfile_path", "/var/log/postfix_exporter_input.log", "Path where Postfix writes log entries. This file will be truncated by this exporter.")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
exporter, err := NewPostfixExporter(*postfixShowqPath)
|
exporter, err := NewPostfixExporter(*postfixShowqPath, *postfixLogfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user