From f4d0a789736687bc1a3d9d7dd467b6c05333cfd2 Mon Sep 17 00:00:00 2001 From: James Ding Date: Sun, 28 Jun 2026 18:45:59 -0700 Subject: [PATCH 1/2] refactor: replace logrus with log/slog and tint Swap the deprecated logrus logger for the standard-library log/slog with the lmittmann/tint handler for colored, human-readable output. - Logs now write to stderr (was stdout), per CLI convention - Color auto-disables when output is not a TTY - logrus Trace/Debug both map to slog.Debug; --debug raises the threshold - log.Fatal becomes slog.Error + os.Exit(1) --- cycler.go | 22 +++++++++++----------- go.mod | 7 ++++--- go.sum | 4 ++-- main.go | 45 +++++++++++++++++++++++++++------------------ rpc.go | 18 +++++++++--------- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/cycler.go b/cycler.go index a67b1aa..6a0b9af 100644 --- a/cycler.go +++ b/cycler.go @@ -2,9 +2,9 @@ package main import ( "fmt" + "log/slog" "time" - log "github.com/sirupsen/logrus" lfm "github.com/twangodev/lfm-api" "github.com/xeyossr/go-discordrpc/client" ) @@ -24,7 +24,7 @@ func cycle() { LargeImage: "lfm_logo", }) if err != nil { - log.Warnln("Failed to keep activity. Dropping connection to reconnect next cycle.") + slog.Warn("Failed to keep activity. Dropping connection to reconnect next cycle.") logout() return } @@ -33,15 +33,15 @@ func cycle() { // Login logout logic if s.Active { // Login if scrobble detected and if currently logged out if !loggedIn { - log.Info("New scrobble detected. Logging in.") + slog.Info("New scrobble detected. Logging in.") login() } } else { // No new scrobble if loggedIn { // Logout if logged in - log.Info("No scrobble detected. Logging out.") + slog.Info("No scrobble detected. Logging out.") logout() } else { // Retain logout state - log.Traceln("No new scrobble detected.") + slog.Debug("No new scrobble detected.") } return } @@ -49,7 +49,7 @@ func cycle() { if ts != s.DataTimestamp { // Update old timestamp to match current scrobble ts = s.DataTimestamp - log.WithFields(log.Fields{"scrobbling": s}).Infoln("Updating presence.") + slog.Info("Updating presence.", "scrobbling", s) } else { // Prevents update of the same scrobble, use timestamp to differentiate return } @@ -57,22 +57,22 @@ func cycle() { // First RPC attempt is without songLink err1 := rpcClient.SetActivity(createActivity(s, false)) if err1 != nil { - log.Info("Failed to set base RPC. Retrying with detailed payload.") + slog.Info("Failed to set base RPC. Retrying with detailed payload.") } else { - log.Traceln("Successfully set base RPC.") + slog.Debug("Successfully set base RPC.") } // Second RPC attempt is with songLink err2 := rpcClient.SetActivity(createActivity(s, true)) if err2 != nil { if err1 != nil { - log.Warnln("Both attempts to set RPC failed. Reconnecting next cycle.") + slog.Warn("Both attempts to set RPC failed. Reconnecting next cycle.") logout() ts = time.Time{} } else { - log.Info("Failed to set detailed RPC.") + slog.Info("Failed to set detailed RPC.") } } else { - log.Traceln("Successfully set detailed RPC.") + slog.Debug("Successfully set detailed RPC.") } } diff --git a/go.mod b/go.mod index d03da58..9783774 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,23 @@ module lfm-cli go 1.25.0 require ( + github.com/lmittmann/tint v1.1.3 github.com/mattn/go-colorable v0.1.14 - github.com/sirupsen/logrus v1.9.4 + github.com/mattn/go-isatty v0.0.20 github.com/twangodev/lfm-api v1.1.0 github.com/urfave/cli/v2 v2.27.7 github.com/xeyossr/go-discordrpc v1.1.0 - golang.org/x/net v0.55.0 ) require ( github.com/bozd4g/go-http-client v1.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/sys v0.45.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) diff --git a/go.sum b/go.sum index 1b8a882..63f2265 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3 github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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= +github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I= +github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -14,8 +16,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twangodev/lfm-api v1.1.0 h1:xxg55iZfcYs+1rAHeiCrpSJay0G3ugrp+RFcqO97jc8= diff --git a/main.go b/main.go index b3316b3..404364e 100644 --- a/main.go +++ b/main.go @@ -2,11 +2,13 @@ package main import ( "fmt" + "log/slog" "os" "time" + "github.com/lmittmann/tint" "github.com/mattn/go-colorable" - log "github.com/sirupsen/logrus" + "github.com/mattn/go-isatty" lfm "github.com/twangodev/lfm-api" "github.com/urfave/cli/v2" ) @@ -28,6 +30,9 @@ var debug bool var profileUrl string +// logLevel lets main() build the handler before flags are parsed; exec() raises it for --debug. +var logLevel = new(slog.LevelVar) + func exec(ctx *cli.Context) error { showProfile = !ctx.Bool("hide-profile") @@ -37,28 +42,28 @@ func exec(ctx *cli.Context) error { keepStatus = ctx.Bool("keep-status") debug = ctx.Bool("debug") if debug { - log.SetLevel(log.TraceLevel) + logLevel.Set(slog.LevelDebug) } else { - log.SetLevel(log.InfoLevel) + logLevel.Set(slog.LevelInfo) } profileUrl = fmt.Sprintf("%vuser/%v", lfm.LastFmUrl, username) - log.WithFields(log.Fields{ - "username": username, - "refresh_interval": refreshInterval, - "show_profile": showProfile, - "show_loved": showLoved, - "show_covers": covers, - "show_elapsed": elapsed, - "keep_status": keepStatus, - "debug_enabled": debug, - }).Infoln("Configuration loaded from arguments") + slog.Info("Configuration loaded from arguments", + "username", username, + "refresh_interval", refreshInterval, + "show_profile", showProfile, + "show_loved", showLoved, + "show_covers", covers, + "show_elapsed", elapsed, + "keep_status", keepStatus, + "debug_enabled", debug, + ) for { - log.Traceln("Cycle begin.") + slog.Debug("Cycle begin.") cycle() - log.Traceln("Cycle complete.") + slog.Debug("Cycle complete.") time.Sleep(time.Duration(refreshInterval) * time.Second) } @@ -66,8 +71,11 @@ func exec(ctx *cli.Context) error { func main() { - log.SetFormatter(&log.TextFormatter{ForceColors: true}) - log.SetOutput(colorable.NewColorableStdout()) + slog.SetDefault(slog.New(tint.NewHandler(colorable.NewColorableStderr(), &tint.Options{ + Level: logLevel, + TimeFormat: "15:04:05.000", + NoColor: !isatty.IsTerminal(os.Stderr.Fd()), + }))) app := &cli.App{ Name: name, @@ -130,7 +138,8 @@ func main() { err := app.Run(os.Args) if err != nil { - log.Fatal(err) + slog.Error("fatal error", tint.Err(err)) + os.Exit(1) } } diff --git a/rpc.go b/rpc.go index 7bd1b26..8e77b69 100644 --- a/rpc.go +++ b/rpc.go @@ -1,7 +1,9 @@ package main import ( - log "github.com/sirupsen/logrus" + "log/slog" + + "github.com/lmittmann/tint" "github.com/xeyossr/go-discordrpc/client" ) @@ -10,10 +12,8 @@ var loggedIn = false // Logout closes the socket; the next Login redials to recover a dropped connection var rpcClient = client.NewClient(discordAppId) -func getRPCLogCtx() *log.Entry { - return log.WithFields(log.Fields{ - "loggedIn": loggedIn, - }) +func getRPCLogCtx() *slog.Logger { + return slog.With("loggedIn", loggedIn) } func login() { @@ -21,18 +21,18 @@ func login() { return } if err := rpcClient.Login(); err != nil { - getRPCLogCtx().Warnln("Could not login to Discord.") + getRPCLogCtx().Warn("Could not login to Discord.") logout() return } loggedIn = true - getRPCLogCtx().Debugln("Successfully logged into Discord's RPC Server.") + getRPCLogCtx().Debug("Successfully logged into Discord's RPC Server.") } func logout() { if err := rpcClient.Logout(); err != nil { - getRPCLogCtx().WithError(err).Debugln("Error closing Discord RPC connection.") + getRPCLogCtx().Debug("Error closing Discord RPC connection.", tint.Err(err)) } loggedIn = false - getRPCLogCtx().Debugln("Successfully logged out of Discord's RPC Server.") + getRPCLogCtx().Debug("Successfully logged out of Discord's RPC Server.") } From fc6c6e75abafdb4efabee1ff76ac8df284484429 Mon Sep 17 00:00:00 2001 From: James Ding Date: Sun, 28 Jun 2026 19:34:42 -0700 Subject: [PATCH 2/2] refactor: include RPC and login error details in warning logs Attach the dropped error values (err/err1/err2) to the keep-activity, RPC retry, reconnect, and login-failure log records so the reason Discord rejected the payload or disconnected is preserved. --- cycler.go | 9 +++++---- rpc.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cycler.go b/cycler.go index 6a0b9af..c570e5f 100644 --- a/cycler.go +++ b/cycler.go @@ -5,6 +5,7 @@ import ( "log/slog" "time" + "github.com/lmittmann/tint" lfm "github.com/twangodev/lfm-api" "github.com/xeyossr/go-discordrpc/client" ) @@ -24,7 +25,7 @@ func cycle() { LargeImage: "lfm_logo", }) if err != nil { - slog.Warn("Failed to keep activity. Dropping connection to reconnect next cycle.") + slog.Warn("Failed to keep activity. Dropping connection to reconnect next cycle.", tint.Err(err)) logout() return } @@ -57,7 +58,7 @@ func cycle() { // First RPC attempt is without songLink err1 := rpcClient.SetActivity(createActivity(s, false)) if err1 != nil { - slog.Info("Failed to set base RPC. Retrying with detailed payload.") + slog.Info("Failed to set base RPC. Retrying with detailed payload.", tint.Err(err1)) } else { slog.Debug("Successfully set base RPC.") } @@ -66,11 +67,11 @@ func cycle() { err2 := rpcClient.SetActivity(createActivity(s, true)) if err2 != nil { if err1 != nil { - slog.Warn("Both attempts to set RPC failed. Reconnecting next cycle.") + slog.Warn("Both attempts to set RPC failed. Reconnecting next cycle.", "base_err", err1, "detailed_err", err2) logout() ts = time.Time{} } else { - slog.Info("Failed to set detailed RPC.") + slog.Info("Failed to set detailed RPC.", tint.Err(err2)) } } else { slog.Debug("Successfully set detailed RPC.") diff --git a/rpc.go b/rpc.go index 8e77b69..fdb1145 100644 --- a/rpc.go +++ b/rpc.go @@ -21,7 +21,7 @@ func login() { return } if err := rpcClient.Login(); err != nil { - getRPCLogCtx().Warn("Could not login to Discord.") + getRPCLogCtx().Warn("Could not login to Discord.", tint.Err(err)) logout() return }