Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add slog support to goose provider #836

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"database/sql"
"errors"
"fmt"
"io"
"io/fs"
"log/slog"
"math"
"os"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -62,13 +65,32 @@ func NewProvider(dialect Dialect, db *sql.DB, fsys fs.FS, opts ...ProviderOption
registered: make(map[int64]*Migration),
excludePaths: make(map[string]bool),
excludeVersions: make(map[int64]bool),
logger: &stdLogger{},
}
for _, opt := range opts {
if err := opt.apply(&cfg); err != nil {
return nil, err
}
}
if cfg.logger == nil {
output := io.Discard
if cfg.verbose {
output = os.Stdout
}
replaceAttr := func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{
Key: slog.TimeKey,
Value: slog.StringValue(a.Value.Time().Format("2006-01-02 15:04:05")),
}
}
return a
}
handler := slog.NewTextHandler(output, &slog.HandlerOptions{
Level: slog.LevelInfo,
ReplaceAttr: replaceAttr,
})
cfg.logger = slog.New(handler).With(slog.String("logger", "goose"))
}
// Allow users to specify a custom store implementation, but only if they don't specify a
// dialect. If they specify a dialect, we'll use the default store implementation.
if dialect == "" && cfg.store == nil {
Expand Down Expand Up @@ -118,8 +140,9 @@ func newProvider(
}
// Skip adding global Go migrations if explicitly disabled.
if cfg.disableGlobalRegistry {
// TODO(mf): let's add a warn-level log here to inform users if len(global) > 0. Would like
// to add this once we're on go1.21 and leverage the new slog package.
if len(global) > 0 {
cfg.logger.Warn("detected global go migrations, but global go migrations are disabled", slog.Int("count", len(global)))
}
} else {
for version, m := range global {
if _, ok := versionToGoMigration[version]; ok {
Expand Down Expand Up @@ -434,7 +457,7 @@ func (p *Provider) down(
}
// We never migrate the zero version down.
if dbMigrations[0].Version == 0 {
p.printf("no migrations to run, current version: 0")
p.cfg.logger.Info("no migrations to run, current version: 0")
return nil, nil
}
var apply []*Migration
Expand Down
16 changes: 11 additions & 5 deletions provider_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package goose
import (
"errors"
"fmt"
"log/slog"

"github.com/pressly/goose/v3/database"
"github.com/pressly/goose/v3/lock"
Expand Down Expand Up @@ -165,10 +166,20 @@ func WithDisableVersioning(b bool) ProviderOption {
})
}

// WithLogger sets the logger to use for logging. By default, goose will use a modified version of
// the [slog.NewTextHandler] with a custom format.
func WithLogger(logger *slog.Logger) ProviderOption {
return configFunc(func(c *config) error {
c.logger = logger
return nil
})
}

type config struct {
store database.Store

verbose bool
logger *slog.Logger
excludePaths map[string]bool
excludeVersions map[int64]bool

Expand All @@ -184,11 +195,6 @@ type config struct {
disableVersioning bool
allowMissing bool
disableGlobalRegistry bool

// Let's not expose the Logger just yet. Ideally we consolidate on the std lib slog package
// added in go1.21 and then expose that (if that's even necessary). For now, just use the std
// lib log package.
logger Logger
}

type configFunc func(*config) error
Expand Down
25 changes: 10 additions & 15 deletions provider_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"errors"
"fmt"
"io/fs"
"log/slog"
"path/filepath"
"runtime/debug"
"strings"
"time"

"github.com/pressly/goose/v3/database"
Expand Down Expand Up @@ -66,17 +67,6 @@ func (p *Provider) prepareMigration(fsys fs.FS, m *Migration, direction bool) er
return fmt.Errorf("invalid migration type: %+v", m)
}

// printf is a helper function that prints the given message if verbose is enabled. It also prepends
// the "goose: " prefix to the message.
func (p *Provider) printf(msg string, args ...interface{}) {
if p.cfg.verbose {
if !strings.HasPrefix(msg, "goose:") {
msg = "goose: " + msg
}
p.cfg.logger.Printf(msg, args...)
}
}

// runMigrations runs migrations sequentially in the given direction. If the migrations list is
// empty, return nil without error.
func (p *Provider) runMigrations(
Expand All @@ -94,7 +84,7 @@ func (p *Provider) runMigrations(
if err != nil {
return nil, err
}
p.printf("no migrations to run, current version: %d", maxVersion)
p.cfg.logger.Info("no migrations to run", slog.Int64("current_version", maxVersion))
}
return nil, nil
}
Expand Down Expand Up @@ -150,14 +140,19 @@ func (p *Provider) runMigrations(
}
result.Duration = time.Since(start)
results = append(results, result)
p.printf("%s", result)
p.cfg.logger.Info("applied migration",
slog.String("source", filepath.Base(m.Source)),
slog.Any("direction", direction),
slog.Any("duration", result.Duration),
slog.Bool("empty", result.Empty),
)
}
if !p.cfg.disableVersioning && !byOne {
maxVersion, err := p.getDBMaxVersion(ctx, conn)
if err != nil {
return nil, err
}
p.printf("successfully migrated database, current version: %d", maxVersion)
p.cfg.logger.Info("successfully migrated database", slog.Int64("current_version", maxVersion))
}
return results, nil
}
Expand Down