diff --git a/cmd/mailroom/main.go b/cmd/mailroom/main.go index 7866850cb..b6c277131 100644 --- a/cmd/mailroom/main.go +++ b/cmd/mailroom/main.go @@ -7,15 +7,16 @@ import ( "os/signal" goruntime "runtime" "syscall" + "time" + "github.com/getsentry/sentry-go" _ "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" + slogmulti "github.com/samber/slog-multi" + slogsentry "github.com/samber/slog-sentry" _ "github.com/nyaruka/mailroom/core/handlers" _ "github.com/nyaruka/mailroom/core/hooks" @@ -70,46 +71,52 @@ func main() { os.Exit(1) } - level, err := logrus.ParseLevel(config.LogLevel) + var level slog.Level + err := level.UnmarshalText([]byte(config.LogLevel)) if err != nil { - slog.Error("invalid log level", "level", level) + ulog.Fatalf("invalid log level %s", level) os.Exit(1) } - logrus.SetLevel(level) - logrus.SetOutput(os.Stdout) - logrus.SetFormatter(&logrus.TextFormatter{}) + // configure our logger + logHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}) + slog.SetDefault(slog.New(logHandler)) - // 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) + logger := slog.With("comp", "main") + logger.Info("starting mailroom", "version", version, "released", date) // if we have a DSN entry, try to initialize it if config.SentryDSN != "" { - hook, err := logrus_sentry.NewSentryHook(config.SentryDSN, []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}) - hook.Timeout = 0 - hook.StacktraceConfiguration.Enable = true - hook.StacktraceConfiguration.Skip = 4 - hook.StacktraceConfiguration.Context = 5 - hook.StacktraceConfiguration.IncludeErrorBreadcrumb = true + err := sentry.Init(sentry.ClientOptions{ + Dsn: config.SentryDSN, + EnableTracing: false, + }) if err != nil { - log.Error("unable to configure sentry hook", "dsn", config.SentryDSN, "error", err) + ulog.Fatalf("error initiating sentry client, error %s, dsn %s", err, config.SentryDSN) os.Exit(1) } - logrus.StandardLogger().Hooks.Add(hook) + + defer sentry.Flush(2 * time.Second) + + logger = slog.New( + slogmulti.Fanout( + logHandler, + slogsentry.Option{Level: slog.LevelError}.NewSentryHandler(), + ), + ) + logger = logger.With("release", version) + slog.SetDefault(logger) } if config.UUIDSeed != 0 { uuids.SetGenerator(uuids.NewSeededGenerator(int64(config.UUIDSeed))) - log.Warn("using seeded UUID generation", "uuid-seed", config.UUIDSeed) + logger.Warn("using seeded UUID generation", "uuid-seed", config.UUIDSeed) } mr := mailroom.NewMailroom(config) err = mr.Start() if err != nil { - log.Error("unable to start server", "error", err) + logger.Error("unable to start server", "error", err) } // handle our signals diff --git a/go.mod b/go.mod index 3980dbca3..fc0332554 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/aws/aws-sdk-go v1.45.24 github.com/buger/jsonparser v1.1.1 github.com/edganiukov/fcm v0.4.0 + github.com/getsentry/sentry-go v0.22.0 github.com/go-chi/chi v4.1.2+incompatible github.com/go-playground/validator/v10 v10.14.1 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -27,8 +28,9 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_model v0.5.0 github.com/prometheus/common v0.44.0 + github.com/samber/slog-multi v1.0.2 + github.com/samber/slog-sentry v1.2.2 github.com/shopspring/decimal v1.3.1 - github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 golang.org/x/exp v0.0.0-20231006140011-7918f672742d ) @@ -37,11 +39,9 @@ require ( github.com/Shopify/gomail v0.0.0-20220729171026-0784ece65e69 // indirect github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 // indirect github.com/blevesearch/segment v0.9.1 // indirect - github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect - github.com/getsentry/raven-go v0.2.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect @@ -56,7 +56,9 @@ require ( github.com/nyaruka/null/v2 v2.0.3 // indirect github.com/nyaruka/phonenumbers v1.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/samber/lo v1.38.1 // indirect github.com/sergi/go-diff v1.3.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum index 4386f486f..163bf294b 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+j github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s= -github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,10 +22,12 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/getsentry/sentry-go v0.22.0 h1:XNX9zKbv7baSEI65l+H1GEJgSeIC1c7EN5kluWaP6dM= +github.com/getsentry/sentry-go v0.22.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -49,8 +49,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -91,8 +91,6 @@ github.com/nyaruka/goflow v0.197.2 h1:g/C31OTTCGqIRe/MkWqsuHGKDjR3i3yaD5kPOE6aPS github.com/nyaruka/goflow v0.197.2/go.mod h1:85aGw8FytONUE8W22xmkgYWIVwgZszeYtaeLowYqaq0= github.com/nyaruka/librato v1.1.1 h1:0nTYtJLl3Sn7lX3CuHsLf+nXy1k/tGV0OjVxLy3Et4s= github.com/nyaruka/librato v1.1.1/go.mod h1:fme1Fu1PT2qvkaBZyw8WW+SrnFe2qeeCWpvqmAaKAKE= -github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d h1:hyp9u36KIwbTCo2JAJ+TuJcJBc+UZzEig7RI/S5Dvkc= -github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d/go.mod h1:FGdPJVDTNqbRAD+2RvnK9YoO2HcEW7ogSMPzc90b638= github.com/nyaruka/null/v2 v2.0.3 h1:rdmMRQyVzrOF3Jff/gpU/7BDR9mQX0lcLl4yImsA3kw= github.com/nyaruka/null/v2 v2.0.3/go.mod h1:OCVeCkCXwrg5/qE6RU0c1oUVZBy+ZDrT+xYg1XSaIWA= github.com/nyaruka/null/v3 v3.0.0 h1:JvOiNuKmRBFHxzZFt4sWii+ewmMkCQ1vO7X0clTNn6E= @@ -107,6 +105,8 @@ github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5Bn github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -115,6 +115,12 @@ github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cY github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ= +github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= +github.com/samber/slog-sentry v1.2.2 h1:S0glIVITlGCCfSvIOte2Sh63HMHJpYN3hDr+97hILIk= +github.com/samber/slog-sentry v1.2.2/go.mod h1:bHm8jm1dks0p+xc/lH2i4TIFwnPcMTvZeHgCBj5+uhA= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= diff --git a/testsuite/testsuite.go b/testsuite/testsuite.go index 3fe15d552..e8dee65bf 100644 --- a/testsuite/testsuite.go +++ b/testsuite/testsuite.go @@ -3,6 +3,7 @@ package testsuite import ( "context" "fmt" + "log/slog" "os" "os/exec" "path" @@ -14,7 +15,6 @@ import ( "github.com/nyaruka/mailroom/runtime" "github.com/nyaruka/rp-indexer/v8/indexers" "github.com/olivere/elastic/v7" - "github.com/sirupsen/logrus" ) var _db *sqlx.DB @@ -84,7 +84,7 @@ func Runtime() (context.Context, *runtime.Runtime) { Config: cfg, } - logrus.SetLevel(logrus.DebugLevel) + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))) return context.Background(), rt } diff --git a/utils/logrus.go b/utils/logrus.go deleted file mode 100644 index 2650bf9bb..000000000 --- a/utils/logrus.go +++ /dev/null @@ -1,92 +0,0 @@ -// 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{}