Skip to content

Commit

Permalink
Start switching to slog
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanseymour committed Aug 29, 2023
1 parent ddf5aa0 commit bae35c6
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: CI
on: [push, pull_request]
env:
go-version: "1.20.x"
go-version: "1.21.x"
jobs:
test:
name: Test
Expand Down
36 changes: 24 additions & 12 deletions cmd/mailroom/main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package main

import (
"fmt"
"log/slog"
"os"
"os/signal"
goruntime "runtime"
"syscall"

_ "github.com/lib/pq"
"github.com/nyaruka/ezconf"
"github.com/nyaruka/gocommon/uuids"
"github.com/nyaruka/logrus_sentry"
"github.com/nyaruka/mailroom"
"github.com/nyaruka/mailroom/runtime"
"github.com/nyaruka/mailroom/utils"
"github.com/sirupsen/logrus"

_ "github.com/nyaruka/mailroom/core/handlers"
_ "github.com/nyaruka/mailroom/core/hooks"
Expand Down Expand Up @@ -42,9 +47,6 @@ import (
_ "github.com/nyaruka/mailroom/web/simulation"
_ "github.com/nyaruka/mailroom/web/surveyor"
_ "github.com/nyaruka/mailroom/web/ticket"

_ "github.com/lib/pq"
"github.com/sirupsen/logrus"
)

var (
Expand All @@ -65,18 +67,25 @@ func main() {

// ensure config is valid
if err := config.Validate(); err != nil {
logrus.Fatalf("invalid config: %s", err)
slog.Error("invalid config", "error", err)
os.Exit(1)
}

level, err := logrus.ParseLevel(config.LogLevel)
if err != nil {
logrus.Fatalf("invalid log level '%s'", level)
slog.Error("invalid log level", "level", level)
os.Exit(1)
}

logrus.SetLevel(level)
logrus.SetOutput(os.Stdout)
logrus.SetFormatter(&logrus.TextFormatter{})
logrus.WithField("version", version).WithField("released", date).Info("starting mailroom")

// configure golang std structured logging to route to logrus
slog.SetDefault(slog.New(utils.NewLogrusHandler(logrus.StandardLogger())))

log := slog.With("comp", "main")
log.Info("starting mailroom", "version", version, "released", date)

// if we have a DSN entry, try to initialize it
if config.SentryDSN != "" {
Expand All @@ -87,20 +96,21 @@ func main() {
hook.StacktraceConfiguration.Context = 5
hook.StacktraceConfiguration.IncludeErrorBreadcrumb = true
if err != nil {
logrus.Fatalf("invalid sentry DSN: '%s': %s", config.SentryDSN, err)
log.Error("unable to configure sentry hook", "dsn", config.SentryDSN, "error", err)
os.Exit(1)
}
logrus.StandardLogger().Hooks.Add(hook)
}

if config.UUIDSeed != 0 {
uuids.SetGenerator(uuids.NewSeededGenerator(int64(config.UUIDSeed)))
logrus.WithField("uuid-seed", config.UUIDSeed).Warn("using seeded UUID generation which is only appropriate for testing environments")
log.Warn("using seeded UUID generation", "uuid-seed", config.UUIDSeed)
}

mr := mailroom.NewMailroom(config)
err = mr.Start()
if err != nil {
logrus.Fatalf("error starting server: %s", err)
log.Error("unable to start server", "error", err)
}

// handle our signals
Expand All @@ -114,14 +124,16 @@ func handleSignals(mr *mailroom.Mailroom) {

for {
sig := <-sigs
log := slog.With("comp", "main", "signal", sig)

switch sig {
case syscall.SIGQUIT:
buf := make([]byte, 1<<20)
stacklen := goruntime.Stack(buf, true)
logrus.WithField("comp", "main").WithField("signal", sig).Info("received quit signal, dumping stack")
logrus.Printf("\n%s", buf[:stacklen])
log.Info("received quit signal, dumping stack")
fmt.Printf("\n%s", buf[:stacklen])
case syscall.SIGINT, syscall.SIGTERM:
logrus.WithField("comp", "main").WithField("signal", sig).Info("received exit signal, exiting")
log.Info("received exit signal, exiting")
mr.Stop()
return
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/nyaruka/mailroom

go 1.20
go 1.21

require (
github.com/Masterminds/semver v1.5.0
Expand Down
28 changes: 15 additions & 13 deletions mailroom.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mailroom
import (
"context"
"database/sql"
"log/slog"
"net/url"
"strings"
"sync"
Expand All @@ -20,7 +21,6 @@ import (
"github.com/gomodule/redigo/redis"
"github.com/jmoiron/sqlx"
"github.com/olivere/elastic/v7"
"github.com/sirupsen/logrus"
)

// InitFunction is a function that will be called when mailroom starts
Expand Down Expand Up @@ -83,20 +83,20 @@ func NewMailroom(config *runtime.Config) *Mailroom {
func (mr *Mailroom) Start() error {
c := mr.rt.Config

log := logrus.WithFields(logrus.Fields{"state": "starting"})
log := slog.With("comp", "mailroom")

var err error
_, mr.rt.DB, err = openAndCheckDBConnection(c.DB, c.DBPoolSize)
if err != nil {
log.WithError(err).Error("db not reachable")
log.Error("db not reachable", "error", err)
} else {
log.Info("db ok")
}

if c.ReadonlyDB != "" {
mr.rt.ReadonlyDB, _, err = openAndCheckDBConnection(c.ReadonlyDB, c.DBPoolSize)
if err != nil {
log.WithError(err).Error("readonly db not reachable")
log.Error("readonly db not reachable", "error", err)
} else {
log.Info("readonly db ok")
}
Expand All @@ -108,7 +108,7 @@ func (mr *Mailroom) Start() error {

mr.rt.RP, err = openAndCheckRedisPool(c.Redis)
if err != nil {
log.WithError(err).Error("redis not reachable")
log.Error("redis not reachable", "error", err)
} else {
log.Info("redis ok")
}
Expand Down Expand Up @@ -141,32 +141,32 @@ func (mr *Mailroom) Start() error {

// check our storages
if err := checkStorage(mr.rt.AttachmentStorage); err != nil {
log.WithError(err).Error(mr.rt.AttachmentStorage.Name() + " attachment storage not available")
log.Error(mr.rt.AttachmentStorage.Name()+" attachment storage not available", "error", err)
} else {
log.Info(mr.rt.AttachmentStorage.Name() + " attachment storage ok")
}
if err := checkStorage(mr.rt.SessionStorage); err != nil {
log.WithError(err).Error(mr.rt.SessionStorage.Name() + " session storage not available")
log.Error(mr.rt.SessionStorage.Name()+" session storage not available", "error", err)
} else {
log.Info(mr.rt.SessionStorage.Name() + " session storage ok")
}
if err := checkStorage(mr.rt.LogStorage); err != nil {
log.WithError(err).Error(mr.rt.LogStorage.Name() + " log storage not available")
log.Error(mr.rt.LogStorage.Name()+" log storage not available", "error", err)
} else {
log.Info(mr.rt.LogStorage.Name() + " log storage ok")
}

// initialize our elastic client
mr.rt.ES, err = newElasticClient(c.Elastic, c.ElasticUsername, c.ElasticPassword)
if err != nil {
log.WithError(err).Error("elastic search not available")
log.Error("elastic search not available", "error", err)
} else {
log.Info("elastic ok")
}

// warn if we won't be doing FCM syncing
if c.FCMKey == "" {
logrus.Warn("fcm not configured, no syncing of android channels")
log.Warn("fcm not configured, no android syncing")
}

for _, initFunc := range initFunctions {
Expand All @@ -188,14 +188,16 @@ func (mr *Mailroom) Start() error {
mr.webserver = web.NewServer(mr.ctx, mr.rt, mr.wg)
mr.webserver.Start()

logrus.WithField("domain", c.Domain).Info("mailroom started")
log.Info("mailroom started", "domain", c.Domain)

return nil
}

// Stop stops the mailroom service
func (mr *Mailroom) Stop() error {
logrus.Info("mailroom stopping")
log := slog.With("comp", "mailroom")
log.Info("mailroom stopping")

mr.batchForeman.Stop()
mr.handlerForeman.Stop()
analytics.Stop()
Expand All @@ -212,7 +214,7 @@ func (mr *Mailroom) Stop() error {
mr.rt.ES.Stop()
}

logrus.Info("mailroom stopped")
log.Info("mailroom stopped")
return nil
}

Expand Down
92 changes: 92 additions & 0 deletions utils/logrus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Structured logging handler for logrus so we can rewrite code to use slog package incrementally. Once all logging is
// happening via slog, we just need to hook up Sentry directly to that, and then we can get rid of this file.
package utils

import (
"context"
"log/slog"
"slices"
"strings"

"github.com/sirupsen/logrus"
)

var levels = map[slog.Level]logrus.Level{
slog.LevelError: logrus.ErrorLevel,
slog.LevelWarn: logrus.WarnLevel,
slog.LevelInfo: logrus.InfoLevel,
slog.LevelDebug: logrus.DebugLevel,
}

type LogrusHandler struct {
logger *logrus.Logger
groups []string
attrs []slog.Attr
}

func NewLogrusHandler(logger *logrus.Logger) *LogrusHandler {
return &LogrusHandler{logger: logger}
}

func (l *LogrusHandler) clone() *LogrusHandler {
return &LogrusHandler{
logger: l.logger,
groups: slices.Clip(l.groups),
attrs: slices.Clip(l.attrs),
}
}

func (l *LogrusHandler) Enabled(ctx context.Context, level slog.Level) bool {
return levels[level] <= l.logger.GetLevel()
}

func (l *LogrusHandler) Handle(ctx context.Context, r slog.Record) error {
log := logrus.NewEntry(l.logger)
if r.Time.IsZero() {
log = log.WithTime(r.Time)
}

f := logrus.Fields{}
for _, a := range l.attrs {
if a.Key != "" {
f[a.Key] = a.Value
}
}
log = log.WithFields(f)

r.Attrs(func(attr slog.Attr) bool {
if attr.Key == "" {
return true
}
log = log.WithField(attr.Key, attr.Value)
return true
})
log.Logf(levels[r.Level], r.Message)
return nil
}

func (l *LogrusHandler) groupPrefix() string {
if len(l.groups) > 0 {
return strings.Join(l.groups, ":") + ":"
}
return ""
}

func (l *LogrusHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newHandler := l.clone()
for _, a := range attrs {
newHandler.attrs = append(newHandler.attrs, slog.Attr{
Key: l.groupPrefix() + a.Key,
Value: a.Value,
})
}
return newHandler
}

func (l *LogrusHandler) WithGroup(name string) slog.Handler {
newHandler := l.clone()
newHandler.groups = append(newHandler.groups, name)
return newHandler
}

var _ slog.Handler = &LogrusHandler{}
16 changes: 11 additions & 5 deletions web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"compress/flate"
"context"
"fmt"
"log/slog"
"net/http"
"sync"
"time"
Expand All @@ -13,7 +14,6 @@ import (
"github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/mailroom/runtime"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

const (
Expand Down Expand Up @@ -97,14 +97,16 @@ func (s *Server) WrapHandler(handler Handler) http.HandlerFunc {
return
}

logrus.WithError(err).WithField("http_request", r).Error("error handling request")
slog.Error("error handling request", "comp", "server", "request", r, "error", err)

WriteMarshalled(w, http.StatusInternalServerError, NewErrorResponse(err))
}
}

// Start starts our web server, listening for new requests
func (s *Server) Start() {
log := slog.With("comp", "server", "address", s.rt.Config.Address, "port", s.rt.Config.Port)

s.wg.Add(1)

// start serving HTTP
Expand All @@ -113,18 +115,22 @@ func (s *Server) Start() {

err := s.httpServer.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
logrus.WithFields(logrus.Fields{"comp": "server", "state": "stopping", "err": err}).Error()
log.Error("error listening", "error", err)
}
}()

logrus.WithField("address", s.rt.Config.Address).WithField("port", s.rt.Config.Port).Info("server started")
log.Info("server started")
}

// Stop stops our web server
func (s *Server) Stop() {
log := slog.With("comp", "server")

// shut down our HTTP server
if err := s.httpServer.Shutdown(context.Background()); err != nil {
logrus.WithField("state", "stopping").WithError(err).Error("error shutting down server")
log.Error("error shutting down server", "error", err)
} else {
log.Info("server stopped")
}
}

Expand Down
Loading

0 comments on commit bae35c6

Please sign in to comment.