Skip to content

Add slog support#989

Merged
mfridman merged 4 commits intomainfrom
mf/logger
Sep 18, 2025
Merged

Add slog support#989
mfridman merged 4 commits intomainfrom
mf/logger

Conversation

@mfridman
Copy link
Copy Markdown
Collaborator

@mfridman mfridman commented Sep 18, 2025

This PR adds a WithSlog provider option.

A bit more maintenance on maintainers to compose both sets of messages. But I think this should be okay, we don't log a lot via the provider.


Full example:

package main

import (
	"context"
	"database/sql"
	"log"
	"log/slog"
	"os"

	"github.com/pressly/goose/v3"
	_ "modernc.org/sqlite"
)

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}

func run() error {
	db, err := sql.Open("sqlite", "test.db")
	if err != nil {
		return err
	}
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
			if a.Key == "statement" {
				return slog.Attr{
					Key:   a.Key,
					Value: slog.StringValue("<omitted>"),
				}
			}
			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
		},
	}))
	// remove "statement" from the log output for brevity
	p, err := goose.NewProvider(goose.DialectSQLite3, db, os.DirFS("testdata/migrations"),
		goose.WithSlog(logger),
		goose.WithVerbose(true),
	)
	if err != nil {
		return err
	}
	args := os.Args[1:]
	if len(args) > 0 {
		switch args[0] {
		case "up":
			_, err = p.Up(context.Background())
		case "down":
			_, err = p.Down(context.Background())
		}
	}
	return err
}

go run ./cmd/debug/ up

{
    "time": "2025-09-17 23:05:23",
    "level": "INFO",
    "msg": "executing statement",
    "logger": "goose",
    "source": "00001_users_table.sql",
    "statement": "<omitted>",
    "type": "sql",
    "version": 1
}
{
    "time": "2025-09-17 23:05:23",
    "level": "INFO",
    "msg": "migration completed",
    "logger": "goose",
    "direction": "up",
    "duration_seconds": 0.000857834,
    "source": "00001_users_table.sql",
    "state": "applied",
    "type": "sql",
    "version": 1
}

Copy link
Copy Markdown
Collaborator

@VojtechVitek VojtechVitek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thank you! 🎉

Link back to the original discussion: #768

@mfridman mfridman merged commit 975cfb4 into main Sep 18, 2025
4 checks passed
@mfridman mfridman deleted the mf/logger branch September 18, 2025 12:23
@mfridman
Copy link
Copy Markdown
Collaborator Author

Alright @VojtechVitek, merged.

Was going to cut a release this weekend (but if you need it sooner let me know).

Surprisingly, I have some time, so I would like to squeeze a few more things in the next release.

Oh, and I suppose we need to be careful about changing attribute keys since they may be used by end-users in handler options.

@VojtechVitek
Copy link
Copy Markdown
Collaborator

@mfridman no rush. Up to you :)

Perhaps we can export const for each of these keys?

slog.String("source", filepath.Base(result.Source.Path)),
			slog.String("direction", result.Direction),
			slog.Float64("duration_seconds", result.Duration.Seconds()),
			slog.String("state", state),
			slog.Int64("version", result.Source.Version),
			slog.String("type", string(result.Source.Type)),

@mfridman
Copy link
Copy Markdown
Collaborator Author

mfridman commented Sep 18, 2025

Perhaps we can export const for each of these keys?

Yeah, had a similar thought. I suspect these become part of the public API surface indirectly, so they could be exposed.

Maybe we add package logkey or just do it directly in the top-level goose package. Or do nothing. No strong opinion

E.g.,

// Log key constants for structured logging. These keys are used in slog output and can be used by
// consumers to parse or filter log entries.
const (
	LogKeyLogger          = "logger"
	LogKeyCurrentVersion  = "current_version"
	LogKeySource          = "source"
	LogKeyDirection       = "direction"
	LogKeyDurationSeconds = "duration_seconds"
	LogKeyState           = "state"
	LogKeyVersion         = "version"
	LogKeyType            = "type"
	LogKeyStatement       = "statement"
)

@VojtechVitek
Copy link
Copy Markdown
Collaborator

Looks good. The top-level pkg sounds fine too 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants