// Package maillog implements a log specifically for email. package maillog import ( "fmt" "io" "log/syslog" "net" "sync" "time" "blitiri.com.ar/go/chasquid/internal/trace" "blitiri.com.ar/go/log" ) // Global event logs. var ( authLog = trace.New("Authentication", "Incoming SMTP") ) // Logger contains a backend used to log data to, such as a file or syslog. // It implements various user-friendly methods for logging mail information to // it. type Logger struct { inner *log.Logger once sync.Once } // New creates a new Logger which will write messages to the given writer. func New(w io.WriteCloser) *Logger { inner := log.New(w) // Don't include level or caller in the output, it doesn't add value for // this type of log. inner.LogLevel = false inner.LogCaller = false return &Logger{inner: inner} } // NewFile creates a new Logger which will write messages to the file at the // given path. func NewFile(path string) (*Logger, error) { inner, err := log.NewFile(path) if err != nil { return nil, err } // Don't include level or caller in the output, it doesn't add value for // this type of log. inner.LogLevel = false inner.LogCaller = false return &Logger{inner: inner}, nil } // NewSyslog creates a new Logger which will write messages to syslog. func NewSyslog() (*Logger, error) { inner, err := log.NewSyslog(syslog.LOG_INFO|syslog.LOG_MAIL, "chasquid") if err != nil { return nil, err } // Like NewFile, we skip level and caller. In addition, we skip time, as // syslog usually adds that on its own. inner.LogLevel = false inner.LogCaller = false inner.LogTime = false return &Logger{inner: inner}, nil } func (l *Logger) printf(format string, args ...interface{}) { err := l.inner.Log(log.Info, 2, format, args...) if err != nil { l.once.Do(func() { log.Errorf("failed to write to maillog: %v", err) log.Errorf("(will not report this again)") }) } } // Reopen the underlying logger. func (l *Logger) Reopen() error { return l.inner.Reopen() } // Listening logs that the daemon is listening on the given address. func (l *Logger) Listening(a string) { l.printf("daemon listening on %s\n", a) } // Auth logs an authentication request. func (l *Logger) Auth(netAddr net.Addr, user string, successful bool) { res := "succeeded" if !successful { res = "failed" } msg := fmt.Sprintf("%s auth %s for %s\n", netAddr, res, user) l.printf(msg) authLog.Debugf(msg) } // Rejected logs that we've rejected an email. 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) } // Queued logs that we have queued an email. 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) } // SendAttempt logs that we have attempted to send an email. func (l *Logger) SendAttempt(id, from, to string, err error, permanent bool) { if err == nil { l.printf("%s from=%s to=%s sent\n", id, from, to) } else { t := "(temporary)" if permanent { t = "(permanent)" } l.printf("%s from=%s to=%s failed %s: %v\n", id, from, to, t, err) } } // QueueLoop logs that we have completed a queue loop. func (l *Logger) QueueLoop(id, from string, nextDelay time.Duration) { if nextDelay > 0 { l.printf("%s from=%s completed loop, next in %v\n", id, from, nextDelay) } else { l.printf("%s from=%s all done\n", id, from) } } type nopCloser struct { io.Writer } func (nopCloser) Close() error { return nil } // Default logger, used in the following top-level functions. var Default *Logger = New(nopCloser{io.Discard}) // Listening logs that the daemon is listening on the given address. func Listening(a string) { Default.Listening(a) } // Auth logs an authentication request. func Auth(netAddr net.Addr, user string, successful bool) { Default.Auth(netAddr, user, successful) } // Rejected logs that we've rejected an email. func Rejected(netAddr net.Addr, from string, to []string, err string) { Default.Rejected(netAddr, from, to, err) } // Queued logs that we have queued an email. func Queued(netAddr net.Addr, from string, to []string, id string) { Default.Queued(netAddr, from, to, id) } // SendAttempt logs that we have attempted to send an email. func SendAttempt(id, from, to string, err error, permanent bool) { Default.SendAttempt(id, from, to, err, permanent) } // QueueLoop logs that we have completed a queue loop. func QueueLoop(id, from string, nextDelay time.Duration) { Default.QueueLoop(id, from, nextDelay) }