Merge branch 'master' into tail
This commit is contained in:
commit
a4e09a2c37
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,2 +1,9 @@
|
||||
# Editor files
|
||||
*~
|
||||
-.idea/
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Binaries
|
||||
postfix_exporter
|
||||
.idea/
|
||||
|
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
FROM golang:1.8
|
||||
ADD . /go/src/github.com/kumina/postfix_exporter
|
||||
WORKDIR /go/src/github.com/kumina/postfix_exporter
|
||||
RUN apt-get update -qq && apt-get install -qqy \
|
||||
build-essential \
|
||||
libsystemd-dev
|
||||
RUN go get -v ./...
|
||||
RUN go build
|
||||
|
||||
FROM debian:latest
|
||||
EXPOSE 9154
|
||||
WORKDIR /
|
||||
COPY --from=0 /go/src/github.com/kumina/postfix_exporter/postfix_exporter .
|
||||
ENTRYPOINT ["/postfix_exporter"]
|
@ -3,14 +3,19 @@
|
||||
|
||||
package main
|
||||
|
||||
import "io"
|
||||
import(
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
)
|
||||
|
||||
|
||||
type Journal struct {
|
||||
io.Closer
|
||||
Path string
|
||||
}
|
||||
|
||||
func systemdFlags(enable *bool, unit, slice, path *string) {}
|
||||
func systemdFlags(enable *bool, unit, slice, path *string, app *kingpin.Application) {}
|
||||
|
||||
func NewJournal(unit, slice, path string) (*Journal, error) {
|
||||
return nil, nil
|
||||
|
@ -17,8 +17,8 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/alecthomas/kingpin"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
@ -78,7 +78,10 @@ type PostfixExporter struct {
|
||||
// for null bytes in the first 128 bytes of output.
|
||||
func CollectShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
|
||||
reader := bufio.NewReader(file)
|
||||
buf, _ := reader.Peek(128)
|
||||
buf, err := reader.Peek(128)
|
||||
if err != nil && err != io.EOF {
|
||||
log.Printf("Could not read postfix output, %v", err)
|
||||
}
|
||||
if bytes.IndexByte(buf, 0) >= 0 {
|
||||
return CollectBinaryShowqFromReader(reader, ch)
|
||||
}
|
||||
@ -92,7 +95,7 @@ func CollectTextualShowqFromReader(file io.Reader, ch chan<- prometheus.Metric)
|
||||
|
||||
// Regular expression for matching postqueue's output. Example:
|
||||
// "A07A81514 5156 Tue Feb 14 13:13:54 MAILER-DAEMON"
|
||||
messageLine := regexp.MustCompile("^[0-9A-F]+([\\*!]?) +(\\d+) (\\w{3} \\w{3} +\\d+ +\\d+:\\d{2}:\\d{2}) +")
|
||||
messageLine := regexp.MustCompile(`^[0-9A-F]+([\*!]?) +(\d+) (\w{3} \w{3} +\d+ +\d+:\d{2}:\d{2}) +`)
|
||||
|
||||
// Histograms tracking the messages by size and age.
|
||||
sizeHistogram := prometheus.NewHistogramVec(
|
||||
@ -119,7 +122,11 @@ func CollectTextualShowqFromReader(file io.Reader, ch chan<- prometheus.Metric)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
location, _ := time.LoadLocation("Local")
|
||||
location, err := time.LoadLocation("Local")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
for scanner.Scan() {
|
||||
matches := messageLine.FindStringSubmatch(scanner.Text())
|
||||
if matches != nil {
|
||||
@ -232,11 +239,11 @@ func CollectBinaryShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) e
|
||||
sizeHistogram.WithLabelValues(queue).Observe(size)
|
||||
} else if key == "time" {
|
||||
// Message time as a UNIX timestamp.
|
||||
time, err := strconv.ParseFloat(value, 64)
|
||||
utime, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ageHistogram.WithLabelValues(queue).Observe(now - time)
|
||||
ageHistogram.WithLabelValues(queue).Observe(now - utime)
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,14 +253,14 @@ func CollectBinaryShowqFromReader(file io.Reader, ch chan<- prometheus.Metric) e
|
||||
}
|
||||
|
||||
// CollectShowqFromFile collects Postfix queue statistics from a file.
|
||||
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)
|
||||
}
|
||||
//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)
|
||||
//}
|
||||
|
||||
// CollectShowqFromSocket collects Postfix queue statistics from a socket.
|
||||
func CollectShowqFromSocket(path string, ch chan<- prometheus.Metric) error {
|
||||
@ -267,16 +274,16 @@ func CollectShowqFromSocket(path string, ch chan<- prometheus.Metric) error {
|
||||
|
||||
// Patterns for parsing log messages.
|
||||
var (
|
||||
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\\)$")
|
||||
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\)$`)
|
||||
)
|
||||
|
||||
// CollectFromLogline collects metrict from a Postfix log line.
|
||||
@ -294,35 +301,65 @@ func (e *PostfixExporter) CollectFromLogline(line string) {
|
||||
}
|
||||
} else if logMatches[1] == "lmtp" {
|
||||
if lmtpMatches := lmtpPipeSMTPLine.FindStringSubmatch(logMatches[2]); lmtpMatches != nil {
|
||||
pdelay, _ := strconv.ParseFloat(lmtpMatches[2], 64)
|
||||
pdelay, err := strconv.ParseFloat(lmtpMatches[2], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert LMTP pdelay: %v", err)
|
||||
}
|
||||
e.lmtpDelays.WithLabelValues("before_queue_manager").Observe(pdelay)
|
||||
adelay, _ := strconv.ParseFloat(lmtpMatches[3], 64)
|
||||
adelay, err := strconv.ParseFloat(lmtpMatches[3], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert LMTP adelay: %v", err)
|
||||
}
|
||||
e.lmtpDelays.WithLabelValues("queue_manager").Observe(adelay)
|
||||
sdelay, _ := strconv.ParseFloat(lmtpMatches[4], 64)
|
||||
sdelay, err := strconv.ParseFloat(lmtpMatches[4], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert LMTP adelay: %v", err)
|
||||
}
|
||||
e.lmtpDelays.WithLabelValues("connection_setup").Observe(sdelay)
|
||||
xdelay, _ := strconv.ParseFloat(lmtpMatches[5], 64)
|
||||
xdelay, err := strconv.ParseFloat(lmtpMatches[5], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert LMTP xdelay: %v", err)
|
||||
}
|
||||
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)
|
||||
pdelay, err := strconv.ParseFloat(pipeMatches[2], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert PIPE pdelay: %v", err)
|
||||
}
|
||||
e.pipeDelays.WithLabelValues(pipeMatches[1], "before_queue_manager").Observe(pdelay)
|
||||
adelay, _ := strconv.ParseFloat(pipeMatches[3], 64)
|
||||
adelay, err := strconv.ParseFloat(pipeMatches[3], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert PIPE adelay: %v", err)
|
||||
}
|
||||
e.pipeDelays.WithLabelValues(pipeMatches[1], "queue_manager").Observe(adelay)
|
||||
sdelay, _ := strconv.ParseFloat(pipeMatches[4], 64)
|
||||
sdelay, err := strconv.ParseFloat(pipeMatches[4], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert PIPE sdelay: %v", err)
|
||||
}
|
||||
e.pipeDelays.WithLabelValues(pipeMatches[1], "connection_setup").Observe(sdelay)
|
||||
xdelay, _ := strconv.ParseFloat(pipeMatches[5], 64)
|
||||
xdelay, err := strconv.ParseFloat(pipeMatches[5], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert PIPE xdelay: %v", err)
|
||||
}
|
||||
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)
|
||||
size, err := strconv.ParseFloat(qmgrInsertMatches[1], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert QMGR size: %v", err)
|
||||
}
|
||||
e.qmgrInsertsSize.Observe(size)
|
||||
nrcpt, _ := strconv.ParseFloat(qmgrInsertMatches[2], 64)
|
||||
nrcpt, err := strconv.ParseFloat(qmgrInsertMatches[2], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert QMGR nrcpt: %v", err)
|
||||
}
|
||||
e.qmgrInsertsNrcpt.Observe(nrcpt)
|
||||
} else if strings.HasSuffix(logMatches[2], ": removed") {
|
||||
e.qmgrRemoves.Inc()
|
||||
@ -331,13 +368,25 @@ func (e *PostfixExporter) CollectFromLogline(line string) {
|
||||
}
|
||||
} else if logMatches[1] == "smtp" {
|
||||
if smtpMatches := lmtpPipeSMTPLine.FindStringSubmatch(logMatches[2]); smtpMatches != nil {
|
||||
pdelay, _ := strconv.ParseFloat(smtpMatches[2], 64)
|
||||
pdelay, err := strconv.ParseFloat(smtpMatches[2], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert SMTP pdelay: %v", err)
|
||||
}
|
||||
e.smtpDelays.WithLabelValues("before_queue_manager").Observe(pdelay)
|
||||
adelay, _ := strconv.ParseFloat(smtpMatches[3], 64)
|
||||
adelay, err := strconv.ParseFloat(smtpMatches[3], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert SMTP adelay: %v", err)
|
||||
}
|
||||
e.smtpDelays.WithLabelValues("queue_manager").Observe(adelay)
|
||||
sdelay, _ := strconv.ParseFloat(smtpMatches[4], 64)
|
||||
sdelay, err := strconv.ParseFloat(smtpMatches[4], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert SMTP sdelay: %v", err)
|
||||
}
|
||||
e.smtpDelays.WithLabelValues("connection_setup").Observe(sdelay)
|
||||
xdelay, _ := strconv.ParseFloat(smtpMatches[5], 64)
|
||||
xdelay, err := strconv.ParseFloat(smtpMatches[5], 64)
|
||||
if err != nil {
|
||||
log.Printf("Couldn't convert SMTP xdelay: %v", err)
|
||||
}
|
||||
e.smtpDelays.WithLabelValues("transmission").Observe(xdelay)
|
||||
} else if smtpTLSMatches := smtpTLSLine.FindStringSubmatch(logMatches[2]); smtpTLSMatches != nil {
|
||||
e.smtpTLSConnects.WithLabelValues(smtpTLSMatches[1:]...).Inc()
|
||||
@ -610,16 +659,17 @@ func (e *PostfixExporter) Collect(ch chan<- prometheus.Metric) {
|
||||
|
||||
func main() {
|
||||
var (
|
||||
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.")
|
||||
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.")
|
||||
|
||||
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()
|
||||
systemdEnable bool
|
||||
systemdUnit, systemdSlice, systemdJournalPath string
|
||||
)
|
||||
systemdFlags(&systemdEnable, &systemdUnit, &systemdSlice, &systemdJournalPath)
|
||||
flag.Parse()
|
||||
systemdFlags(&systemdEnable, &systemdUnit, &systemdSlice, &systemdJournalPath, app)
|
||||
|
||||
kingpin.MustParse(app.Parse(os.Args[1:]))
|
||||
|
||||
var journal *Journal
|
||||
if systemdEnable {
|
||||
@ -643,7 +693,7 @@ func main() {
|
||||
|
||||
http.Handle(*metricsPath, prometheus.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`
|
||||
_, err = w.Write([]byte(`
|
||||
<html>
|
||||
<head><title>Postfix Exporter</title></head>
|
||||
<body>
|
||||
@ -651,6 +701,9 @@ func main() {
|
||||
<p><a href='` + *metricsPath + `'>Metrics</a></p>
|
||||
</body>
|
||||
</html>`))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
|
||||
log.Print("Listening on ", *listenAddress)
|
||||
|
18
systemd.go
18
systemd.go
@ -3,12 +3,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/coreos/go-systemd/sdjournal"
|
||||
)
|
||||
|
||||
@ -46,8 +46,10 @@ func NewJournal(unit, slice, path string) (j *Journal, err error) {
|
||||
}
|
||||
|
||||
// Start at end of journal
|
||||
j.SeekRealtimeUsec(uint64(time.Now().UnixNano() / 1000))
|
||||
|
||||
err = j.SeekRealtimeUsec(uint64(time.Now().UnixNano() / 1000))
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -86,11 +88,11 @@ func (j *Journal) NextMessage() (s string, c uint64, err error) {
|
||||
}
|
||||
|
||||
// systemdFlags sets the flags for use with systemd
|
||||
func systemdFlags(enable *bool, unit, slice, path *string) {
|
||||
flag.BoolVar(enable, "systemd.enable", false, "Read from the systemd journal instead of log")
|
||||
flag.StringVar(unit, "systemd.unit", "postfix.service", "Name of the Postfix systemd unit.")
|
||||
flag.StringVar(slice, "systemd.slice", "", "Name of the Postfix systemd slice. Overrides the systemd unit.")
|
||||
flag.StringVar(path, "systemd.journal_path", "", "Path to the systemd journal")
|
||||
func systemdFlags(enable *bool, unit, slice, path *string, app *kingpin.Application) {
|
||||
app.Flag("systemd.enable", "Read from the systemd journal instead of log").Default("false").BoolVar(enable)
|
||||
app.Flag("systemd.unit", "Name of the Postfix systemd unit.").Default("postfix.service").StringVar(unit)
|
||||
app.Flag("systemd.slice", "Name of the Postfix systemd slice. Overrides the systemd unit.").Default("").StringVar(slice)
|
||||
app.Flag("systemd.journal_path", "Path to the systemd journal").Default("").StringVar(path)
|
||||
}
|
||||
|
||||
// CollectLogfileFromJournal Collects entries from the systemd journal.
|
||||
|
Loading…
Reference in New Issue
Block a user