mirror of
https://github.com/benbjohnson/litestream.git
synced 2026-01-25 05:06:30 +00:00
feat(replicate): add --log-level CLI flag for runtime debugging (#986)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ func NewReplicateCommand() *ReplicateCommand {
|
||||
func (c *ReplicateCommand) ParseFlags(_ context.Context, args []string) (err error) {
|
||||
fs := flag.NewFlagSet("litestream-replicate", flag.ContinueOnError)
|
||||
execFlag := fs.String("exec", "", "execute subcommand")
|
||||
logLevelFlag := fs.String("log-level", "", "log level (trace, debug, info, warn, error)")
|
||||
restoreIfDBNotExists := fs.Bool("restore-if-db-not-exists", false, "restore from replica if database doesn't exist")
|
||||
onceFlag := fs.Bool("once", false, "replicate once and exit")
|
||||
forceSnapshotFlag := fs.Bool("force-snapshot", false, "force snapshot when replicating once")
|
||||
@@ -78,6 +79,17 @@ func (c *ReplicateCommand) ParseFlags(_ context.Context, args []string) (err err
|
||||
if c.Config, err = ReadConfigFile(*configPath, !*noExpandEnv); err != nil {
|
||||
return err
|
||||
}
|
||||
// Override log level if CLI flag provided (takes precedence over env var)
|
||||
if *logLevelFlag != "" {
|
||||
c.Config.Logging.Level = *logLevelFlag
|
||||
// Set env var so initLog sees CLI flag as highest priority
|
||||
os.Setenv("LOG_LEVEL", *logLevelFlag)
|
||||
logOutput := os.Stdout
|
||||
if c.Config.Logging.Stderr {
|
||||
logOutput = os.Stderr
|
||||
}
|
||||
initLog(logOutput, c.Config.Logging.Level, c.Config.Logging.Type)
|
||||
}
|
||||
|
||||
case 1:
|
||||
// Only database path provided, missing replica URL
|
||||
@@ -91,7 +103,14 @@ func (c *ReplicateCommand) ParseFlags(_ context.Context, args []string) (err err
|
||||
|
||||
// Initialize config with defaults when using command-line arguments
|
||||
c.Config = DefaultConfig()
|
||||
initLog(os.Stdout, "INFO", "text")
|
||||
logLevel := "INFO"
|
||||
if *logLevelFlag != "" {
|
||||
logLevel = *logLevelFlag
|
||||
// Set env var so initLog sees CLI flag as highest priority
|
||||
os.Setenv("LOG_LEVEL", *logLevelFlag)
|
||||
}
|
||||
c.Config.Logging.Level = logLevel
|
||||
initLog(os.Stdout, logLevel, "text")
|
||||
|
||||
dbConfig := &DBConfig{
|
||||
Path: fs.Arg(0),
|
||||
@@ -482,6 +501,10 @@ Arguments:
|
||||
This removes snapshots that are older than the configured
|
||||
snapshot retention period.
|
||||
|
||||
-log-level LEVEL
|
||||
Sets the log level. Overrides the config file setting.
|
||||
Valid values: trace, debug, info, warn, error
|
||||
|
||||
-no-expand-env
|
||||
Disables environment variable expansion in configuration file.
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package main_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -172,3 +173,106 @@ func TestReplicateCommand_ParseFlags_FlagPositioning(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReplicateCommand_ParseFlags_LogLevel(t *testing.T) {
|
||||
t.Run("LogLevelWithCLIArgs", func(t *testing.T) {
|
||||
cmd := main.NewReplicateCommand()
|
||||
args := []string{"-log-level", "debug", "test.db", "file:///tmp/replica"}
|
||||
err := cmd.ParseFlags(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cmd.Config.Logging.Level != "debug" {
|
||||
t.Errorf("expected log level to be %q, got %q", "debug", cmd.Config.Logging.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("LogLevelTrace", func(t *testing.T) {
|
||||
cmd := main.NewReplicateCommand()
|
||||
args := []string{"-log-level", "trace", "test.db", "file:///tmp/replica"}
|
||||
err := cmd.ParseFlags(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cmd.Config.Logging.Level != "trace" {
|
||||
t.Errorf("expected log level to be %q, got %q", "trace", cmd.Config.Logging.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("LogLevelError", func(t *testing.T) {
|
||||
cmd := main.NewReplicateCommand()
|
||||
args := []string{"-log-level", "error", "test.db", "file:///tmp/replica"}
|
||||
err := cmd.ParseFlags(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cmd.Config.Logging.Level != "error" {
|
||||
t.Errorf("expected log level to be %q, got %q", "error", cmd.Config.Logging.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("LogLevelDefaultsToInfo", func(t *testing.T) {
|
||||
cmd := main.NewReplicateCommand()
|
||||
args := []string{"test.db", "file:///tmp/replica"}
|
||||
err := cmd.ParseFlags(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cmd.Config.Logging.Level != "INFO" {
|
||||
t.Errorf("expected log level to default to %q, got %q", "INFO", cmd.Config.Logging.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("LogLevelWithOtherFlags", func(t *testing.T) {
|
||||
cmd := main.NewReplicateCommand()
|
||||
args := []string{"-log-level", "warn", "-once", "test.db", "file:///tmp/replica"}
|
||||
err := cmd.ParseFlags(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if cmd.Config.Logging.Level != "warn" {
|
||||
t.Errorf("expected log level to be %q, got %q", "warn", cmd.Config.Logging.Level)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("LogLevelAfterPositionalArgs", func(t *testing.T) {
|
||||
cmd := main.NewReplicateCommand()
|
||||
args := []string{"test.db", "file:///tmp/replica", "-log-level", "debug"}
|
||||
err := cmd.ParseFlags(context.Background(), args)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when -log-level flag is positioned after positional arguments")
|
||||
}
|
||||
expectedError := `flag "-log-level" must be positioned before DB_PATH and REPLICA_URL arguments`
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("expected error message to contain %q, got %q", expectedError, err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("LogLevelFlagOverridesEnvVar", func(t *testing.T) {
|
||||
// Set LOG_LEVEL env var to a different value
|
||||
oldEnv := os.Getenv("LOG_LEVEL")
|
||||
os.Setenv("LOG_LEVEL", "error")
|
||||
defer func() {
|
||||
if oldEnv == "" {
|
||||
os.Unsetenv("LOG_LEVEL")
|
||||
} else {
|
||||
os.Setenv("LOG_LEVEL", oldEnv)
|
||||
}
|
||||
}()
|
||||
|
||||
cmd := main.NewReplicateCommand()
|
||||
args := []string{"-log-level", "debug", "test.db", "file:///tmp/replica"}
|
||||
err := cmd.ParseFlags(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// CLI flag should take precedence, setting LOG_LEVEL env var to "debug"
|
||||
if got := os.Getenv("LOG_LEVEL"); got != "debug" {
|
||||
t.Errorf("expected LOG_LEVEL env var to be %q (CLI flag), got %q", "debug", got)
|
||||
}
|
||||
if cmd.Config.Logging.Level != "debug" {
|
||||
t.Errorf("expected config log level to be %q, got %q", "debug", cmd.Config.Logging.Level)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user