mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-23 15:37:01 +00:00
maillog: Introduce a special-purpose mail logging package
The default INFO logs are more oriented towards debugging and can be a bit too verbose when looking for high-level information. This patch introduces a new "maillog" package, used to log messages of particular relevance to mail transmission at a higher level.
This commit is contained in:
136
internal/maillog/maillog.go
Normal file
136
internal/maillog/maillog.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Package maillog implements a log specifically for email.
|
||||
package maillog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log/syslog"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"blitiri.com.ar/go/chasquid/internal/trace"
|
||||
)
|
||||
|
||||
// Global event logs.
|
||||
var (
|
||||
authLog = trace.NewEventLog("Authentication", "Incoming SMTP")
|
||||
)
|
||||
|
||||
// A writer that prepends timing information.
|
||||
type timedWriter struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (t timedWriter) Write(b []byte) (int, error) {
|
||||
fmt.Fprintf(t.w, "%s ", time.Now().Format("2006-01-02 15:04:05.000000"))
|
||||
return t.w.Write(b)
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
w io.Writer
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func New(w io.Writer) *Logger {
|
||||
return &Logger{w: timedWriter{w}}
|
||||
}
|
||||
|
||||
func NewSyslog() (*Logger, error) {
|
||||
w, err := syslog.New(syslog.LOG_INFO|syslog.LOG_MAIL, "chasquid")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := &Logger{w: w}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *Logger) printf(format string, args ...interface{}) {
|
||||
_, err := fmt.Fprintf(l.w, format, args...)
|
||||
if err != nil {
|
||||
l.once.Do(func() {
|
||||
glog.Errorf("failed to write to maillog: %v", err)
|
||||
glog.Error("(will not report this again)")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Listening(a string) {
|
||||
l.printf("daemon listening on %s\n", a)
|
||||
}
|
||||
|
||||
func (l *Logger) Auth(netAddr net.Addr, user string, successful bool) {
|
||||
res := "successful"
|
||||
if !successful {
|
||||
res = "failed"
|
||||
}
|
||||
msg := fmt.Sprintf("%s authentication %s for %s\n", netAddr, res, user)
|
||||
l.printf(msg)
|
||||
authLog.Debugf(msg)
|
||||
}
|
||||
|
||||
func (l *Logger) Rejected(netAddr net.Addr, from string, to []string, err string) {
|
||||
if from != "" {
|
||||
from = fmt.Sprintf(" from=%s", from)
|
||||
}
|
||||
toStr := ""
|
||||
if len(to) > 0 {
|
||||
toStr = fmt.Sprintf(" to=%v", to)
|
||||
}
|
||||
l.printf("%s rejected%s%s - %v\n", netAddr, from, toStr, err)
|
||||
}
|
||||
|
||||
func (l *Logger) Queued(netAddr net.Addr, from string, to []string, id string) {
|
||||
l.printf("%s from=%s queued ip=%s to=%v\n", id, from, netAddr, to)
|
||||
}
|
||||
|
||||
func (l *Logger) SendAttempt(id, from, to string, err error, permanent bool) {
|
||||
if err == nil {
|
||||
l.printf("%s from=%s to=%s sent successfully\n", id, from, to)
|
||||
} else {
|
||||
t := "(temporary)"
|
||||
if permanent {
|
||||
t = "(permanent)"
|
||||
}
|
||||
l.printf("%s from=%s to=%s sent failed %s: %v\n", id, from, to, t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) QueueLoop(id string, nextDelay time.Duration) {
|
||||
if nextDelay > 0 {
|
||||
l.printf("%s completed loop, next in %v\n", id, nextDelay)
|
||||
} else {
|
||||
l.printf("%s all done\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
// The default logger used in the following top-level functions.
|
||||
var Default *Logger = New(ioutil.Discard)
|
||||
|
||||
func Listening(a string) {
|
||||
Default.Listening(a)
|
||||
}
|
||||
|
||||
func Auth(netAddr net.Addr, user string, successful bool) {
|
||||
Default.Auth(netAddr, user, successful)
|
||||
}
|
||||
|
||||
func Rejected(netAddr net.Addr, from string, to []string, err string) {
|
||||
Default.Rejected(netAddr, from, to, err)
|
||||
}
|
||||
|
||||
func Queued(netAddr net.Addr, from string, to []string, id string) {
|
||||
Default.Queued(netAddr, from, to, id)
|
||||
}
|
||||
|
||||
func SendAttempt(id, from, to string, err error, permanent bool) {
|
||||
Default.SendAttempt(id, from, to, err, permanent)
|
||||
}
|
||||
|
||||
func QueueLoop(id string, nextDelay time.Duration) {
|
||||
Default.QueueLoop(id, nextDelay)
|
||||
}
|
||||
116
internal/maillog/maillog_test.go
Normal file
116
internal/maillog/maillog_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package maillog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var netAddr = &net.TCPAddr{
|
||||
IP: net.ParseIP("1.2.3.4"),
|
||||
Port: 4321,
|
||||
}
|
||||
|
||||
func expect(t *testing.T, buf *bytes.Buffer, s string) {
|
||||
if strings.Contains(buf.String(), s) {
|
||||
return
|
||||
}
|
||||
t.Errorf("buffer mismatch:")
|
||||
t.Errorf(" expected to contain: %q", s)
|
||||
t.Errorf(" got: %q", buf.String())
|
||||
}
|
||||
|
||||
func TestLogger(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
l := New(buf)
|
||||
|
||||
l.Listening("1.2.3.4:4321")
|
||||
expect(t, buf, "daemon listening on 1.2.3.4:4321")
|
||||
buf.Reset()
|
||||
|
||||
l.Auth(netAddr, "user@domain", false)
|
||||
expect(t, buf, "1.2.3.4:4321 authentication failed for user@domain")
|
||||
buf.Reset()
|
||||
|
||||
l.Auth(netAddr, "user@domain", true)
|
||||
expect(t, buf, "1.2.3.4:4321 authentication successful for user@domain")
|
||||
buf.Reset()
|
||||
|
||||
l.Rejected(netAddr, "from", []string{"to1", "to2"}, "error")
|
||||
expect(t, buf, "1.2.3.4:4321 rejected from=from to=[to1 to2] - error")
|
||||
buf.Reset()
|
||||
|
||||
l.Queued(netAddr, "from", []string{"to1", "to2"}, "qid")
|
||||
expect(t, buf, "qid from=from queued ip=1.2.3.4:4321 to=[to1 to2]")
|
||||
buf.Reset()
|
||||
|
||||
l.SendAttempt("qid", "from", "to", nil, false)
|
||||
expect(t, buf, "qid from=from to=to sent successfully")
|
||||
buf.Reset()
|
||||
|
||||
l.SendAttempt("qid", "from", "to", fmt.Errorf("error"), false)
|
||||
expect(t, buf, "qid from=from to=to sent failed (temporary): error")
|
||||
buf.Reset()
|
||||
|
||||
l.SendAttempt("qid", "from", "to", fmt.Errorf("error"), true)
|
||||
expect(t, buf, "qid from=from to=to sent failed (permanent): error")
|
||||
buf.Reset()
|
||||
|
||||
l.QueueLoop("qid", 17*time.Second)
|
||||
expect(t, buf, "qid completed loop, next in 17s")
|
||||
buf.Reset()
|
||||
|
||||
l.QueueLoop("qid", 0)
|
||||
expect(t, buf, "qid all done")
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
// Test that the default actions go reasonably to the default logger.
|
||||
// Unfortunately this is almost the same as TestLogger.
|
||||
func TestDefault(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
Default = New(buf)
|
||||
|
||||
Listening("1.2.3.4:4321")
|
||||
expect(t, buf, "daemon listening on 1.2.3.4:4321")
|
||||
buf.Reset()
|
||||
|
||||
Auth(netAddr, "user@domain", false)
|
||||
expect(t, buf, "1.2.3.4:4321 authentication failed for user@domain")
|
||||
buf.Reset()
|
||||
|
||||
Auth(netAddr, "user@domain", true)
|
||||
expect(t, buf, "1.2.3.4:4321 authentication successful for user@domain")
|
||||
buf.Reset()
|
||||
|
||||
Rejected(netAddr, "from", []string{"to1", "to2"}, "error")
|
||||
expect(t, buf, "1.2.3.4:4321 rejected from=from to=[to1 to2] - error")
|
||||
buf.Reset()
|
||||
|
||||
Queued(netAddr, "from", []string{"to1", "to2"}, "qid")
|
||||
expect(t, buf, "qid from=from queued ip=1.2.3.4:4321 to=[to1 to2]")
|
||||
buf.Reset()
|
||||
|
||||
SendAttempt("qid", "from", "to", nil, false)
|
||||
expect(t, buf, "qid from=from to=to sent successfully")
|
||||
buf.Reset()
|
||||
|
||||
SendAttempt("qid", "from", "to", fmt.Errorf("error"), false)
|
||||
expect(t, buf, "qid from=from to=to sent failed (temporary): error")
|
||||
buf.Reset()
|
||||
|
||||
SendAttempt("qid", "from", "to", fmt.Errorf("error"), true)
|
||||
expect(t, buf, "qid from=from to=to sent failed (permanent): error")
|
||||
buf.Reset()
|
||||
|
||||
QueueLoop("qid", 17*time.Second)
|
||||
expect(t, buf, "qid completed loop, next in 17s")
|
||||
buf.Reset()
|
||||
|
||||
QueueLoop("qid", 0)
|
||||
expect(t, buf, "qid all done")
|
||||
buf.Reset()
|
||||
}
|
||||
Reference in New Issue
Block a user