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:
Ed Schouten 2017-05-02 15:07:19 +02:00
parent 2e75bb4583
commit 9b56cf9737
2 changed files with 374 additions and 27 deletions

View File

@ -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.

View File

@ -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)
} }