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

@ -20,6 +20,8 @@ import (
_ "embed"
"golang.org/x/exp/slog"
"github.com/mjl-/sherpa"
"github.com/mjl-/sherpadoc"
"github.com/mjl-/sherpaprom"
@ -37,7 +39,7 @@ func init() {
mox.LimitersInit()
}
var xlog = mlog.New("webaccount")
var pkglog = mlog.New("webaccount", nil)
//go:embed accountapi.json
var accountapiJSON []byte
@ -52,7 +54,7 @@ var accountSherpaHandler http.Handler
func mustParseAPI(api string, buf []byte) (doc sherpadoc.Section) {
err := json.Unmarshal(buf, &doc)
if err != nil {
xlog.Fatalx("parsing webaccount api docs", err, mlog.Field("api", api))
pkglog.Fatalx("parsing webaccount api docs", err, slog.String("api", api))
}
return doc
}
@ -60,12 +62,12 @@ func mustParseAPI(api string, buf []byte) (doc sherpadoc.Section) {
func init() {
collector, err := sherpaprom.NewCollector("moxaccount", nil)
if err != nil {
xlog.Fatalx("creating sherpa prometheus collector", err)
pkglog.Fatalx("creating sherpa prometheus collector", err)
}
accountSherpaHandler, err = sherpa.NewHandler("/api/", moxvar.Version, Account{}, &accountDoc, &sherpa.HandlerOpts{Collector: collector, AdjustFunctionNames: "none"})
if err != nil {
xlog.Fatalx("sherpa handler", err)
pkglog.Fatalx("sherpa handler", err)
}
}
@ -75,7 +77,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})
}
@ -85,7 +87,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})
}
@ -96,7 +98,7 @@ type Account struct{}
// CheckAuth checks http basic auth, returns login address and account name if
// valid, and writes http response and returns empty string otherwise.
func CheckAuth(ctx context.Context, log *mlog.Log, kind string, w http.ResponseWriter, r *http.Request) (address, account string) {
func CheckAuth(ctx context.Context, log mlog.Log, kind string, w http.ResponseWriter, r *http.Request) (address, account string) {
authResult := "error"
start := time.Now()
var addr *net.TCPAddr
@ -111,7 +113,7 @@ func CheckAuth(ctx context.Context, log *mlog.Log, kind string, w http.ResponseW
var remoteIP net.IP
addr, err = net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err != nil {
log.Errorx("parsing remote address", err, mlog.Field("addr", r.RemoteAddr))
log.Errorx("parsing remote address", err, slog.Any("addr", r.RemoteAddr))
} else if addr != nil {
remoteIP = addr.IP
}
@ -127,10 +129,10 @@ func CheckAuth(ctx context.Context, log *mlog.Log, kind string, w http.ResponseW
log.Debugx("parsing base64", err)
} else if t := strings.SplitN(string(authBuf), ":", 2); len(t) != 2 {
log.Debug("bad user:pass form")
} else if acc, err := store.OpenEmailAuth(t[0], t[1]); err != nil {
} else if acc, err := store.OpenEmailAuth(log, t[0], t[1]); err != nil {
if errors.Is(err, store.ErrUnknownCredentials) {
authResult = "badcreds"
log.Info("failed authentication attempt", mlog.Field("username", t[0]), mlog.Field("remote", remoteIP))
log.Info("failed authentication attempt", slog.String("username", t[0]), slog.Any("remote", remoteIP))
}
log.Errorx("open account", err)
} else {
@ -148,7 +150,7 @@ func CheckAuth(ctx context.Context, log *mlog.Log, kind string, w http.ResponseW
func Handle(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), mlog.CidKey, mox.Cid())
log := xlog.WithContext(ctx).Fields(mlog.Field("userauth", ""))
log := pkglog.WithContext(ctx).With(slog.String("userauth", ""))
// Without authentication. The token is unguessable.
if r.URL.Path == "/importprogress" {
@ -213,8 +215,8 @@ func Handle(w http.ResponseWriter, r *http.Request) {
return
}
if lw, ok := w.(interface{ AddField(p 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))
}
switch r.URL.Path {
@ -239,7 +241,7 @@ func Handle(w http.ResponseWriter, r *http.Request) {
maildir := strings.Contains(r.URL.Path, "maildir")
tgz := strings.Contains(r.URL.Path, ".tgz")
acc, err := store.OpenAccount(accName)
acc, err := store.OpenAccount(log, accName)
if err != nil {
log.Errorx("open account for export", err)
http.Error(w, "500 - internal server error", http.StatusInternalServerError)
@ -336,17 +338,18 @@ var authCtxKey ctxKey = "account"
// Sessions are not interrupted, and will keep working. New login attempts must use the new password.
// Password must be at least 8 characters.
func (Account) SetPassword(ctx context.Context, password string) {
log := pkglog.WithContext(ctx)
if len(password) < 8 {
panic(&sherpa.Error{Code: "user:error", Message: "password must be at least 8 characters"})
}
accountName := ctx.Value(authCtxKey).(string)
acc, err := store.OpenAccount(accountName)
acc, err := store.OpenAccount(log, accountName)
xcheckf(ctx, err, "open account")
defer func() {
err := acc.Close()
xlog.Check(err, "closing account")
log.Check(err, "closing account")
}()
err = acc.SetPassword(password)
err = acc.SetPassword(log, password)
xcheckf(ctx, err, "setting password")
}

View File

@ -41,7 +41,8 @@ func TestAccount(t *testing.T) {
mox.ConfigStaticPath = filepath.FromSlash("../testdata/httpaccount/mox.conf")
mox.ConfigDynamicPath = filepath.Join(filepath.Dir(mox.ConfigStaticPath), "domains.conf")
mox.MustLoadConfig(true, false)
acc, err := store.OpenAccount("mjl")
log := mlog.New("webaccount", nil)
acc, err := store.OpenAccount(log, "mjl")
tcheck(t, err, "open account")
defer func() {
err = acc.Close()
@ -49,8 +50,6 @@ func TestAccount(t *testing.T) {
}()
defer store.Switchboard()()
log := mlog.New("store")
test := func(userpass string, expect string) {
t.Helper()

View File

@ -21,6 +21,7 @@ import (
"time"
"golang.org/x/exp/maps"
"golang.org/x/exp/slog"
"golang.org/x/text/unicode/norm"
"github.com/mjl-/bstore"
@ -64,10 +65,10 @@ var importers = struct {
// ImportManage should be run as a goroutine, it manages imports of mboxes/maildirs, propagating progress over SSE connections.
func ImportManage() {
log := mlog.New("httpimport")
log := mlog.New("httpimport", nil)
defer func() {
if x := recover(); x != nil {
log.Error("import manage panic", mlog.Field("err", x))
log.Error("import manage panic", slog.Any("err", x))
debug.PrintStack()
metrics.PanicInc(metrics.Importmanage)
}
@ -94,7 +95,7 @@ func ImportManage() {
sendEvent := func(kind string, v any) {
buf, err := json.Marshal(v)
if err != nil {
log.Errorx("marshal event", err, mlog.Field("kind", kind), mlog.Field("event", v))
log.Errorx("marshal event", err, slog.String("kind", kind), slog.Any("event", v))
return
}
ssemsg := fmt.Sprintf("event: %s\ndata: %s\n\n", kind, buf)
@ -199,7 +200,7 @@ type importStep struct {
// importStart prepare the import and launches the goroutine to actually import.
// importStart is responsible for closing f and removing f.
func importStart(log *mlog.Log, accName string, f *os.File, skipMailboxPrefix string) (string, error) {
func importStart(log mlog.Log, accName string, f *os.File, skipMailboxPrefix string) (string, error) {
defer func() {
if f != nil {
store.CloseRemoveTempFile(log, f, "upload for import")
@ -249,7 +250,7 @@ func importStart(log *mlog.Log, accName string, f *os.File, skipMailboxPrefix st
tr = tar.NewReader(gzr)
}
acc, err := store.OpenAccount(accName)
acc, err := store.OpenAccount(log, accName)
if err != nil {
return "", fmt.Errorf("open acount: %v", err)
}
@ -276,7 +277,7 @@ func importStart(log *mlog.Log, accName string, f *os.File, skipMailboxPrefix st
// importMessages imports the messages from zip/tgz file f.
// importMessages is responsible for unlocking and closing acc, and closing tx and f.
func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store.Account, tx *bstore.Tx, zr *zip.Reader, tr *tar.Reader, f *os.File, skipMailboxPrefix string) {
func importMessages(ctx context.Context, log mlog.Log, token string, acc *store.Account, tx *bstore.Tx, zr *zip.Reader, tr *tar.Reader, f *os.File, skipMailboxPrefix string) {
// If a fatal processing error occurs, we panic with this type.
type importError struct{ Err error }
@ -289,7 +290,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
sendEvent := func(kind string, v any) {
buf, err := json.Marshal(v)
if err != nil {
log.Errorx("marshal event", err, mlog.Field("kind", kind), mlog.Field("event", v))
log.Errorx("marshal event", err, slog.String("kind", kind), slog.Any("event", v))
return
}
ssemsg := fmt.Sprintf("event: %s\ndata: %s\n\n", kind, buf)
@ -317,7 +318,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
for _, id := range deliveredIDs {
p := acc.MessagePath(id)
err := os.Remove(p)
log.Check(err, "closing message file after import error", mlog.Field("path", p))
log.Check(err, "closing message file after import error", slog.String("path", p))
}
if tx != nil {
err := tx.Rollback()
@ -338,7 +339,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
problemf("%s (aborting)", err.Err)
sendEvent("aborted", importAborted{})
} else {
log.Error("import panic", mlog.Field("err", x))
log.Error("import panic", slog.Any("err", x))
debug.PrintStack()
metrics.PanicInc(metrics.Importmessages)
}
@ -511,7 +512,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
}
// Parse message and store parsed information for later fast retrieval.
p, err := message.EnsurePart(log, false, f, m.Size)
p, err := message.EnsurePart(log.Logger, false, f, m.Size)
if err != nil {
problemf("parsing message %s: %s (continuing)", pos, err)
}
@ -562,7 +563,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
}
mb := xensureMailbox(mailbox)
mr := store.NewMboxReader(store.CreateMessageTemp, filename, r, log)
mr := store.NewMboxReader(log, store.CreateMessageTemp, filename, r)
for {
m, mf, pos, err := mr.Next()
if err == io.EOF {
@ -582,7 +583,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
}
mb := xensureMailbox(mailbox)
f, err := store.CreateMessageTemp("import")
f, err := store.CreateMessageTemp(log, "import")
ximportcheckf(err, "creating temp message")
defer func() {
if f != nil {
@ -707,7 +708,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
mailbox := path.Dir(name)
dovecotKeywords := map[rune]string{}
words, err := store.ParseDovecotKeywordsFlags(r, log)
log.Check(err, "parsing dovecot keywords for mailbox", mlog.Field("mailbox", mailbox))
log.Check(err, "parsing dovecot keywords for mailbox", slog.String("mailbox", mailbox))
for i, kw := range words {
dovecotKeywords['a'+rune(i)] = kw
}
@ -801,7 +802,7 @@ func importMessages(ctx context.Context, log *mlog.Log, token string, acc *store
for _, count := range messages {
total += count
}
log.Debug("messages imported", mlog.Field("total", total))
log.Debug("messages imported", slog.Int("total", total))
// Send final update for count of last-imported mailbox.
if prevMailbox != "" {