diff --git a/cli/cli.go b/cli/cli.go index 50eaad20..ce97b96b 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -18,41 +18,6 @@ // integrated client. package cli -import ( - "fmt" - "os" -) - -var cmdMap map[string]Command = make(map[string]Command) - -// AddCommand adds a command using the specific symbol. -func AddCommand(symbol string, cmd Command) { - cmdMap[symbol] = cmd -} - // Session holds all necessary information about the current CLI session. type Session struct { } - -// Run runs our CLI command, based on the args. We keep it very simple for now -// without any extra package, so we just take the first arg and see if if -// matches any of our commands -func Run(args []string) { - var ( - cmd Command - ok bool - s *Session - ) - - // Create a new session. TODO(oxisto): We do not yet have auth, but in the - // future we need to fetch a token here - s = new(Session) - - // Try to look up command in our command map - cmd, ok = cmdMap[args[1]] - if ok { - cmd.Exec(s, os.Args[1:]...) - } else { - fmt.Print("Command not found.\n") - } -} diff --git a/cmd/moneyd/moneyd.go b/cmd/moneyd/moneyd.go index 9705063e..75cfee76 100644 --- a/cmd/moneyd/moneyd.go +++ b/cmd/moneyd/moneyd.go @@ -18,28 +18,59 @@ package main import ( "log" + "log/slog" "net/http" + "os" "strings" + "time" - "github.com/mattn/go-colorable" + "github.com/alecthomas/kong" "github.com/oxisto/money-gopher/gen/portfoliov1connect" "github.com/oxisto/money-gopher/persistence" "github.com/oxisto/money-gopher/service/portfolio" "github.com/oxisto/money-gopher/service/securities" + "github.com/lmittmann/tint" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) +var cmd moneydCmd + +type moneydCmd struct { + Debug bool `help:"Enable debug mode."` +} + func main() { - log.SetPrefix("[🤑] ") - log.SetFlags(log.Lmsgprefix | log.Ltime) - log.Print("Welcome to The Money Gopher") - log.SetOutput(colorable.NewColorableStdout()) + ctx := kong.Parse(&cmd) + + err := ctx.Run() + ctx.FatalIfErrorf(err) +} + +func (cmd *moneydCmd) Run() error { + var w = os.Stdout + var level = slog.LevelInfo + if cmd.Debug { + level = slog.LevelDebug + } + + logger := slog.New( + tint.NewHandler(colorable.NewColorable(w), &tint.Options{ + TimeFormat: time.TimeOnly, + Level: level, + NoColor: !isatty.IsTerminal(w.Fd()), + }), + ) + + slog.SetDefault(logger) + slog.Info("Welcome to the Money Gopher") db, err := persistence.OpenDB(persistence.Options{}) if err != nil { - log.Fatalf("Error while opening database: %v", err) + slog.Error("Error while opening database", tint.Err(err)) } mux := http.NewServeMux() @@ -58,6 +89,7 @@ func main() { h2c.NewHandler(handleCORS(mux), &http2.Server{}), ) log.Fatalf("listen failed: %v", err) + return err } func handleCORS(h http.Handler) http.Handler { diff --git a/go.mod b/go.mod index 25a79dba..4b8ac58f 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,9 @@ require ( github.com/alecthomas/kong v0.8.1 github.com/fatih/color v1.16.0 github.com/jotaen/kong-completion v0.0.6 + github.com/lmittmann/tint v1.0.3 github.com/mattn/go-colorable v0.1.13 + github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-sqlite3 v1.14.18 github.com/oxisto/assert v0.0.6 github.com/posener/complete v1.2.3 @@ -17,7 +19,6 @@ require ( ) require ( - github.com/mattn/go-isatty v0.0.20 // indirect github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect golang.org/x/sys v0.15.0 // indirect ) diff --git a/go.sum b/go.sum index aa741ad9..858b65e0 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/jotaen/kong-completion v0.0.6 h1:VP1KGvXPeB7MytYR+zZQoWw1gf/HIV1/EvWC38BHZN4= github.com/jotaen/kong-completion v0.0.6/go.mod h1:fuWw9snL6joY5mXbI0Dd5FWEZODaWXAeqaRxo6dAvLk= +github.com/lmittmann/tint v1.0.3 h1:W5PHeA2D8bBJVvabNfQD/XW9HPLZK1XoPZH0cq8NouQ= +github.com/lmittmann/tint v1.0.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= diff --git a/money-gopher.code-workspace b/money-gopher.code-workspace index f9201ce5..76441a6e 100644 --- a/money-gopher.code-workspace +++ b/money-gopher.code-workspace @@ -18,6 +18,7 @@ "fieldmaskpb", "headlessui", "heroicons", + "isatty", "ISIN", "jotaen", "kongcompletion", diff --git a/persistence/persistence.go b/persistence/persistence.go index 382c0ccf..2c22536d 100644 --- a/persistence/persistence.go +++ b/persistence/persistence.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "log" + "log/slog" "os" "strings" @@ -37,6 +38,13 @@ type Options struct { DSN string } +// LogValue implements slog.LogValuer. +func (o Options) LogValue() slog.Value { + return slog.GroupValue( + slog.Bool("in-memory", o.UseInMemory), + slog.String("dsn", o.DSN)) +} + // DB is a wrapper around [sql.DB]. This allows us to access all the // functionalities of [sql.DB] as well as accessing the DB object in our // internal functions. @@ -94,7 +102,7 @@ func OpenDB(opts Options) (db *DB, err error) { db.log.SetPrefix("[📄] ") db.initTables() - db.log.Print("Successfully opened database connection") + slog.Info("Successfully opened database connection", "opts", opts) return } diff --git a/service/securities/quote.go b/service/securities/quote.go index f0007d77..b27c6487 100644 --- a/service/securities/quote.go +++ b/service/securities/quote.go @@ -19,8 +19,10 @@ package securities import ( "context" "log" + "log/slog" "time" + "github.com/lmittmann/tint" portfoliov1 "github.com/oxisto/money-gopher/gen" "google.golang.org/protobuf/types/known/timestamppb" @@ -55,8 +57,15 @@ func (svc *service) TriggerSecurityQuoteUpdate(ctx context.Context, req *connect } // Trigger update from quote provider in separate go-routine - for _, ls := range sec.ListedOn { - go svc.updateQuote(qp, ls) + for idx, _ := range sec.ListedOn { + idx := idx + go func() { + ls := sec.ListedOn[idx] + err = svc.updateQuote(qp, ls) + if err != nil { + slog.Error("An error occurred during quote update", tint.Err(err), "ls", ls) + } + }() } }