From a5aaa1e419d7d93a717a8f50e818813a318ea43e Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 12 Oct 2023 11:07:54 +0200 Subject: [PATCH 1/2] Replace logrus with slog in rapidpro backend --- backends/rapidpro/backend.go | 50 ++++++++++++++---------------- backends/rapidpro/channel_event.go | 6 ++-- backends/rapidpro/channel_log.go | 16 +++++----- backends/rapidpro/contact.go | 10 +++--- backends/rapidpro/msg.go | 6 ++-- backends/rapidpro/status.go | 18 +++++------ backends/rapidpro/urn.go | 8 ++--- 7 files changed, 56 insertions(+), 58 deletions(-) diff --git a/backends/rapidpro/backend.go b/backends/rapidpro/backend.go index 6ff73654c..7ee1dfb74 100644 --- a/backends/rapidpro/backend.go +++ b/backends/rapidpro/backend.go @@ -6,6 +6,7 @@ import ( "database/sql" "encoding/json" "fmt" + "log/slog" "net/url" "path" "path/filepath" @@ -29,7 +30,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/redisx" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // the name for our message queue @@ -105,10 +105,10 @@ func newBackend(cfg *courier.Config) courier.Backend { // Start starts our RapidPro backend, this tests our various connections and starts our spool flushers func (b *backend) Start() error { // parse and test our redis config - log := logrus.WithFields(logrus.Fields{ - "comp": "backend", - "state": "starting", - }) + log := slog.With( + "comp", "backend", + "state", "starting", + ) log.Info("starting backend") // parse and test our db config @@ -137,7 +137,7 @@ func (b *backend) Start() error { err = b.db.PingContext(ctx) cancel() if err != nil { - log.WithError(err).Error("db not reachable") + log.Error("db not reachable", "error", err) } else { log.Info("db ok") } @@ -183,7 +183,7 @@ func (b *backend) Start() error { defer conn.Close() _, err = conn.Do("PING") if err != nil { - log.WithError(err).Error("redis not reachable") + log.Error("redis not reachable", "error", err) } else { log.Info("redis ok") } @@ -221,12 +221,12 @@ func (b *backend) Start() error { // check our storages if err := checkStorage(b.attachmentStorage); err != nil { - log.WithError(err).Error(b.attachmentStorage.Name() + " attachment storage not available") + log.Error(b.attachmentStorage.Name()+" attachment storage not available", "error", err) } else { log.Info(b.attachmentStorage.Name() + " attachment storage ok") } if err := checkStorage(b.logStorage); err != nil { - log.WithError(err).Error(b.logStorage.Name() + " log storage not available") + log.Error(b.logStorage.Name()+" log storage not available", "error", err) } else { log.Info(b.logStorage.Name() + " log storage ok") } @@ -240,7 +240,7 @@ func (b *backend) Start() error { err = courier.EnsureSpoolDirPresent(b.config.SpoolDir, "events") } if err != nil { - log.WithError(err).Error("spool directories not writable") + log.Error("spool directories not writable", "error", err) } else { log.Info("spool directories ok") } @@ -260,7 +260,7 @@ func (b *backend) Start() error { courier.RegisterFlusher(path.Join(b.config.SpoolDir, "statuses"), b.flushStatusFile) courier.RegisterFlusher(path.Join(b.config.SpoolDir, "events"), b.flushChannelEventFile) - logrus.WithFields(logrus.Fields{"comp": "backend", "state": "started"}).Info("backend started") + slog.Info("backend started", "comp", "backend", "state", "started") return nil } @@ -496,14 +496,14 @@ func (b *backend) MarkOutgoingMsgComplete(ctx context.Context, msg courier.MsgOu rc.Send("expire", dateKey, 60*60*24*2) _, err := rc.Do("") if err != nil { - logrus.WithError(err).WithField("sent_msgs_key", dateKey).Error("unable to add new unsent message") + slog.Error("unable to add new unsent message", "error", err, "sent_msgs_key", dateKey) } // if our msg has an associated session and timeout, update that if dbMsg.SessionWaitStartedOn_ != nil { err = updateSessionTimeout(ctx, b, dbMsg.SessionID_, *dbMsg.SessionWaitStartedOn_, dbMsg.SessionTimeout_) if err != nil { - logrus.WithError(err).WithField("session_id", dbMsg.SessionID_).Error("unable to update session timeout") + slog.Error("unable to update session timeout", "error", err, "session_id", dbMsg.SessionID_) } } } @@ -529,7 +529,7 @@ func (b *backend) NewStatusUpdateByExternalID(channel courier.Channel, externalI // WriteStatusUpdate writes the passed in MsgStatus to our store func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUpdate) error { - log := logrus.WithFields(logrus.Fields{"msg_id": status.MsgID(), "msg_external_id": status.ExternalID(), "status": status.Status()}) + log := slog.With("msg_id", status.MsgID(), "msg_external_id", status.ExternalID(), "status", status.Status()) su := status.(*StatusUpdate) if status.MsgID() == courier.NilMsgID && status.ExternalID() == "" { @@ -553,7 +553,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp err := b.sentExternalIDs.Set(rc, fmt.Sprintf("%d|%s", su.ChannelID_, su.ExternalID_), fmt.Sprintf("%d", status.MsgID())) if err != nil { - log.WithError(err).Error("error recording external id") + log.Error("error recording external id", "error", err) } } @@ -561,7 +561,7 @@ func (b *backend) WriteStatusUpdate(ctx context.Context, status courier.StatusUp if status.Status() == courier.MsgStatusErrored { err := b.ClearMsgSent(ctx, status.MsgID()) if err != nil { - log.WithError(err).Error("error clearing sent flags") + log.Error("error clearing sent flags", "error", err) } } } @@ -799,16 +799,14 @@ func (b *backend) Heartbeat() error { analytics.Gauge("courier.bulk_queue", float64(bulkSize)) analytics.Gauge("courier.priority_queue", float64(prioritySize)) - logrus.WithFields(logrus.Fields{ - "db_busy": dbStats.InUse, - "db_idle": dbStats.Idle, - "db_wait_time": dbWaitDurationInPeriod, - "db_wait_count": dbWaitCountInPeriod, - "redis_wait_time": dbWaitDurationInPeriod, - "redis_wait_count": dbWaitCountInPeriod, - "priority_size": prioritySize, - "bulk_size": bulkSize, - }).Info("current analytics") + slog.Info("current analytics", "db_busy", dbStats.InUse, + "db_idle", dbStats.Idle, + "db_wait_time", dbWaitDurationInPeriod, + "db_wait_count", dbWaitCountInPeriod, + "redis_wait_time", dbWaitDurationInPeriod, + "redis_wait_count", dbWaitCountInPeriod, + "priority_size", prioritySize, + "bulk_size", bulkSize) return nil } diff --git a/backends/rapidpro/channel_event.go b/backends/rapidpro/channel_event.go index 8cb530dc9..e9b58c022 100644 --- a/backends/rapidpro/channel_event.go +++ b/backends/rapidpro/channel_event.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "log" + "log/slog" "os" "strconv" "time" @@ -14,7 +15,6 @@ import ( "github.com/nyaruka/courier" "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/null/v3" - "github.com/sirupsen/logrus" ) // ChannelEventID is the type of our channel event ids @@ -124,7 +124,7 @@ func writeChannelEvent(ctx context.Context, b *backend, event courier.ChannelEve // failed writing, write to our spool instead if err != nil { - logrus.WithError(err).WithField("channel_id", dbEvent.ChannelID).WithField("event_type", dbEvent.EventType_).Error("error writing channel event to db") + slog.Error("error writing channel event to db", "error", err, "channel_id", dbEvent.ChannelID, "event_type", dbEvent.EventType_) } if err != nil { @@ -171,7 +171,7 @@ func writeChannelEventToDB(ctx context.Context, b *backend, e *ChannelEvent, clo // if we had a problem queueing the event, log it err = queueChannelEvent(rc, contact, e) if err != nil { - logrus.WithError(err).WithField("evt_id", e.ID_).Error("error queueing channel event") + slog.Error("error queueing channel event", "error", err, "evt_id", e.ID_) } return nil diff --git a/backends/rapidpro/channel_log.go b/backends/rapidpro/channel_log.go index 3aba20d34..8c0fbce13 100644 --- a/backends/rapidpro/channel_log.go +++ b/backends/rapidpro/channel_log.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "path" "sync" "time" @@ -15,7 +16,6 @@ import ( "github.com/nyaruka/gocommon/jsonx" "github.com/nyaruka/gocommon/storage" "github.com/nyaruka/gocommon/syncx" - "github.com/sirupsen/logrus" ) const sqlInsertChannelLog = ` @@ -57,7 +57,7 @@ type channelError struct { // queues the passed in channel log to a writer func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) { - log := logrus.WithFields(logrus.Fields{"log_uuid": clog.UUID(), "log_type": clog.Type(), "channel_uuid": clog.Channel().UUID()}) + log := slog.With("log_uuid", clog.UUID(), "log_type", clog.Type(), "channel_uuid", clog.Channel().UUID()) dbChan := clog.Channel().(*Channel) // so that we don't save null @@ -79,7 +79,7 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) // if log is attached to a call or message, only write to storage if clog.Attached() { - log = log.WithField("storage", "s3") + log = log.With("storage", "s3") v := &stChannelLog{ UUID: clog.UUID(), Type: clog.Type(), @@ -94,7 +94,7 @@ func queueChannelLog(ctx context.Context, b *backend, clog *courier.ChannelLog) } } else { // otherwise write to database so it's retrievable - log = log.WithField("storage", "db") + log = log.With("storage", "db") v := &dbChannelLog{ UUID: clog.UUID(), Type: clog.Type(), @@ -136,14 +136,14 @@ func writeDBChannelLogs(ctx context.Context, db *sqlx.DB, batch []*dbChannelLog) for _, v := range batch { err = dbutil.BulkQuery(ctx, db, sqlInsertChannelLog, []*dbChannelLog{v}) if err != nil { - log := logrus.WithField("comp", "log writer").WithField("log_uuid", v.UUID) + log := slog.With("comp", "log writer", "log_uuid", v.UUID) if qerr := dbutil.AsQueryError(err); qerr != nil { query, params := qerr.Query() - log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) + log = log.With("sql", query, "sql_params", params) } - log.WithError(err).Error("error writing channel log") + log.Error("error writing channel log", "error", err) } } } @@ -174,6 +174,6 @@ func writeStorageChannelLogs(ctx context.Context, st storage.Storage, batch []*s } } if err := st.BatchPut(ctx, uploads); err != nil { - logrus.WithField("comp", "storage log writer").Error("error writing channel logs") + slog.Error("error writing channel logs", "comp", "storage log writer") } } diff --git a/backends/rapidpro/contact.go b/backends/rapidpro/contact.go index 80f5cc208..1d7f6d162 100644 --- a/backends/rapidpro/contact.go +++ b/backends/rapidpro/contact.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "database/sql/driver" + "log/slog" "strconv" "time" "unicode/utf8" @@ -16,7 +17,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // used by unit tests to slow down urn operations to test races @@ -107,7 +107,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, contact := &Contact{} err := b.db.GetContext(ctx, contact, lookupContactFromURNSQL, urn.Identity(), org) if err != nil && err != sql.ErrNoRows { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") + slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) return nil, errors.Wrap(err, "error looking up contact by URN") } @@ -116,13 +116,13 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, // insert it tx, err := b.db.BeginTxx(ctx, nil) if err != nil { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") + slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) return nil, errors.Wrap(err, "error beginning transaction") } err = setDefaultURN(tx, channel, contact, urn, authTokens) if err != nil { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("org_id", org).Error("error looking up contact") + slog.Error("error looking up contact", "error", err, "urn", urn.Identity(), "org_id", org) tx.Rollback() return nil, errors.Wrap(err, "error setting default URN for contact") } @@ -148,7 +148,7 @@ func contactForURN(ctx context.Context, b *backend, org OrgID, channel *Channel, // in the case of errors, we log the error but move onwards anyways if err != nil { - logrus.WithField("channel_uuid", channel.UUID()).WithField("channel_type", channel.ChannelType()).WithField("urn", urn).WithError(err).Error("unable to describe URN") + slog.Error("unable to describe URN", "error", err, "channel_uuid", channel.UUID(), "channel_type", channel.ChannelType(), "urn", urn) } else { name = attrs["name"] } diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 11cb6aa87..cd56e70d9 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "log" + "log/slog" "os" "strings" "time" @@ -22,7 +23,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" "github.com/pkg/errors" - "github.com/sirupsen/logrus" filetype "gopkg.in/h2non/filetype.v1" ) @@ -228,7 +228,7 @@ func writeMsg(ctx context.Context, b *backend, msg courier.MsgIn, clog *courier. // fail? log if err != nil { - logrus.WithError(err).WithField("msg", m.UUID()).Error("error writing to db") + slog.Error("error writing to db", "error", err, "msg", m.UUID()) } // if we failed write to spool @@ -282,7 +282,7 @@ func writeMsgToDB(ctx context.Context, b *backend, m *Msg, clog *courier.Channel // if we had a problem queueing the handling, log it, but our message is written, it'll // get picked up by our rapidpro catch-all after a period if err != nil { - logrus.WithError(err).WithField("msg_id", m.ID_).Error("error queueing msg handling") + slog.Error("error queueing msg handling", "error", err, "msg_id", m.ID_) } return nil diff --git a/backends/rapidpro/status.go b/backends/rapidpro/status.go index 16243e6e6..5793049b8 100644 --- a/backends/rapidpro/status.go +++ b/backends/rapidpro/status.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "log/slog" "os" "strconv" "sync" @@ -14,7 +15,6 @@ import ( "github.com/nyaruka/gocommon/syncx" "github.com/nyaruka/gocommon/urns" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // StatusUpdate represents a status update on a message @@ -121,7 +121,7 @@ func (b *backend) flushStatusFile(filename string, contents []byte) error { status := &StatusUpdate{} err := json.Unmarshal(contents, status) if err != nil { - logrus.Printf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err) + slog.Info(fmt.Sprintf("ERROR unmarshalling spool file '%s', renaming: %s\n", filename, err)) os.Rename(filename, fmt.Sprintf("%s.error", filename)) return nil } @@ -182,7 +182,7 @@ func NewStatusWriter(b *backend, spoolDir string, wg *sync.WaitGroup) *StatusWri // tries to write a batch of message statuses to the database and spools those that fail func (b *backend) writeStatuseUpdates(ctx context.Context, spoolDir string, batch []*StatusUpdate) { - log := logrus.WithField("comp", "status writer") + log := slog.With("comp", "status writer") unresolved, err := b.writeStatusUpdatesToDB(ctx, batch) @@ -191,24 +191,24 @@ func (b *backend) writeStatuseUpdates(ctx context.Context, spoolDir string, batc for _, s := range batch { _, err = b.writeStatusUpdatesToDB(ctx, []*StatusUpdate{s}) if err != nil { - log := log.WithField("msg_id", s.MsgID()) + log := log.With("msg_id", s.MsgID()) if qerr := dbutil.AsQueryError(err); qerr != nil { query, params := qerr.Query() - log = log.WithFields(logrus.Fields{"sql": query, "sql_params": params}) + log = log.With("sql", query, "sql_params", params) } - log.WithError(err).Error("error writing msg status") + log.Error("error writing msg status", "error", err) err := courier.WriteToSpool(spoolDir, "statuses", s) if err != nil { - log.WithError(err).Error("error writing status to spool") // just have to log and move on + log.Error("error writing status to spool", "error", err) // just have to log and move on } } } } else { for _, s := range unresolved { - log.Warnf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_) + log.Warn(fmt.Sprintf("unable to find message with channel_id=%d and external_id=%s", s.ChannelID_, s.ExternalID_)) } } } @@ -268,7 +268,7 @@ func (b *backend) resolveStatusUpdateMsgIDs(ctx context.Context, statuses []*Sta cachedIDs, err := b.sentExternalIDs.MGet(rc, chAndExtKeys...) if err != nil { // log error but we continue and try to get ids from the database - logrus.WithError(err).Error("error looking up sent message ids in redis") + slog.Error("error looking up sent message ids in redis", "error", err) } // collect the statuses that couldn't be resolved from cache, update the ones that could diff --git a/backends/rapidpro/urn.go b/backends/rapidpro/urn.go index 46d814d83..07717754d 100644 --- a/backends/rapidpro/urn.go +++ b/backends/rapidpro/urn.go @@ -4,6 +4,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "log/slog" "github.com/jmoiron/sqlx" "github.com/nyaruka/courier" @@ -11,7 +12,6 @@ import ( "github.com/nyaruka/gocommon/urns" "github.com/nyaruka/null/v3" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) // ContactURNID represents a contact urn's id @@ -98,7 +98,7 @@ func setDefaultURN(db *sqlx.Tx, channel *Channel, contact *Contact, urn urns.URN scheme := urn.Scheme() contactURNs, err := getURNsForContact(db, contact.ID_) if err != nil { - logrus.WithError(err).WithField("urn", urn.Identity()).WithField("channel_id", channel.ID()).Error("error looking up contact urns") + slog.Error("error looking up contact urns", "error", err, "urn", urn.Identity(), "channel_id", channel.ID()) return err } @@ -248,7 +248,7 @@ UPDATE contacts_contacturn func updateContactURN(db *sqlx.Tx, urn *ContactURN) error { rows, err := db.NamedQuery(sqlUpdateURN, urn) if err != nil { - logrus.WithError(err).WithField("urn_id", urn.ID).Error("error updating contact urn") + slog.Error("error updating contact urn", "error", err, "urn_id", urn.ID) return err } defer rows.Close() @@ -263,7 +263,7 @@ func updateContactURN(db *sqlx.Tx, urn *ContactURN) error { func fullyUpdateContactURN(db *sqlx.Tx, urn *ContactURN) error { rows, err := db.NamedQuery(sqlFullyUpdateURN, urn) if err != nil { - logrus.WithError(err).WithField("urn_id", urn.ID).Error("error updating contact urn") + slog.Error("error updating contact urn", "error", err, "urn_id", urn.ID) return err } defer rows.Close() From 56a4ad4f21800e96ec25d926ab432531c9998e05 Mon Sep 17 00:00:00 2001 From: Norbert Kwizera Date: Thu, 12 Oct 2023 11:36:05 +0200 Subject: [PATCH 2/2] Discard logs for testing --- backends/rapidpro/backend_test.go | 3 +-- handlers/jiochat/handler_test.go | 4 ++-- handlers/test.go | 4 ++-- handlers/wechat/handler_test.go | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index 2b7231497..97ee68d50 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -26,7 +26,6 @@ import ( "github.com/nyaruka/gocommon/uuids" "github.com/nyaruka/null/v3" "github.com/nyaruka/redisx/assertredis" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" ) @@ -47,7 +46,7 @@ func (ts *BackendTestSuite) SetupSuite() { storageDir = "_test_storage" // turn off logging - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) b, err := courier.NewBackend(testConfig()) if err != nil { diff --git a/handlers/jiochat/handler_test.go b/handlers/jiochat/handler_test.go index 865d902e1..77af22f35 100644 --- a/handlers/jiochat/handler_test.go +++ b/handlers/jiochat/handler_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "io" + "log" "log/slog" "net/http" "net/http/httptest" @@ -19,7 +20,6 @@ import ( "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -262,7 +262,7 @@ func buildMockJCAPI(testCases []IncomingTestCase) *httptest.Server { func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null logger := slog.Default() - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) config := courier.NewConfig() config.DB = "postgres://courier_test:temba@localhost:5432/courier_test?sslmode=disable" config.Redis = "redis://localhost:6379/0" diff --git a/handlers/test.go b/handlers/test.go index ec8693b88..ff46c2a9c 100644 --- a/handlers/test.go +++ b/handlers/test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "log" "log/slog" "mime/multipart" "net/http" @@ -21,7 +22,6 @@ import ( "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/i18n" "github.com/nyaruka/gocommon/urns" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -149,7 +149,7 @@ func testHandlerRequest(tb testing.TB, s courier.Server, path string, headers ma func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null logger := slog.Default() - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) config := courier.NewConfig() config.FacebookWebhookSecret = "fb_webhook_secret" diff --git a/handlers/wechat/handler_test.go b/handlers/wechat/handler_test.go index b784ec8fc..843e238e4 100644 --- a/handlers/wechat/handler_test.go +++ b/handlers/wechat/handler_test.go @@ -5,6 +5,7 @@ import ( "crypto/sha1" "encoding/hex" "io" + "log" "log/slog" "net/http" "net/http/httptest" @@ -19,7 +20,6 @@ import ( "github.com/nyaruka/courier/test" "github.com/nyaruka/gocommon/httpx" "github.com/nyaruka/gocommon/urns" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -214,7 +214,7 @@ func buildMockWCAPI(testCases []IncomingTestCase) *httptest.Server { func newServer(backend courier.Backend) courier.Server { // for benchmarks, log to null logger := slog.Default() - logrus.SetOutput(io.Discard) + log.SetOutput(io.Discard) config := courier.NewConfig() config.DB = "postgres://courier_test:temba@localhost:5432/courier_test?sslmode=disable" config.Redis = "redis://localhost:6379/0"