From 9f5c408bbc4aea49965afc380fe47ccf19622a3b Mon Sep 17 00:00:00 2001 From: Michal Wasilewski Date: Wed, 27 Sep 2023 17:35:53 +0200 Subject: [PATCH] feat: support for structured logging Signed-off-by: Michal Wasilewski --- client/session/interface.go | 6 ++- internal/cmd/module/local_preview.go | 8 ++-- internal/cmd/profile/login_command.go | 13 +++--- main.go | 59 +++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/client/session/interface.go b/client/session/interface.go index 4487de2..b38dfb5 100644 --- a/client/session/interface.go +++ b/client/session/interface.go @@ -2,7 +2,8 @@ package session import ( "context" - "log" + "log/slog" + "os" ) // Session is an abstraction around session creation based on credentials from @@ -16,7 +17,8 @@ type Session interface { // Must provides a helper that either creates a Session or dies trying. func Must(out Session, err error) Session { if err != nil { - log.Fatalf("Could not create a Spacelift session: %v", err) + slog.Error("Could not create a Spacelift session", "err", err) + os.Exit(1) } return out diff --git a/internal/cmd/module/local_preview.go b/internal/cmd/module/local_preview.go index 1afd31f..8b612a9 100644 --- a/internal/cmd/module/local_preview.go +++ b/internal/cmd/module/local_preview.go @@ -3,19 +3,20 @@ package module import ( "context" "fmt" - "log" + "log/slog" "os" "path/filepath" "sync" "time" + "golang.org/x/sync/errgroup" + "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/mholt/archiver/v3" "github.com/shurcooL/graphql" "github.com/urfave/cli/v2" - "golang.org/x/sync/errgroup" "github.com/spacelift-io/spacectl/internal" "github.com/spacelift-io/spacectl/internal/cmd/authenticated" @@ -131,7 +132,8 @@ func localPreview() cli.ActionFunc { }) } if err := g.Wait(); err != nil { - log.Fatal("couldn't get runs: ", err) + slog.Error("couldn't get runs", "err", err) + os.Exit(1) } model.setRuns(newRuns) } diff --git a/internal/cmd/profile/login_command.go b/internal/cmd/profile/login_command.go index 945098a..e2baab2 100644 --- a/internal/cmd/profile/login_command.go +++ b/internal/cmd/profile/login_command.go @@ -5,7 +5,7 @@ import ( "context" "encoding/base64" "fmt" - "log" + "log/slog" "net" "net/http" "net/url" @@ -14,11 +14,12 @@ import ( "syscall" "time" + "golang.org/x/term" + "github.com/manifoldco/promptui" "github.com/pkg/browser" "github.com/pkg/errors" "github.com/urfave/cli/v2" - "golang.org/x/term" "github.com/spacelift-io/spacectl/client/session" "github.com/spacelift-io/spacectl/internal" @@ -226,11 +227,12 @@ func loginUsingWebBrowser(ctx *cli.Context, creds *session.StoredCredentials) er infoPage, err := url.Parse(creds.Endpoint) if err != nil { - log.Fatal(err) + slog.Error("Error parsing URL", "err", err) + os.Exit(1) } if handlerErr != nil { - log.Println(handlerErr) + slog.Error("login error", "err", handlerErr) infoPage.Path = cliAuthFailurePage http.Redirect(w, r, infoPage.String(), http.StatusTemporaryRedirect) } else { @@ -320,7 +322,8 @@ func serveOnOpenPort(host *string, port *int, handler func(w http.ResponseWriter go func() { if err := server.Serve(l); err != nil { if !errors.Is(err, http.ErrServerClosed) { - log.Fatalf("could not start local server: %s", err) + slog.Error("could not start local server", "err", err) + os.Exit(1) } } }() diff --git a/main.go b/main.go index 34ec509..451b985 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "log" + "log/slog" "os" "time" @@ -19,11 +19,15 @@ import ( var version = "dev" var date = "2006-01-02T15:04:05Z" +var loggingLevel = new(slog.LevelVar) func main() { + compileTime, err := time.Parse(time.RFC3339, date) + if err != nil { - log.Fatalf("Could not parse compilation date: %v", err) + slog.Error("Could not parse compilation date", "err", err) + os.Exit(1) } app := &cli.App{ Name: "spacectl", @@ -40,9 +44,58 @@ func main() { versioncmd.Command(version), workerpools.Command(), }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "log-format", + Aliases: []string{"f"}, + Usage: "Log format: json, text", + Value: "text", + }, + &cli.StringFlag{ + Name: "log-level", + Aliases: []string{"l"}, + Usage: "Log level: debug, info, warn, error", + Value: "info", + }, + }, + Before: func(c *cli.Context) error { + var logger *slog.Logger + switch c.String("log-format") { + case "json": + logger = slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{ + AddSource: true, + Level: loggingLevel, + })) + case "text": + logger = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + AddSource: true, + Level: loggingLevel, + })) + default: + return cli.Exit("Invalid log format", 1) + } + + switch c.String("log-level") { + case "debug": + loggingLevel.Set(slog.LevelDebug) + case "info": + loggingLevel.Set(slog.LevelInfo) + case "warn": + loggingLevel.Set(slog.LevelWarn) + case "error": + loggingLevel.Set(slog.LevelError) + default: + return cli.Exit("Invalid log level", 1) + } + + slog.SetDefault(logger) + + return nil + }, } if err := app.Run(os.Args); err != nil { - log.Fatal(err) + slog.Error("error", "err", err) + os.Exit(1) } }