switch to slog.Logger for logging, for easier reuse of packages by external software

we don't want external software to include internal details like mlog.
slog.Logger is/will be the standard.

we still have mlog for its helper functions, and its handler that logs in
concise logfmt used by mox.

packages that are not meant for reuse still pass around mlog.Log for
convenience.

we use golang.org/x/exp/slog because we also support the previous Go toolchain
version. with the next Go release, we'll switch to the builtin slog.
This commit is contained in:
Mechiel Lukkien
2023-12-05 13:35:58 +01:00
parent 56b2a9d980
commit 5b20cba50a
150 changed files with 5176 additions and 1898 deletions

View File

@ -23,6 +23,7 @@ import (
_ "embed"
"golang.org/x/exp/maps"
"golang.org/x/exp/slog"
"github.com/mjl-/bstore"
"github.com/mjl-/sherpa"
@ -33,7 +34,6 @@ import (
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/message"
"github.com/mjl-/mox/metrics"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/moxio"
"github.com/mjl-/mox/moxvar"
@ -55,7 +55,7 @@ type Webmail struct {
func mustParseAPI(api string, buf []byte) (doc sherpadoc.Section) {
err := json.Unmarshal(buf, &doc)
if err != nil {
xlog.Fatalx("parsing webmail api docs", err, mlog.Field("api", api))
pkglog.Fatalx("parsing webmail api docs", err, slog.String("api", api))
}
return doc
}
@ -71,14 +71,14 @@ func makeSherpaHandler(maxMessageSize int64) (http.Handler, error) {
func init() {
collector, err := sherpaprom.NewCollector("moxwebmail", nil)
if err != nil {
xlog.Fatalx("creating sherpa prometheus collector", err)
pkglog.Fatalx("creating sherpa prometheus collector", err)
}
sherpaHandlerOpts = &sherpa.HandlerOpts{Collector: collector, AdjustFunctionNames: "none"}
// Just to validate.
_, err = makeSherpaHandler(0)
if err != nil {
xlog.Fatalx("sherpa handler", err)
pkglog.Fatalx("sherpa handler", err)
}
}
@ -112,9 +112,9 @@ func (Webmail) Request(ctx context.Context, req Request) {
// ParsedMessage returns enough to render the textual body of a message. It is
// assumed the client already has other fields through MessageItem.
func (Webmail) ParsedMessage(ctx context.Context, msgID int64) (pm ParsedMessage) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -234,8 +234,8 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
// todo: consider making this an HTTP POST, so we can upload as regular form, which is probably more efficient for encoding for the client and we can stream the data in.
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
log := xlog.WithContext(ctx).Fields(mlog.Field("account", reqInfo.AccountName))
acc, err := store.OpenAccount(reqInfo.AccountName)
log := pkglog.WithContext(ctx).With(slog.String("account", reqInfo.AccountName))
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -326,7 +326,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
}
// Create file to compose message into.
dataFile, err := store.CreateMessageTemp("webmail-submit")
dataFile, err := store.CreateMessageTemp(log, "webmail-submit")
xcheckf(ctx, err, "creating temporary file for message")
defer store.CloseRemoveTempFile(log, dataFile, "message to submit")
@ -361,7 +361,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
// Note: we don't have "via" or "with", there is no registered for webmail.
recvHdr.Add(" ", "Received:", "from", recvFrom, "by", recvBy, "id", recvID) // ../rfc/5321:3158
if reqInfo.Request.TLS != nil {
recvHdr.Add(" ", message.TLSReceivedComment(log, *reqInfo.Request.TLS)...)
recvHdr.Add(" ", mox.TLSReceivedComment(log, *reqInfo.Request.TLS)...)
}
recvHdr.Add(" ", "for", "<"+rcptTo+">;", time.Now().Format(message.RFC5322Z))
return recvHdr.String()
@ -558,7 +558,7 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
fd := fromAddr.Address.Domain
confDom, _ := mox.Conf.Domain(fd)
if len(confDom.DKIM.Sign) > 0 {
dkimHeaders, err := dkim.Sign(ctx, fromAddr.Address.Localpart, fd, confDom.DKIM, smtputf8, dataFile)
dkimHeaders, err := dkim.Sign(ctx, log.Logger, fromAddr.Address.Localpart, fd, confDom.DKIM, smtputf8, dataFile)
if err != nil {
metricServerErrors.WithLabelValues("dkimsign").Inc()
}
@ -671,9 +671,9 @@ func (w Webmail) MessageSubmit(ctx context.Context, m SubmitMessage) {
// MessageMove moves messages to another mailbox. If the message is already in
// the mailbox an error is returned.
func (Webmail) MessageMove(ctx context.Context, messageIDs []int64, mailboxID int64) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -802,9 +802,9 @@ func (Webmail) MessageMove(ctx context.Context, messageIDs []int64, mailboxID in
// MessageDelete permanently deletes messages, without moving them to the Trash mailbox.
func (Webmail) MessageDelete(ctx context.Context, messageIDs []int64) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -895,9 +895,9 @@ func (Webmail) MessageDelete(ctx context.Context, messageIDs []int64) {
// FlagsAdd adds flags, either system flags like \Seen or custom keywords. The
// flags should be lower-case, but will be converted and verified.
func (Webmail) FlagsAdd(ctx context.Context, messageIDs []int64, flaglist []string) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -978,9 +978,9 @@ func (Webmail) FlagsAdd(ctx context.Context, messageIDs []int64, flaglist []stri
// FlagsClear clears flags, either system flags like \Seen or custom keywords.
func (Webmail) FlagsClear(ctx context.Context, messageIDs []int64, flaglist []string) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1056,9 +1056,9 @@ func (Webmail) FlagsClear(ctx context.Context, messageIDs []int64, flaglist []st
// MailboxCreate creates a new mailbox.
func (Webmail) MailboxCreate(ctx context.Context, name string) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1086,9 +1086,9 @@ func (Webmail) MailboxCreate(ctx context.Context, name string) {
// MailboxDelete deletes a mailbox and all its messages.
func (Webmail) MailboxDelete(ctx context.Context, mailboxID int64) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1123,16 +1123,16 @@ func (Webmail) MailboxDelete(ctx context.Context, mailboxID int64) {
for _, mID := range removeMessageIDs {
p := acc.MessagePath(mID)
err := os.Remove(p)
log.Check(err, "removing message file for mailbox delete", mlog.Field("path", p))
log.Check(err, "removing message file for mailbox delete", slog.String("path", p))
}
}
// MailboxEmpty empties a mailbox, removing all messages from the mailbox, but not
// its child mailboxes.
func (Webmail) MailboxEmpty(ctx context.Context, mailboxID int64) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1196,16 +1196,16 @@ func (Webmail) MailboxEmpty(ctx context.Context, mailboxID int64) {
for _, m := range expunged {
p := acc.MessagePath(m.ID)
err := os.Remove(p)
log.Check(err, "removing message file after emptying mailbox", mlog.Field("path", p))
log.Check(err, "removing message file after emptying mailbox", slog.String("path", p))
}
}
// MailboxRename renames a mailbox, possibly moving it to a new parent. The mailbox
// ID and its messages are unchanged.
func (Webmail) MailboxRename(ctx context.Context, mailboxID int64, newName string) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1239,9 +1239,9 @@ func (Webmail) MailboxRename(ctx context.Context, mailboxID int64, newName strin
// matches, most recently used first, and whether this is the full list and further
// requests for longer prefixes aren't necessary.
func (Webmail) CompleteRecipient(ctx context.Context, search string) ([]string, bool) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1342,9 +1342,9 @@ func addressString(a message.Address, smtputf8 bool) string {
// MailboxSetSpecialUse sets the special use flags of a mailbox.
func (Webmail) MailboxSetSpecialUse(ctx context.Context, mb store.Mailbox) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1395,9 +1395,9 @@ func (Webmail) MailboxSetSpecialUse(ctx context.Context, mb store.Mailbox) {
// children. The messageIDs are typically thread roots. But not all roots
// (without parent) of a thread need to have the same collapsed state.
func (Webmail) ThreadCollapse(ctx context.Context, messageIDs []int64, collapse bool) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1455,9 +1455,9 @@ func (Webmail) ThreadCollapse(ctx context.Context, messageIDs []int64, collapse
// ThreadMute saves the ThreadMute field for the messages and their children.
// If messages are muted, they are also marked collapsed.
func (Webmail) ThreadMute(ctx context.Context, messageIDs []int64, mute bool) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1555,7 +1555,7 @@ type RecipientSecurity struct {
// RecipientSecurity looks up security properties of the address in the
// single-address message addressee (as it appears in a To/Cc/Bcc/etc header).
func (Webmail) RecipientSecurity(ctx context.Context, messageAddressee string) (RecipientSecurity, error) {
resolver := dns.StrictResolver{Pkg: "webmail"}
resolver := dns.StrictResolver{Pkg: "webmail", Log: pkglog.WithContext(ctx).Logger}
return recipientSecurity(ctx, resolver, messageAddressee)
}
@ -1565,15 +1565,15 @@ func logPanic(ctx context.Context) {
if x == nil {
return
}
log := xlog.WithContext(ctx)
log.Error("recover from panic", mlog.Field("panic", x))
log := pkglog.WithContext(ctx)
log.Error("recover from panic", slog.Any("panic", x))
debug.PrintStack()
metrics.PanicInc(metrics.Webmail)
}
// separate function for testing with mocked resolver.
func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddressee string) (RecipientSecurity, error) {
log := xlog.WithContext(ctx)
log := pkglog.WithContext(ctx)
rs := RecipientSecurity{
SecurityResultUnknown,
@ -1601,7 +1601,7 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
defer logPanic(ctx)
defer wg.Done()
policy, _, _, err := mtastsdb.Get(ctx, resolver, addr.Domain)
policy, _, _, err := mtastsdb.Get(ctx, log.Logger, resolver, addr.Domain)
if policy != nil && policy.Mode == mtasts.ModeEnforce {
rs.MTASTS = SecurityResultYes
} else if err == nil {
@ -1617,7 +1617,7 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
defer logPanic(ctx)
defer wg.Done()
_, origNextHopAuthentic, expandedNextHopAuthentic, _, hosts, _, err := smtpclient.GatherDestinations(ctx, log, resolver, dns.IPDomain{Domain: addr.Domain})
_, origNextHopAuthentic, expandedNextHopAuthentic, _, hosts, _, err := smtpclient.GatherDestinations(ctx, log.Logger, resolver, dns.IPDomain{Domain: addr.Domain})
if err != nil {
rs.DNSSEC = SecurityResultError
return
@ -1641,7 +1641,7 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
// Resolve the IPs. Required for DANE to prevent bad DNS servers from causing an
// error result instead of no-DANE result.
authentic, expandedAuthentic, expandedHost, _, _, err := smtpclient.GatherIPs(ctx, log, resolver, host, map[string][]net.IP{})
authentic, expandedAuthentic, expandedHost, _, _, err := smtpclient.GatherIPs(ctx, log.Logger, resolver, host, map[string][]net.IP{})
if err != nil {
rs.DANE = SecurityResultError
return
@ -1651,7 +1651,7 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
return
}
daneRequired, _, _, err := smtpclient.GatherTLSA(ctx, log, resolver, host.Domain, expandedAuthentic, expandedHost)
daneRequired, _, _, err := smtpclient.GatherTLSA(ctx, log.Logger, resolver, host.Domain, expandedAuthentic, expandedHost)
if err != nil {
rs.DANE = SecurityResultError
return
@ -1664,7 +1664,7 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
// STARTTLS and RequireTLS
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
acc, err := store.OpenAccount(reqInfo.AccountName)
acc, err := store.OpenAccount(log, reqInfo.AccountName)
xcheckf(ctx, err, "open account")
defer func() {
if acc != nil {
@ -1682,7 +1682,7 @@ func recipientSecurity(ctx context.Context, resolver dns.Resolver, messageAddres
} else if err != nil {
rs.STARTTLS = SecurityResultError
rs.RequireTLS = SecurityResultError
log.Errorx("looking up recipient domain", err, mlog.Field("domain", addr.Domain))
log.Errorx("looking up recipient domain", err, slog.Any("domain", addr.Domain))
return nil
}
if rd.STARTTLS {

View File

@ -13,6 +13,7 @@ import (
"github.com/mjl-/sherpa"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/queue"
"github.com/mjl-/mox/store"
@ -49,13 +50,14 @@ func TestAPI(t *testing.T) {
mox.MustLoadConfig(true, false)
defer store.Switchboard()()
acc, err := store.OpenAccount("mjl")
log := mlog.New("webmail", nil)
acc, err := store.OpenAccount(log, "mjl")
tcheck(t, err, "open account")
err = acc.SetPassword("test1234")
err = acc.SetPassword(log, "test1234")
tcheck(t, err, "set password")
defer func() {
err := acc.Close()
xlog.Check(err, "closing account")
pkglog.Check(err, "closing account")
}()
var zerom store.Message

View File

@ -12,6 +12,8 @@ import (
"sync"
"time"
"golang.org/x/exp/slog"
"github.com/mjl-/mox/metrics"
"github.com/mjl-/mox/mlog"
)
@ -69,7 +71,7 @@ var waitGen = mathrand.New(mathrand.NewSource(time.Now().UnixNano()))
// Schedule an event for writing to the connection. If events get a delay, this
// function still returns immediately.
func (ew *eventWriter) xsendEvent(ctx context.Context, log *mlog.Log, name string, v any) {
func (ew *eventWriter) xsendEvent(ctx context.Context, log mlog.Log, name string, v any) {
if (ew.waitMin > 0 || ew.waitMax > 0) && ew.events == nil {
// First write on a connection with delay.
ew.events = make(chan struct {
@ -82,7 +84,7 @@ func (ew *eventWriter) xsendEvent(ctx context.Context, log *mlog.Log, name strin
defer func() {
x := recover() // Should not happen, but don't take program down if it does.
if x != nil {
log.WithContext(ctx).Error("writeEvent panic", mlog.Field("err", x))
log.WithContext(ctx).Error("writeEvent panic", slog.Any("err", x))
debug.PrintStack()
metrics.PanicInc(metrics.Webmailsendevent)
}

View File

@ -8,6 +8,8 @@ import (
"net/url"
"strings"
"golang.org/x/exp/slog"
"github.com/mjl-/mox/dns"
"github.com/mjl-/mox/message"
"github.com/mjl-/mox/mlog"
@ -32,19 +34,19 @@ import (
// "filename*0*=UTF-8”%...; filename*1*=%.... We'll look for Q/B-word encoding
// markers ("=?"-prefix or "?="-suffix) and try to decode if present. This would
// only cause trouble for filenames having this prefix/suffix.
func tryDecodeParam(log *mlog.Log, name string) string {
func tryDecodeParam(log mlog.Log, name string) string {
if name == "" || !strings.HasPrefix(name, "=?") && !strings.HasSuffix(name, "?=") {
return name
}
// todo: find where this is allowed. it seems quite common. perhaps we should remove the pedantic check?
if moxvar.Pedantic {
log.Debug("attachment contains rfc2047 q/b-word-encoded mime parameter instead of rfc2231-encoded", mlog.Field("name", name))
log.Debug("attachment contains rfc2047 q/b-word-encoded mime parameter instead of rfc2231-encoded", slog.String("name", name))
return name
}
dec := mime.WordDecoder{}
s, err := dec.DecodeHeader(name)
if err != nil {
log.Debugx("q/b-word decoding mime parameter", err, mlog.Field("name", name))
log.Debugx("q/b-word decoding mime parameter", err, slog.String("name", name))
return name
}
return s
@ -52,7 +54,7 @@ func tryDecodeParam(log *mlog.Log, name string) string {
// todo: mime.FormatMediaType does not wrap long lines. should do it ourselves, and split header into several parts (if commonly supported).
func messageItem(log *mlog.Log, m store.Message, state *msgState) (MessageItem, error) {
func messageItem(log mlog.Log, m store.Message, state *msgState) (MessageItem, error) {
pm, err := parsedMessage(log, m, state, false, true)
if err != nil {
return MessageItem{}, fmt.Errorf("parsing message %d for item: %v", m.ID, err)
@ -161,7 +163,7 @@ func formatFirstLine(r io.Reader) (string, error) {
return result, scanner.Err()
}
func parsedMessage(log *mlog.Log, m store.Message, state *msgState, full, msgitem bool) (pm ParsedMessage, rerr error) {
func parsedMessage(log mlog.Log, m store.Message, state *msgState, full, msgitem bool) (pm ParsedMessage, rerr error) {
if full || msgitem {
if !state.ensurePart(m, true) {
return pm, state.err
@ -240,11 +242,11 @@ func parsedMessage(log *mlog.Log, m store.Message, state *msgState, full, msgite
if full || msgitem {
// todo: should have this, and perhaps all content-* headers, preparsed in message.Part?
h, err := p.Header()
log.Check(err, "parsing attachment headers", mlog.Field("msgid", m.ID))
log.Check(err, "parsing attachment headers", slog.Int64("msgid", m.ID))
cp := h.Get("Content-Disposition")
if cp != "" {
disp, params, err := mime.ParseMediaType(cp)
log.Check(err, "parsing content-disposition", mlog.Field("cp", cp))
log.Check(err, "parsing content-disposition", slog.String("cp", cp))
if strings.EqualFold(disp, "attachment") {
name := tryDecodeParam(log, p.ContentTypeParams["name"])
if name == "" {
@ -322,11 +324,11 @@ func parsedMessage(log *mlog.Log, m store.Message, state *msgState, full, msgite
if name == "" && (full || msgitem) {
// todo: should have this, and perhaps all content-* headers, preparsed in message.Part?
h, err := p.Header()
log.Check(err, "parsing attachment headers", mlog.Field("msgid", m.ID))
log.Check(err, "parsing attachment headers", slog.Int64("msgid", m.ID))
cp := h.Get("Content-Disposition")
if cp != "" {
_, params, err := mime.ParseMediaType(cp)
log.Check(err, "parsing content-disposition", mlog.Field("cp", cp))
log.Check(err, "parsing content-disposition", slog.String("cp", cp))
name = tryDecodeParam(log, params["filename"])
}
}

View File

@ -20,6 +20,7 @@ import (
"time"
"golang.org/x/exp/slices"
"golang.org/x/exp/slog"
"github.com/mjl-/bstore"
"github.com/mjl-/sherpa"
@ -486,7 +487,7 @@ type ioErr struct {
// serveEvents serves an SSE connection. Authentication is done through a query
// string parameter "token", a one-time-use token returned by the Token API call.
func serveEvents(ctx context.Context, log *mlog.Log, w http.ResponseWriter, r *http.Request) {
func serveEvents(ctx context.Context, log mlog.Log, w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "405 - method not allowed - use get", http.StatusMethodNotAllowed)
return
@ -569,7 +570,7 @@ func serveEvents(ctx context.Context, log *mlog.Log, w http.ResponseWriter, r *h
} else if _, ok := x.(ioErr); ok {
return
} else {
log.WithContext(ctx).Error("serveEvents panic", mlog.Field("err", x))
log.WithContext(ctx).Error("serveEvents panic", slog.Any("err", x))
debug.PrintStack()
metrics.PanicInc(metrics.Webmail)
panic(x)
@ -597,7 +598,7 @@ func serveEvents(ctx context.Context, log *mlog.Log, w http.ResponseWriter, r *h
defer writer.close()
// Fetch initial data.
acc, err := store.OpenAccount(accName)
acc, err := store.OpenAccount(log, accName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
@ -1123,7 +1124,7 @@ func (v view) inRange(m store.Message) bool {
// message). getmsg retrieves the message, which may be necessary depending on the
// active filters. Used to determine if a store.Change with a new message should be
// sent, and for the destination and anchor messages in view requests.
func (v view) matches(log *mlog.Log, acc *store.Account, checkRange bool, messageID int64, mailboxID int64, uid store.UID, flags store.Flags, keywords []string, getmsg func(int64, int64, store.UID) (store.Message, error)) (match bool, rerr error) {
func (v view) matches(log mlog.Log, acc *store.Account, checkRange bool, messageID int64, mailboxID int64, uid store.UID, flags store.Flags, keywords []string, getmsg func(int64, int64, store.UID) (store.Message, error)) (match bool, rerr error) {
var m store.Message
ensureMessage := func() bool {
if m.ID == 0 && rerr == nil {
@ -1208,7 +1209,7 @@ type msgResp struct {
// and sending Event* to the SSE connection.
//
// It always closes tx.
func viewRequestTx(ctx context.Context, log *mlog.Log, acc *store.Account, tx *bstore.Tx, v view, msgc chan EventViewMsgs, errc chan EventViewErr, resetc chan EventViewReset, donec chan int64) {
func viewRequestTx(ctx context.Context, log mlog.Log, acc *store.Account, tx *bstore.Tx, v view, msgc chan EventViewMsgs, errc chan EventViewErr, resetc chan EventViewReset, donec chan int64) {
defer func() {
err := tx.Rollback()
log.Check(err, "rolling back query transaction")
@ -1217,7 +1218,7 @@ func viewRequestTx(ctx context.Context, log *mlog.Log, acc *store.Account, tx *b
x := recover() // Should not happen, but don't take program down if it does.
if x != nil {
log.WithContext(ctx).Error("viewRequestTx panic", mlog.Field("err", x))
log.WithContext(ctx).Error("viewRequestTx panic", slog.Any("err", x))
debug.PrintStack()
metrics.PanicInc(metrics.Webmailrequest)
}
@ -1296,11 +1297,11 @@ func viewRequestTx(ctx context.Context, log *mlog.Log, acc *store.Account, tx *b
// It sends on msgc, with several types of messages: errors, whether the view is
// reset due to missing AnchorMessageID, and when the end of the view was reached
// and/or for a message.
func queryMessages(ctx context.Context, log *mlog.Log, acc *store.Account, tx *bstore.Tx, v view, mrc chan msgResp) {
func queryMessages(ctx context.Context, log mlog.Log, acc *store.Account, tx *bstore.Tx, v view, mrc chan msgResp) {
defer func() {
x := recover() // Should not happen, but don't take program down if it does.
if x != nil {
log.WithContext(ctx).Error("queryMessages panic", mlog.Field("err", x))
log.WithContext(ctx).Error("queryMessages panic", slog.Any("err", x))
debug.PrintStack()
mrc <- msgResp{err: fmt.Errorf("query failed")}
metrics.PanicInc(metrics.Webmailquery)
@ -1542,7 +1543,7 @@ func queryMessages(ctx context.Context, log *mlog.Log, acc *store.Account, tx *b
}
}
func gatherThread(log *mlog.Log, tx *bstore.Tx, acc *store.Account, v view, m store.Message, destMessageID int64, first bool) ([]MessageItem, *ParsedMessage, error) {
func gatherThread(log mlog.Log, tx *bstore.Tx, acc *store.Account, v view, m store.Message, destMessageID int64, first bool) ([]MessageItem, *ParsedMessage, error) {
if m.ThreadID == 0 {
// If we would continue, FilterNonzero would fail because there are no non-zero fields.
return nil, nil, fmt.Errorf("message has threadid 0, account is probably still being upgraded, try turning threading off until the upgrade is done")
@ -1717,7 +1718,7 @@ func (q Query) flagFilterFn() func(store.Flags, []string) bool {
// attachmentFilterFn returns a function that filters for the attachment-related
// filter from the query. A nil function is returned if there are attachment
// filters.
func (q Query) attachmentFilterFn(log *mlog.Log, acc *store.Account, state *msgState) func(m store.Message) bool {
func (q Query) attachmentFilterFn(log mlog.Log, acc *store.Account, state *msgState) func(m store.Message) bool {
if q.Filter.Attachments == AttachmentIndifferent && q.NotFilter.Attachments == AttachmentIndifferent {
return nil
}
@ -1774,7 +1775,7 @@ var attachmentExtensions = map[string]AttachmentType{
".pptx": AttachmentPresentation,
}
func attachmentTypes(log *mlog.Log, m store.Message, state *msgState) (map[AttachmentType]bool, error) {
func attachmentTypes(log mlog.Log, m store.Message, state *msgState) (map[AttachmentType]bool, error) {
types := map[AttachmentType]bool{}
pm, err := parsedMessage(log, m, state, false, false)
@ -1810,7 +1811,7 @@ func attachmentTypes(log *mlog.Log, m store.Message, state *msgState) (map[Attac
// used by IMAP, i.e. basic message headers from/to/subject, an unfortunate name
// clash with SMTP envelope) for the query. A nil function is returned if no
// filtering is needed.
func (q Query) envFilterFn(log *mlog.Log, state *msgState) func(m store.Message) bool {
func (q Query) envFilterFn(log mlog.Log, state *msgState) func(m store.Message) bool {
if len(q.Filter.From) == 0 && len(q.Filter.To) == 0 && len(q.Filter.Subject) == 0 && len(q.NotFilter.From) == 0 && len(q.NotFilter.To) == 0 && len(q.NotFilter.Subject) == 0 {
return nil
}
@ -1898,7 +1899,7 @@ func (q Query) envFilterFn(log *mlog.Log, state *msgState) func(m store.Message)
// headerFilterFn returns a function that filters for the header filters in the
// query. A nil function is returned if there are no header filters.
func (q Query) headerFilterFn(log *mlog.Log, state *msgState) func(m store.Message) bool {
func (q Query) headerFilterFn(log mlog.Log, state *msgState) func(m store.Message) bool {
if len(q.Filter.Headers) == 0 {
return nil
}
@ -1939,7 +1940,7 @@ func (q Query) headerFilterFn(log *mlog.Log, state *msgState) func(m store.Messa
// wordFiltersFn returns a function that applies the word filters of the query. A
// nil function is returned when query does not contain a word filter.
func (q Query) wordsFilterFn(log *mlog.Log, state *msgState) func(m store.Message) bool {
func (q Query) wordsFilterFn(log mlog.Log, state *msgState) func(m store.Message) bool {
if len(q.Filter.Words) == 0 && len(q.NotFilter.Words) == 0 {
return nil
}

View File

@ -17,6 +17,7 @@ import (
"testing"
"time"
"github.com/mjl-/mox/mlog"
"github.com/mjl-/mox/mox-"
"github.com/mjl-/mox/store"
)
@ -28,13 +29,14 @@ func TestView(t *testing.T) {
mox.MustLoadConfig(true, false)
defer store.Switchboard()()
acc, err := store.OpenAccount("mjl")
log := mlog.New("webmail", nil)
acc, err := store.OpenAccount(log, "mjl")
tcheck(t, err, "open account")
err = acc.SetPassword("test1234")
err = acc.SetPassword(log, "test1234")
tcheck(t, err, "set password")
defer func() {
err := acc.Close()
xlog.Check(err, "closing account")
pkglog.Check(err, "closing account")
}()
api := Webmail{maxMessageSize: 1024 * 1024}
@ -465,7 +467,7 @@ type eventReader struct {
func (r eventReader) Get(name string, event any) {
timer := time.AfterFunc(2*time.Second, func() {
r.r.Close()
xlog.Print("event timeout")
pkglog.Print("event timeout")
})
defer timer.Stop()

View File

@ -26,11 +26,12 @@ import (
_ "embed"
"golang.org/x/exp/slog"
"golang.org/x/net/html"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"golang.org/x/net/html"
"github.com/mjl-/bstore"
"github.com/mjl-/sherpa"
@ -48,7 +49,7 @@ func init() {
mox.LimitersInit()
}
var xlog = mlog.New("webmail")
var pkglog = mlog.New("webmail", nil)
// We pass the request to the sherpa handler so the TLS info can be used for
// the Received header in submitted messages. Most API calls need just the
@ -115,7 +116,7 @@ func xcheckf(ctx context.Context, err error, format string, args ...any) {
}
msg := fmt.Sprintf(format, args...)
errmsg := fmt.Sprintf("%s: %s", msg, err)
xlog.WithContext(ctx).Errorx(msg, err)
pkglog.WithContext(ctx).Errorx(msg, err)
panic(&sherpa.Error{Code: "server:error", Message: errmsg})
}
@ -125,7 +126,7 @@ func xcheckuserf(ctx context.Context, err error, format string, args ...any) {
}
msg := fmt.Sprintf(format, args...)
errmsg := fmt.Sprintf("%s: %s", msg, err)
xlog.WithContext(ctx).Errorx(msg, err)
pkglog.WithContext(ctx).Errorx(msg, err)
panic(&sherpa.Error{Code: "user:error", Message: errmsg})
}
@ -166,7 +167,7 @@ var webmail = &merged{
// fallbackMtime returns a time to use for the Last-Modified header in case we
// cannot find a file, e.g. when used in production.
func fallbackMtime(log *mlog.Log) time.Time {
func fallbackMtime(log mlog.Log) time.Time {
p, err := os.Executable()
log.Check(err, "finding executable for mtime")
if err == nil {
@ -180,7 +181,7 @@ func fallbackMtime(log *mlog.Log) time.Time {
return time.Now()
}
func (m *merged) serve(ctx context.Context, log *mlog.Log, w http.ResponseWriter, r *http.Request) {
func (m *merged) serve(ctx context.Context, log mlog.Log, w http.ResponseWriter, r *http.Request) {
// We typically return the embedded file, but during development it's handy
// to load from disk.
fhtml, _ := os.Open(m.htmlPath)
@ -304,7 +305,7 @@ func (w gzipInjector) WriteHeader(statusCode int) {
// should already have set the content-type. We use this to return a file from
// the local file system (during development), or embedded in the binary (when
// deployed).
func serveContentFallback(log *mlog.Log, w http.ResponseWriter, r *http.Request, path string, fallback []byte) {
func serveContentFallback(log mlog.Log, w http.ResponseWriter, r *http.Request, path string, fallback []byte) {
f, err := os.Open(path)
if err == nil {
defer f.Close()
@ -332,7 +333,7 @@ func Handler(maxMessageSize int64) func(w http.ResponseWriter, r *http.Request)
func handle(apiHandler http.Handler, w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := xlog.WithContext(ctx).Fields(mlog.Field("userauth", ""))
log := pkglog.WithContext(ctx).With(slog.String("userauth", ""))
// Server-sent event connection, for all initial data (list of mailboxes), list of
// messages, and all events afterwards. Authenticated through a token in the query
@ -349,8 +350,8 @@ func handle(apiHandler http.Handler, w http.ResponseWriter, r *http.Request) {
return
}
if lw, ok := w.(interface{ AddField(f mlog.Pair) }); ok {
lw.AddField(mlog.Field("authaccount", accName))
if lw, ok := w.(interface{ AddAttr(a slog.Attr) }); ok {
lw.AddAttr(slog.String("authaccount", accName))
}
defer func() {
@ -360,7 +361,7 @@ func handle(apiHandler http.Handler, w http.ResponseWriter, r *http.Request) {
}
err, ok := x.(*sherpa.Error)
if !ok {
log.WithContext(ctx).Error("handle panic", mlog.Field("err", x))
log.WithContext(ctx).Error("handle panic", slog.Any("err", x))
debug.PrintStack()
metrics.PanicInc(metrics.Webmailhandle)
panic(x)
@ -462,7 +463,7 @@ func handle(apiHandler http.Handler, w http.ResponseWriter, r *http.Request) {
var err error
acc, err = store.OpenAccount(accName)
acc, err = store.OpenAccount(log, accName)
xcheckf(ctx, err, "open account")
m = store.Message{ID: id}
@ -922,7 +923,7 @@ func acceptsGzip(r *http.Request) bool {
// HTML, setHeaders is called to write the required headers for content-type and
// CSP. On error, setHeader is not called, no output is written and the caller
// should write an error response.
func inlineSanitizeHTML(log *mlog.Log, setHeaders func(), w io.Writer, p *message.Part, parents []*message.Part) error {
func inlineSanitizeHTML(log mlog.Log, setHeaders func(), w io.Writer, p *message.Part, parents []*message.Part) error {
// Prepare cids if there is a chance we will use them.
cids := map[string]*message.Part{}
for _, parent := range parents {

View File

@ -252,14 +252,14 @@ type testmsg struct {
}
func tdeliver(t *testing.T, acc *store.Account, tm *testmsg) {
msgFile, err := store.CreateMessageTemp("webmail-test")
msgFile, err := store.CreateMessageTemp(pkglog, "webmail-test")
tcheck(t, err, "create message temp")
defer os.Remove(msgFile.Name())
defer msgFile.Close()
size, err := msgFile.Write(tm.msg.Marshal(t))
tcheck(t, err, "write message temp")
m := store.Message{Flags: tm.Flags, Keywords: tm.Keywords, Size: int64(size)}
err = acc.DeliverMailbox(xlog, tm.Mailbox, &m, msgFile)
err = acc.DeliverMailbox(pkglog, tm.Mailbox, &m, msgFile)
tcheck(t, err, "deliver test message")
err = msgFile.Close()
tcheck(t, err, "closing test message")
@ -279,13 +279,13 @@ func TestWebmail(t *testing.T) {
mox.MustLoadConfig(true, false)
defer store.Switchboard()()
acc, err := store.OpenAccount("mjl")
acc, err := store.OpenAccount(pkglog, "mjl")
tcheck(t, err, "open account")
err = acc.SetPassword("test1234")
err = acc.SetPassword(pkglog, "test1234")
tcheck(t, err, "set password")
defer func() {
err := acc.Close()
xlog.Check(err, "closing account")
pkglog.Check(err, "closing account")
}()
api := Webmail{maxMessageSize: 1024 * 1024}