From f388af994ec86ad17c6f69864f50e57683b7f65b Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 1 May 2023 15:19:55 -0400 Subject: [PATCH] feat: use contexts and clean logging --- cmd/soft/hook.go | 43 ++++++++++++++----------- cmd/soft/migrate_config.go | 56 ++++++++++++++++++--------------- cmd/soft/root.go | 19 ++++++++--- cmd/soft/serve.go | 27 ++++++++++++++-- examples/setuid/main.go | 3 +- log/log.go | 14 --------- server/backend/sqlite/db.go | 2 +- server/backend/sqlite/hooks.go | 15 +++++---- server/backend/sqlite/sqlite.go | 35 ++++++++++----------- server/backend/sqlite/user.go | 2 +- server/config/config.go | 28 +++++++++++++++-- server/config/file.go | 3 ++ server/cron/cron.go | 2 +- server/daemon/daemon.go | 47 +++++++++++++-------------- server/daemon/daemon_test.go | 7 +++-- server/git/git.go | 9 ++++-- server/jobs.go | 6 ++-- server/server.go | 36 ++++++++++----------- server/server_test.go | 5 +-- server/ssh/session_test.go | 3 +- server/ssh/ssh.go | 27 +++++++++------- server/stats/stats.go | 5 ++- server/web/http.go | 22 ++++++------- ui/common/common.go | 3 ++ ui/pages/repo/log.go | 8 ++--- ui/pages/repo/refs.go | 4 +-- ui/pages/repo/repo.go | 6 +--- ui/pages/selection/selection.go | 7 +---- ui/ui.go | 9 ++---- 29 files changed, 255 insertions(+), 198 deletions(-) delete mode 100644 log/log.go diff --git a/cmd/soft/hook.go b/cmd/soft/hook.go index fc78221722e4c0b5801cdc24fe30e9456786d325..c1f9d4233ab56846043584f052c1195bb235cffa 100644 --- a/cmd/soft/hook.go +++ b/cmd/soft/hook.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strings" + "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/server/backend/sqlite" "github.com/charmbracelet/soft-serve/server/config" @@ -18,53 +19,57 @@ import ( "github.com/spf13/cobra" ) -var ( - confixCtxKey = "config" - backendCtxKey = "backend" -) - var ( configPath string + logFileCtxKey = struct{}{} + hookCmd = &cobra.Command{ Use: "hook", Short: "Run git server hooks", Long: "Handles Soft Serve git server hooks.", Hidden: true, PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() cfg, err := config.ParseConfig(configPath) if err != nil { return fmt.Errorf("could not parse config: %w", err) } - customHooksPath := filepath.Join(filepath.Dir(configPath), "hooks") - if _, err := os.Stat(customHooksPath); err != nil && os.IsNotExist(err) { - os.MkdirAll(customHooksPath, os.ModePerm) - // Generate update hook example without executable permissions - hookPath := filepath.Join(customHooksPath, "update.sample") - if err := os.WriteFile(hookPath, []byte(updateHookExample), 0744); err != nil { - return fmt.Errorf("failed to generate update hook example: %w", err) - } + ctx = config.WithContext(ctx, cfg) + + logPath := filepath.Join(cfg.DataPath, "log", "hooks.log") + f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("opening file: %w", err) } + ctx = context.WithValue(ctx, logFileCtxKey, f) + logger := log.FromContext(ctx) + logger.SetOutput(f) + ctx = log.WithContext(ctx, logger) + cmd.SetContext(ctx) + // Set up the backend // TODO: support other backends - sb, err := sqlite.NewSqliteBackend(cmd.Context(), cfg) + sb, err := sqlite.NewSqliteBackend(ctx) if err != nil { return fmt.Errorf("failed to create sqlite backend: %w", err) } cfg = cfg.WithBackend(sb) - cmd.SetContext(context.WithValue(cmd.Context(), confixCtxKey, cfg)) - cmd.SetContext(context.WithValue(cmd.Context(), backendCtxKey, sb)) - return nil }, + PersistentPostRunE: func(cmd *cobra.Command, _ []string) error { + f := cmd.Context().Value(logFileCtxKey).(*os.File) + return f.Close() + }, } hooksRunE = func(cmd *cobra.Command, args []string) error { - cfg := cmd.Context().Value(confixCtxKey).(*config.Config) + ctx := cmd.Context() + cfg := config.FromContext(ctx) hks := cfg.Backend.(backend.Hooks) // This is set in the server before invoking git-receive-pack/git-upload-pack @@ -119,7 +124,7 @@ var ( // Custom hooks if stat, err := os.Stat(customHookPath); err == nil && !stat.IsDir() && stat.Mode()&0o111 != 0 { // If the custom hook is executable, run it - if err := runCommand(cmd.Context(), &buf, stdout, stderr, customHookPath, args...); err != nil { + if err := runCommand(ctx, &buf, stdout, stderr, customHookPath, args...); err != nil { return fmt.Errorf("failed to run custom hook: %w", err) } } diff --git a/cmd/soft/migrate_config.go b/cmd/soft/migrate_config.go index a20cc9d41a51d71aaac9b4433150b52986892177..888c722aa44d82bd9e37bb9fd67510d685534ddc 100644 --- a/cmd/soft/migrate_config.go +++ b/cmd/soft/migrate_config.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "errors" "fmt" "io" "os" @@ -27,15 +28,18 @@ var ( Short: "Migrate config to new format", Hidden: true, RunE: func(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + + logger := log.FromContext(ctx) // Disable logging timestamp - log.SetReportTimestamp(false) + logger.SetReportTimestamp(false) keyPath := os.Getenv("SOFT_SERVE_KEY_PATH") reposPath := os.Getenv("SOFT_SERVE_REPO_PATH") bindAddr := os.Getenv("SOFT_SERVE_BIND_ADDRESS") - ctx := cmd.Context() cfg := config.DefaultConfig() - sb, err := sqlite.NewSqliteBackend(ctx, cfg) + ctx = config.WithContext(ctx, cfg) + sb, err := sqlite.NewSqliteBackend(ctx) if err != nil { return fmt.Errorf("failed to create sqlite backend: %w", err) } @@ -43,13 +47,13 @@ var ( cfg = cfg.WithBackend(sb) // Set SSH listen address - log.Info("Setting SSH listen address...") + logger.Info("Setting SSH listen address...") if bindAddr != "" { cfg.SSH.ListenAddr = bindAddr } // Copy SSH host key - log.Info("Copying SSH host key...") + logger.Info("Copying SSH host key...") if keyPath != "" { if err := os.MkdirAll(filepath.Join(cfg.DataPath, "ssh"), os.ModePerm); err != nil { return fmt.Errorf("failed to create ssh directory: %w", err) @@ -60,14 +64,14 @@ var ( } if err := copyFile(keyPath+".pub", filepath.Join(cfg.DataPath, "ssh", filepath.Base(keyPath))+".pub"); err != nil { - log.Errorf("failed to copy ssh key: %s", err) + logger.Errorf("failed to copy ssh key: %s", err) } cfg.SSH.KeyPath = filepath.Join(cfg.DataPath, "ssh", filepath.Base(keyPath)) } // Read config - log.Info("Reading config repository...") + logger.Info("Reading config repository...") r, err := git.Open(filepath.Join(reposPath, "config")) if err != nil { return fmt.Errorf("failed to open config repo: %w", err) @@ -119,7 +123,7 @@ var ( cfg.SSH.PublicURL = fmt.Sprintf("ssh://%s:%d", ocfg.Host, ocfg.Port) // Set server settings - log.Info("Setting server settings...") + logger.Info("Setting server settings...") if cfg.Backend.SetAllowKeyless(ocfg.AllowKeyless) != nil { fmt.Fprintf(os.Stderr, "failed to set allow keyless\n") } @@ -132,7 +136,7 @@ var ( // Copy repos if reposPath != "" { - log.Info("Copying repos...") + logger.Info("Copying repos...") if err := os.MkdirAll(filepath.Join(cfg.DataPath, "repos"), os.ModePerm); err != nil { return fmt.Errorf("failed to create repos directory: %w", err) } @@ -151,7 +155,7 @@ var ( continue } - log.Infof(" Copying repo %s", dir.Name()) + logger.Infof(" Copying repo %s", dir.Name()) src := filepath.Join(reposPath, utils.SanitizeRepo(dir.Name())) dst := filepath.Join(cfg.DataPath, "repos", utils.SanitizeRepo(dir.Name())) + ".git" if err := os.MkdirAll(dst, os.ModePerm); err != nil { @@ -168,7 +172,7 @@ var ( } if hasReadme { - log.Infof(" Copying readme from \"config\" to \".soft-serve\"") + logger.Infof(" Copying readme from \"config\" to \".soft-serve\"") // Switch to main branch bcmd := git.NewCommand("branch", "-M", "main") @@ -236,7 +240,7 @@ var ( } // Set repos metadata & collabs - log.Info("Setting repos metadata & collabs...") + logger.Info("Setting repos metadata & collabs...") for _, r := range ocfg.Repos { repo, name := r.Repo, r.Name // Special case for config repo @@ -246,26 +250,26 @@ var ( } if err := sb.SetProjectName(repo, name); err != nil { - log.Errorf("failed to set repo name to %s: %s", repo, err) + logger.Errorf("failed to set repo name to %s: %s", repo, err) } if err := sb.SetDescription(repo, r.Note); err != nil { - log.Errorf("failed to set repo description to %s: %s", repo, err) + logger.Errorf("failed to set repo description to %s: %s", repo, err) } if err := sb.SetPrivate(repo, r.Private); err != nil { - log.Errorf("failed to set repo private to %s: %s", repo, err) + logger.Errorf("failed to set repo private to %s: %s", repo, err) } for _, collab := range r.Collabs { if err := sb.AddCollaborator(repo, collab); err != nil { - log.Errorf("failed to add repo collab to %s: %s", repo, err) + logger.Errorf("failed to add repo collab to %s: %s", repo, err) } } } // Create users & collabs - log.Info("Creating users & collabs...") + logger.Info("Creating users & collabs...") for _, user := range ocfg.Users { keys := make(map[string]ssh.PublicKey) for _, key := range user.PublicKeys { @@ -284,23 +288,23 @@ var ( username := strings.ToLower(user.Name) username = strings.ReplaceAll(username, " ", "-") - log.Infof("Creating user %q", username) + logger.Infof("Creating user %q", username) if _, err := sb.CreateUser(username, backend.UserOptions{ Admin: user.Admin, PublicKeys: pubkeys, }); err != nil { - log.Errorf("failed to create user: %s", err) + logger.Errorf("failed to create user: %s", err) } for _, repo := range user.CollabRepos { if err := sb.AddCollaborator(repo, username); err != nil { - log.Errorf("failed to add user collab to %s: %s\n", repo, err) + logger.Errorf("failed to add user collab to %s: %s\n", repo, err) } } } - log.Info("Writing config...") - defer log.Info("Done!") + logger.Info("Writing config...") + defer logger.Info("Done!") return config.WriteConfig(filepath.Join(cfg.DataPath, "config.yaml"), cfg) }, } @@ -371,21 +375,23 @@ func copyDir(src string, dst string) error { if fds, err = os.ReadDir(src); err != nil { return err } + for _, fd := range fds { srcfp := filepath.Join(src, fd.Name()) dstfp := filepath.Join(dst, fd.Name()) if fd.IsDir() { if err = copyDir(srcfp, dstfp); err != nil { - log.Error("failed to copy directory", "err", err) + err = errors.Join(err, err) } } else { if err = copyFile(srcfp, dstfp); err != nil { - log.Error("failed to copy file", "err", err) + err = errors.Join(err, err) } } } - return nil + + return err } // Config is the configuration for the server. diff --git a/cmd/soft/root.go b/cmd/soft/root.go index 44b6e8ab84f9631fa0fcfd069a90e17b60183f86..b47e9cdd04bf8393c4b537e1599d0ed2b47e3dcc 100644 --- a/cmd/soft/root.go +++ b/cmd/soft/root.go @@ -4,9 +4,11 @@ import ( "context" "os" "runtime/debug" + "strconv" + "strings" + "time" "github.com/charmbracelet/log" - _ "github.com/charmbracelet/soft-serve/log" "github.com/spf13/cobra" ) @@ -51,15 +53,24 @@ func init() { } func main() { + ctx := context.Background() logger := log.NewWithOptions(os.Stderr, log.Options{ ReportTimestamp: true, - TimeFormat: "2006-01-02", + TimeFormat: time.DateOnly, }) - if os.Getenv("SOFT_SERVE_DEBUG") == "true" { + if debug, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_DEBUG")); debug { logger.SetLevel(log.DebugLevel) } - ctx := context.Background() + switch strings.ToLower(os.Getenv("SOFT_SERVE_LOG_FORMAT")) { + case "json": + logger.SetFormatter(log.JSONFormatter) + case "logfmt": + logger.SetFormatter(log.LogfmtFormatter) + case "text": + logger.SetFormatter(log.TextFormatter) + } + ctx = log.WithContext(ctx, logger) if err := rootCmd.ExecuteContext(ctx); err != nil { os.Exit(1) diff --git a/cmd/soft/serve.go b/cmd/soft/serve.go index 55e0d2aca2c24c381d795c754149ad269edd5f83..b9a9b2937a1be656acbe38b32325161cee61c572 100644 --- a/cmd/soft/serve.go +++ b/cmd/soft/serve.go @@ -5,10 +5,10 @@ import ( "fmt" "os" "os/signal" + "path/filepath" "syscall" "time" - _ "github.com/charmbracelet/soft-serve/log" "github.com/charmbracelet/soft-serve/server" "github.com/charmbracelet/soft-serve/server/config" "github.com/spf13/cobra" @@ -20,10 +20,31 @@ var ( Short: "Start the server", Long: "Start the server", Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() cfg := config.DefaultConfig() - s, err := server.NewServer(ctx, cfg) + ctx = config.WithContext(ctx, cfg) + cmd.SetContext(ctx) + + // Create custom hooks directory if it doesn't exist + customHooksPath := filepath.Join(cfg.DataPath, "hooks") + if _, err := os.Stat(customHooksPath); err != nil && os.IsNotExist(err) { + os.MkdirAll(customHooksPath, os.ModePerm) // nolint: errcheck + // Generate update hook example without executable permissions + hookPath := filepath.Join(customHooksPath, "update.sample") + // nolint: gosec + if err := os.WriteFile(hookPath, []byte(updateHookExample), 0744); err != nil { + return fmt.Errorf("failed to generate update hook example: %w", err) + } + } + + // Create log directory if it doesn't exist + logPath := filepath.Join(cfg.DataPath, "log") + if _, err := os.Stat(logPath); err != nil && os.IsNotExist(err) { + os.MkdirAll(logPath, os.ModePerm) // nolint: errcheck + } + + s, err := server.NewServer(ctx) if err != nil { return fmt.Errorf("start server: %w", err) } diff --git a/examples/setuid/main.go b/examples/setuid/main.go index e9cc1f751a431c961e36d050fa62f809479bd8c1..4b6c4770d5d6d2a759b1dd291bc67faf655faa4d 100644 --- a/examples/setuid/main.go +++ b/examples/setuid/main.go @@ -46,8 +46,9 @@ func main() { } ctx := context.Background() cfg := config.DefaultConfig() + ctx = config.WithContext(ctx, cfg) cfg.SSH.ListenAddr = fmt.Sprintf(":%d", *port) - s, err := server.NewServer(ctx, cfg) + s, err := server.NewServer(ctx) if err != nil { log.Fatal(err) } diff --git a/log/log.go b/log/log.go deleted file mode 100644 index d2df5bbb46ab00b34f37bceb8c52a55f3a1e4269..0000000000000000000000000000000000000000 --- a/log/log.go +++ /dev/null @@ -1,14 +0,0 @@ -// Package log initializes the logger for Soft Serve modules. -package log - -import ( - "os" - - "github.com/charmbracelet/log" -) - -func init() { - if os.Getenv("SOFT_SERVE_DEBUG") == "true" { - log.SetLevel(log.DebugLevel) - } -} diff --git a/server/backend/sqlite/db.go b/server/backend/sqlite/db.go index 839d9105db96bc4a624cca5c1dca357758fd5a8a..fac0394f4309af33f7c26cc84dcb94904013f512 100644 --- a/server/backend/sqlite/db.go +++ b/server/backend/sqlite/db.go @@ -66,7 +66,7 @@ func (d *SqliteBackend) init() error { for _, k := range d.cfg.InitialAdminKeys { pk, _, err := backend.ParseAuthorizedKey(k) if err != nil { - logger.Error("error parsing initial admin key, skipping", "key", k, "err", err) + d.logger.Error("error parsing initial admin key, skipping", "key", k, "err", err) continue } diff --git a/server/backend/sqlite/hooks.go b/server/backend/sqlite/hooks.go index cf82c2c009a3b72415d0ba09b72152521eabe823..0f9d61658d0b061bc37c14e807c614444f5cc6c7 100644 --- a/server/backend/sqlite/hooks.go +++ b/server/backend/sqlite/hooks.go @@ -4,7 +4,6 @@ import ( "io" "sync" - "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/server/backend" ) @@ -12,28 +11,28 @@ import ( // // It implements Hooks. func (d *SqliteBackend) PostReceive(stdout io.Writer, stderr io.Writer, repo string, args []backend.HookArg) { - log.WithPrefix("backend.sqlite.hooks").Debug("post-receive hook called", "repo", repo, "args", args) + d.logger.Debug("post-receive hook called", "repo", repo, "args", args) } // PreReceive is called by the git pre-receive hook. // // It implements Hooks. func (d *SqliteBackend) PreReceive(stdout io.Writer, stderr io.Writer, repo string, args []backend.HookArg) { - log.WithPrefix("backend.sqlite.hooks").Debug("pre-receive hook called", "repo", repo, "args", args) + d.logger.Debug("pre-receive hook called", "repo", repo, "args", args) } // Update is called by the git update hook. // // It implements Hooks. func (d *SqliteBackend) Update(stdout io.Writer, stderr io.Writer, repo string, arg backend.HookArg) { - log.WithPrefix("backend.sqlite.hooks").Debug("update hook called", "repo", repo, "arg", arg) + d.logger.Debug("update hook called", "repo", repo, "arg", arg) } // PostUpdate is called by the git post-update hook. // // It implements Hooks. func (d *SqliteBackend) PostUpdate(stdout io.Writer, stderr io.Writer, repo string, args ...string) { - log.WithPrefix("backend.sqlite.hooks").Debug("post-update hook called", "repo", repo, "args", args) + d.logger.Debug("post-update hook called", "repo", repo, "args", args) var wg sync.WaitGroup @@ -44,18 +43,18 @@ func (d *SqliteBackend) PostUpdate(stdout io.Writer, stderr io.Writer, repo stri rr, err := d.Repository(repo) if err != nil { - log.WithPrefix("backend.sqlite.hooks").Error("error getting repository", "repo", repo, "err", err) + d.logger.Error("error getting repository", "repo", repo, "err", err) return } r, err := rr.Open() if err != nil { - log.WithPrefix("backend.sqlite.hooks").Error("error opening repository", "repo", repo, "err", err) + d.logger.Error("error opening repository", "repo", repo, "err", err) return } if err := r.UpdateServerInfo(); err != nil { - log.WithPrefix("backend.sqlite.hooks").Error("error updating server-info", "repo", repo, "err", err) + d.logger.Error("error updating server-info", "repo", repo, "err", err) return } }() diff --git a/server/backend/sqlite/sqlite.go b/server/backend/sqlite/sqlite.go index 0720a09710bed98a2a3062482740ae3ddaafa050..8b0a0c98a359e9282e614cabc66dceb76e9a9f6f 100644 --- a/server/backend/sqlite/sqlite.go +++ b/server/backend/sqlite/sqlite.go @@ -17,17 +17,14 @@ import ( _ "modernc.org/sqlite" ) -var ( - logger = log.WithPrefix("backend.sqlite") -) - // SqliteBackend is a backend that uses a SQLite database as a Soft Serve // backend. type SqliteBackend struct { - cfg *config.Config - ctx context.Context - dp string - db *sqlx.DB + cfg *config.Config + ctx context.Context + dp string + db *sqlx.DB + logger *log.Logger } var _ backend.Backend = (*SqliteBackend)(nil) @@ -37,7 +34,8 @@ func (d *SqliteBackend) reposPath() string { } // NewSqliteBackend creates a new SqliteBackend. -func NewSqliteBackend(ctx context.Context, cfg *config.Config) (*SqliteBackend, error) { +func NewSqliteBackend(ctx context.Context) (*SqliteBackend, error) { + cfg := config.FromContext(ctx) dataPath := cfg.DataPath if err := os.MkdirAll(dataPath, os.ModePerm); err != nil { return nil, err @@ -50,10 +48,11 @@ func NewSqliteBackend(ctx context.Context, cfg *config.Config) (*SqliteBackend, } d := &SqliteBackend{ - cfg: cfg, - ctx: ctx, - dp: dataPath, - db: db, + cfg: cfg, + ctx: ctx, + dp: dataPath, + db: db, + logger: log.FromContext(ctx).WithPrefix("sqlite"), } if err := d.init(); err != nil { @@ -137,13 +136,13 @@ func (d *SqliteBackend) CreateRepository(name string, opts backend.RepositoryOpt rr, err := git.Init(rp, true) if err != nil { - logger.Debug("failed to create repository", "err", err) + d.logger.Debug("failed to create repository", "err", err) cleanup() // nolint: errcheck return nil, err } if err := rr.UpdateServerInfo(); err != nil { - logger.Debug("failed to update server info", "err", err) + d.logger.Debug("failed to update server info", "err", err) cleanup() // nolint: errcheck return nil, err } @@ -154,7 +153,7 @@ func (d *SqliteBackend) CreateRepository(name string, opts backend.RepositoryOpt name, opts.ProjectName, opts.Description, opts.Private, opts.Mirror, opts.Hidden) return err }); err != nil { - logger.Debug("failed to create repository in database", "err", err) + d.logger.Debug("failed to create repository in database", "err", err) return nil, wrapDbErr(err) } @@ -192,7 +191,7 @@ func (d *SqliteBackend) ImportRepository(name string, remote string, opts backen } if err := git.Clone(remote, rp, copts); err != nil { - logger.Error("failed to clone repository", "err", err, "mirror", opts.Mirror, "remote", remote, "path", rp) + d.logger.Error("failed to clone repository", "err", err, "mirror", opts.Mirror, "remote", remote, "path", rp) return nil, err } @@ -311,7 +310,7 @@ func (d *SqliteBackend) Repository(repo string) (backend.Repository, error) { } if count == 0 { - logger.Warn("repository exists but not found in database", "repo", repo) + d.logger.Warn("repository exists but not found in database", "repo", repo) return nil, ErrRepoNotExist } diff --git a/server/backend/sqlite/user.go b/server/backend/sqlite/user.go index 456581d4d74c43a507d33194089db8d0611c98dd..7306c5cfa4e7d6f57ffd58258960eb15eadb8b55 100644 --- a/server/backend/sqlite/user.go +++ b/server/backend/sqlite/user.go @@ -180,7 +180,7 @@ func (d *SqliteBackend) CreateUser(username string, opts backend.UserOptions) (b if len(opts.PublicKeys) > 0 { userID, err := r.LastInsertId() if err != nil { - logger.Error("error getting last insert id") + d.logger.Error("error getting last insert id") return err } diff --git a/server/config/config.go b/server/config/config.go index 7bd696e0e0d18220f3726dea9d781635b6f24fec..2c68bda24328db19d129749abe0166b954ab469d 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -1,6 +1,7 @@ package config import ( + "context" "errors" "fmt" "os" @@ -88,6 +89,10 @@ type Config struct { // Stats is the configuration for the stats server. Stats StatsConfig `envPrefix:"STATS_" yaml:"stats"` + // LogFormat is the format of the logs. + // Valid values are "json", "logfmt", and "text". + LogFormat string `env:"LOG_FORMAT" yaml:"log_format"` + // InitialAdminKeys is a list of public keys that will be added to the list of admins. InitialAdminKeys []string `env:"INITIAL_ADMIN_KEYS" envSeparator:"\n" yaml:"initial_admin_keys"` @@ -101,8 +106,9 @@ type Config struct { func parseConfig(path string) (*Config, error) { dataPath := filepath.Dir(path) cfg := &Config{ - Name: "Soft Serve", - DataPath: dataPath, + Name: "Soft Serve", + LogFormat: "text", + DataPath: dataPath, SSH: SSHConfig{ ListenAddr: ":23231", PublicURL: "ssh://localhost:23231", @@ -273,3 +279,21 @@ func parseAuthKeys(aks []string) []ssh.PublicKey { func (c *Config) AdminKeys() []ssh.PublicKey { return parseAuthKeys(c.InitialAdminKeys) } + +var ( + configCtxKey = struct{ string }{"config"} +) + +// WithContext returns a new context with the configuration attached. +func WithContext(ctx context.Context, cfg *Config) context.Context { + return context.WithValue(ctx, configCtxKey, cfg) +} + +// FromContext returns the configuration from the context. +func FromContext(ctx context.Context) *Config { + if c, ok := ctx.Value(configCtxKey).(*Config); ok { + return c + } + + return DefaultConfig() +} diff --git a/server/config/file.go b/server/config/file.go index 1869dd87fcfd72aca7c71fb50cf6a19e49231f9e..11e239fb094d30b5e06591c1f6a8603a29b331b6 100644 --- a/server/config/file.go +++ b/server/config/file.go @@ -12,6 +12,9 @@ var ( # This is the name that will be displayed in the UI. name: "{{ .Name }}" +# Log format to use. Valid values are "json", "logfmt", and "text". +log_format: "{{ .LogFormat }}" + # The SSH server configuration. ssh: # The address on which the SSH server will listen. diff --git a/server/cron/cron.go b/server/cron/cron.go index 27e052dee20e8aeaa4dc77d2530a4f4ab87ef02d..af4baf0ad5f83d8398e403ffe15c124833b1dbda 100644 --- a/server/cron/cron.go +++ b/server/cron/cron.go @@ -39,7 +39,7 @@ func (l cronLogger) Error(err error, msg string, keysAndValues ...interface{}) { // NewCronScheduler returns a new Cron. func NewCronScheduler(ctx context.Context) *CronScheduler { - logger := cronLogger{log.FromContext(ctx).WithPrefix("server.cron")} + logger := cronLogger{log.FromContext(ctx).WithPrefix("cron")} return &CronScheduler{ Cron: cron.New(cron.WithLogger(logger)), } diff --git a/server/daemon/daemon.go b/server/daemon/daemon.go index c3e08381502b6571fb4d3595627790680d053965..cf71ae6730ac445fa8122160c86c401edd79cba7 100644 --- a/server/daemon/daemon.go +++ b/server/daemon/daemon.go @@ -19,10 +19,6 @@ import ( "github.com/prometheus/client_golang/prometheus/promauto" ) -var ( - logger = log.WithPrefix("server.daemon") -) - var ( uploadPackGitCounter = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "soft_serve", @@ -81,6 +77,7 @@ func (m *connections) CloseAll() { // GitDaemon represents a Git daemon. type GitDaemon struct { + ctx context.Context listener net.Listener addr string finished chan struct{} @@ -88,16 +85,20 @@ type GitDaemon struct { cfg *config.Config wg sync.WaitGroup once sync.Once + logger *log.Logger } // NewDaemon returns a new Git daemon. -func NewGitDaemon(cfg *config.Config) (*GitDaemon, error) { +func NewGitDaemon(ctx context.Context) (*GitDaemon, error) { + cfg := config.FromContext(ctx) addr := cfg.Git.ListenAddr d := &GitDaemon{ + ctx: ctx, addr: addr, finished: make(chan struct{}, 1), cfg: cfg, conns: connections{m: make(map[net.Conn]struct{})}, + logger: log.FromContext(ctx).WithPrefix("gitdaemon"), } listener, err := net.Listen("tcp", d.addr) if err != nil { @@ -122,7 +123,7 @@ func (d *GitDaemon) Start() error { case <-d.finished: return ErrServerClosed default: - logger.Debugf("git: error accepting connection: %v", err) + d.logger.Debugf("git: error accepting connection: %v", err) } if ne, ok := err.(net.Error); ok && ne.Temporary() { if tempDelay == 0 { @@ -141,8 +142,8 @@ func (d *GitDaemon) Start() error { // Close connection if there are too many open connections. if d.conns.Size()+1 >= d.cfg.Git.MaxConnections { - logger.Debugf("git: max connections reached, closing %s", conn.RemoteAddr()) - fatal(conn, git.ErrMaxConnections) + d.logger.Debugf("git: max connections reached, closing %s", conn.RemoteAddr()) + d.fatal(conn, git.ErrMaxConnections) continue } @@ -154,10 +155,10 @@ func (d *GitDaemon) Start() error { } } -func fatal(c net.Conn, err error) { +func (d *GitDaemon) fatal(c net.Conn, err error) { git.WritePktline(c, err) if err := c.Close(); err != nil { - logger.Debugf("git: error closing connection: %v", err) + d.logger.Debugf("git: error closing connection: %v", err) } } @@ -185,10 +186,10 @@ func (d *GitDaemon) handleClient(conn net.Conn) { if !s.Scan() { if err := s.Err(); err != nil { if nerr, ok := err.(net.Error); ok && nerr.Timeout() { - fatal(c, git.ErrTimeout) + d.fatal(c, git.ErrTimeout) } else { - logger.Debugf("git: error scanning pktline: %v", err) - fatal(c, git.ErrSystemMalfunction) + d.logger.Debugf("git: error scanning pktline: %v", err) + d.fatal(c, git.ErrSystemMalfunction) } } return @@ -199,14 +200,14 @@ func (d *GitDaemon) handleClient(conn net.Conn) { select { case <-ctx.Done(): if err := ctx.Err(); err != nil { - logger.Debugf("git: connection context error: %v", err) + d.logger.Debugf("git: connection context error: %v", err) } return case <-readc: line := s.Bytes() split := bytes.SplitN(line, []byte{' '}, 2) if len(split) != 2 { - fatal(c, git.ErrInvalidRequest) + d.fatal(c, git.ErrInvalidRequest) return } @@ -220,36 +221,36 @@ func (d *GitDaemon) handleClient(conn net.Conn) { gitPack = git.UploadArchive counter = uploadArchiveGitCounter default: - fatal(c, git.ErrInvalidRequest) + d.fatal(c, git.ErrInvalidRequest) return } opts := bytes.Split(split[1], []byte{'\x00'}) if len(opts) == 0 { - fatal(c, git.ErrInvalidRequest) + d.fatal(c, git.ErrInvalidRequest) return } if !d.cfg.Backend.AllowKeyless() { - fatal(c, git.ErrNotAuthed) + d.fatal(c, git.ErrNotAuthed) return } name := utils.SanitizeRepo(string(opts[0])) - logger.Debugf("git: connect %s %s %s", c.RemoteAddr(), cmd, name) - defer logger.Debugf("git: disconnect %s %s %s", c.RemoteAddr(), cmd, name) + d.logger.Debugf("git: connect %s %s %s", c.RemoteAddr(), cmd, name) + defer d.logger.Debugf("git: disconnect %s %s %s", c.RemoteAddr(), cmd, name) // git bare repositories should end in ".git" // https://git-scm.com/docs/gitrepository-layout repo := name + ".git" reposDir := filepath.Join(d.cfg.DataPath, "repos") if err := git.EnsureWithin(reposDir, repo); err != nil { - fatal(c, err) + d.fatal(c, err) return } auth := d.cfg.Backend.AccessLevel(name, "") if auth < backend.ReadOnlyAccess { - fatal(c, git.ErrNotAuthed) + d.fatal(c, git.ErrNotAuthed) return } @@ -260,7 +261,7 @@ func (d *GitDaemon) handleClient(conn net.Conn) { } if err := gitPack(ctx, c, c, c, filepath.Join(reposDir, repo), envs...); err != nil { - fatal(c, err) + d.fatal(c, err) return } diff --git a/server/daemon/daemon_test.go b/server/daemon/daemon_test.go index e728af6e3597804ec568ce22aa5385a7d370c51d..b06decbf4724ad3ab3a62e6632bddbf32a0078bd 100644 --- a/server/daemon/daemon_test.go +++ b/server/daemon/daemon_test.go @@ -32,13 +32,14 @@ func TestMain(m *testing.M) { os.Setenv("SOFT_SERVE_GIT_MAX_TIMEOUT", "100") os.Setenv("SOFT_SERVE_GIT_IDLE_TIMEOUT", "1") os.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", test.RandomPort())) + ctx := context.TODO() cfg := config.DefaultConfig() - d, err := NewGitDaemon(cfg) + ctx = config.WithContext(ctx, cfg) + d, err := NewGitDaemon(ctx) if err != nil { log.Fatal(err) } - ctx := context.TODO() - fb, err := sqlite.NewSqliteBackend(ctx, cfg) + fb, err := sqlite.NewSqliteBackend(ctx) if err != nil { log.Fatal(err) } diff --git a/server/git/git.go b/server/git/git.go index 9a8b5ed9d401616151e7a47ced47a5ad9ff40dbc..5a49b3dc41f7e71b488ec3f511d1d665aa2a3a29 100644 --- a/server/git/git.go +++ b/server/git/git.go @@ -12,6 +12,7 @@ import ( "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/git" + "github.com/charmbracelet/soft-serve/server/config" "github.com/go-git/go-git/v5/plumbing/format/pktline" "golang.org/x/sync/errgroup" ) @@ -78,12 +79,16 @@ func ReceivePack(ctx context.Context, in io.Reader, out io.Writer, er io.Writer, // RunGit runs a git command in the given repo. func RunGit(ctx context.Context, in io.Reader, out io.Writer, er io.Writer, dir string, envs []string, args ...string) error { - logger := log.WithPrefix("server.git") + cfg := config.FromContext(ctx) + logger := log.FromContext(ctx).WithPrefix("rungit") c := exec.CommandContext(ctx, "git", args...) c.Dir = dir c.Env = append(c.Env, envs...) - c.Env = append(c.Env, "SOFT_SERVE_DEBUG="+os.Getenv("SOFT_SERVE_DEBUG")) c.Env = append(c.Env, "PATH="+os.Getenv("PATH")) + c.Env = append(c.Env, "SOFT_SERVE_DEBUG="+os.Getenv("SOFT_SERVE_DEBUG")) + if cfg != nil { + c.Env = append(c.Env, "SOFT_SERVE_LOG_FORMAT="+cfg.LogFormat) + } stdin, err := c.StdinPipe() if err != nil { diff --git a/server/jobs.go b/server/jobs.go index 5f239be583961649353568d5048042e497f8bfb6..06656d1ccec5b53b6868fb613168b170802962db 100644 --- a/server/jobs.go +++ b/server/jobs.go @@ -5,7 +5,6 @@ import ( "path/filepath" "github.com/charmbracelet/soft-serve/git" - "github.com/charmbracelet/soft-serve/server/config" ) var ( @@ -15,9 +14,10 @@ var ( ) // mirrorJob runs the (pull) mirror job task. -func mirrorJob(cfg *config.Config) func() { +func (s *Server) mirrorJob() func() { + cfg := s.Config b := cfg.Backend - logger := logger.WithPrefix("server.mirrorJob") + logger := s.logger return func() { repos, err := b.Repositories() if err != nil { diff --git a/server/server.go b/server/server.go index 182b20f6662cb49c3a8655f84b5b06d806f047cd..012ea05192cf94ad8bc5f120eaf3c041a40ca62d 100644 --- a/server/server.go +++ b/server/server.go @@ -20,10 +20,6 @@ import ( "golang.org/x/sync/errgroup" ) -var ( - logger = log.WithPrefix("server") -) - // Server is the Soft Serve server. type Server struct { SSHServer *sshsrv.SSHServer @@ -33,7 +29,9 @@ type Server struct { Cron *cron.CronScheduler Config *config.Config Backend backend.Backend - ctx context.Context + + logger *log.Logger + ctx context.Context } // NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH @@ -41,10 +39,12 @@ type Server struct { // key can be provided with authKey. If authKey is provided, access will be // restricted to that key. If authKey is not provided, the server will be // publicly writable until configured otherwise by cloning the `config` repo. -func NewServer(ctx context.Context, cfg *config.Config) (*Server, error) { +func NewServer(ctx context.Context) (*Server, error) { + cfg := config.FromContext(ctx) + var err error if cfg.Backend == nil { - sb, err := sqlite.NewSqliteBackend(ctx, cfg) + sb, err := sqlite.NewSqliteBackend(ctx) if err != nil { return nil, fmt.Errorf("create backend: %w", err) } @@ -56,28 +56,29 @@ func NewServer(ctx context.Context, cfg *config.Config) (*Server, error) { Cron: cron.NewCronScheduler(ctx), Config: cfg, Backend: cfg.Backend, + logger: log.FromContext(ctx).WithPrefix("server"), ctx: ctx, } // Add cron jobs. - srv.Cron.AddFunc(jobSpecs["mirror"], mirrorJob(cfg)) + srv.Cron.AddFunc(jobSpecs["mirror"], srv.mirrorJob()) - srv.SSHServer, err = sshsrv.NewSSHServer(cfg) + srv.SSHServer, err = sshsrv.NewSSHServer(ctx) if err != nil { return nil, fmt.Errorf("create ssh server: %w", err) } - srv.GitDaemon, err = daemon.NewGitDaemon(cfg) + srv.GitDaemon, err = daemon.NewGitDaemon(ctx) if err != nil { return nil, fmt.Errorf("create git daemon: %w", err) } - srv.HTTPServer, err = web.NewHTTPServer(cfg) + srv.HTTPServer, err = web.NewHTTPServer(ctx) if err != nil { return nil, fmt.Errorf("create http server: %w", err) } - srv.StatsServer, err = stats.NewStatsServer(cfg) + srv.StatsServer, err = stats.NewStatsServer(ctx) if err != nil { return nil, fmt.Errorf("create stats server: %w", err) } @@ -101,38 +102,37 @@ func start(ctx context.Context, fn func() error) error { // Start starts the SSH server. func (s *Server) Start() error { - logger := log.FromContext(s.ctx).WithPrefix("server") errg, ctx := errgroup.WithContext(s.ctx) errg.Go(func() error { - logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr) + s.logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr) if err := start(ctx, s.GitDaemon.Start); !errors.Is(err, daemon.ErrServerClosed) { return err } return nil }) errg.Go(func() error { - logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr) + s.logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr) if err := start(ctx, s.HTTPServer.ListenAndServe); !errors.Is(err, http.ErrServerClosed) { return err } return nil }) errg.Go(func() error { - logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr) + s.logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr) if err := start(ctx, s.SSHServer.ListenAndServe); !errors.Is(err, ssh.ErrServerClosed) { return err } return nil }) errg.Go(func() error { - logger.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr) + s.logger.Print("Starting Stats server", "addr", s.Config.Stats.ListenAddr) if err := start(ctx, s.StatsServer.ListenAndServe); !errors.Is(err, http.ErrServerClosed) { return err } return nil }) errg.Go(func() error { - logger.Print("Starting cron scheduler") + s.logger.Print("Starting cron scheduler") s.Cron.Start() return nil }) diff --git a/server/server_test.go b/server/server_test.go index a3986b2d3b985ad49ce9c91bbf555e5d00f89bed..a856f1f4c33aeec35607d0b19420378eb0432776 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -25,10 +25,11 @@ func setupServer(tb testing.TB) (*Server, *config.Config, string) { tb.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEY", authorizedKey(pub)) tb.Setenv("SOFT_SERVE_SSH_LISTEN_ADDR", sshPort) tb.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", test.RandomPort())) + ctx := context.TODO() cfg := config.DefaultConfig() + ctx = config.WithContext(ctx, cfg) tb.Log("configuring server") - ctx := context.TODO() - s, err := NewServer(ctx, cfg) + s, err := NewServer(ctx) if err != nil { tb.Fatal(err) } diff --git a/server/ssh/session_test.go b/server/ssh/session_test.go index a39d500d25823a1b07e496ea75f111076f209a17..995ab20fd122f060479562dba0139f83fe871902 100644 --- a/server/ssh/session_test.go +++ b/server/ssh/session_test.go @@ -59,7 +59,8 @@ func setup(tb testing.TB) (*gossh.Session, func() error) { }) ctx := context.TODO() cfg := config.DefaultConfig() - fb, err := sqlite.NewSqliteBackend(ctx, cfg) + ctx = config.WithContext(ctx, cfg) + fb, err := sqlite.NewSqliteBackend(ctx) if err != nil { log.Fatal(err) } diff --git a/server/ssh/ssh.go b/server/ssh/ssh.go index 419ab241d2e335f8b6c4ca8ae6330177c77dce4f..9c55aea185f4556de3ef88cd70f9d73cb4fd96b7 100644 --- a/server/ssh/ssh.go +++ b/server/ssh/ssh.go @@ -26,10 +26,6 @@ import ( gossh "golang.org/x/crypto/ssh" ) -var ( - logger = log.WithPrefix("server.ssh") -) - var ( publicKeyCounter = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "soft_serve", @@ -76,15 +72,22 @@ var ( // SSHServer is a SSH server that implements the git protocol. type SSHServer struct { - srv *ssh.Server - cfg *config.Config + srv *ssh.Server + cfg *config.Config + ctx context.Context + logger *log.Logger } // NewSSHServer returns a new SSHServer. -func NewSSHServer(cfg *config.Config) (*SSHServer, error) { +func NewSSHServer(ctx context.Context) (*SSHServer, error) { + cfg := config.FromContext(ctx) var err error - s := &SSHServer{cfg: cfg} - logger := logger.StandardLog(log.StandardLogOptions{ForceLevel: log.DebugLevel}) + s := &SSHServer{ + cfg: cfg, + ctx: ctx, + logger: log.FromContext(ctx).WithPrefix("ssh"), + } + logger := s.logger.StandardLog(log.StandardLogOptions{ForceLevel: log.DebugLevel}) mw := []wish.Middleware{ rm.MiddlewareWithLogger( logger, @@ -151,7 +154,7 @@ func (s *SSHServer) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) (allowed }(&allowed) ac := s.cfg.Backend.AccessLevelByPublicKey("", pk) - logger.Debugf("access level for %q: %s", ak, ac) + s.logger.Debugf("access level for %q: %s", ak, ac) allowed = ac >= backend.ReadOnlyAccess return } @@ -168,7 +171,7 @@ func (s *SSHServer) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.Keyboard // checked for access on a per repo basis for a ssh.Session public key. // Hooks.Push and Hooks.Fetch will be called on successful completion of // their commands. -func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware { +func (ss *SSHServer) Middleware(cfg *config.Config) wish.Middleware { return func(sh ssh.Handler) ssh.Handler { return func(s ssh.Session) { func() { @@ -196,7 +199,7 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware { "SOFT_SERVE_PUBLIC_KEY=" + ak, } - logger.Debug("git middleware", "cmd", gc, "access", access.String()) + ss.logger.Debug("git middleware", "cmd", gc, "access", access.String()) repoDir := filepath.Join(reposDir, repo) switch gc { case git.ReceivePackBin: diff --git a/server/stats/stats.go b/server/stats/stats.go index d0029d65aed0b1d0d68aa7e4d625a10838b4d512..b515f1b52a816fd111b40c7842573cc592c0e8fa 100644 --- a/server/stats/stats.go +++ b/server/stats/stats.go @@ -11,15 +11,18 @@ import ( // StatsServer is a server for collecting and reporting statistics. type StatsServer struct { + ctx context.Context cfg *config.Config server *http.Server } // NewStatsServer returns a new StatsServer. -func NewStatsServer(cfg *config.Config) (*StatsServer, error) { +func NewStatsServer(ctx context.Context) (*StatsServer, error) { + cfg := config.FromContext(ctx) mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) return &StatsServer{ + ctx: ctx, cfg: cfg, server: &http.Server{ Addr: cfg.Stats.ListenAddr, diff --git a/server/web/http.go b/server/web/http.go index 694f58807f0750ea75ff84ed2637c0f977ef00ae..c3e33e0f9960eacccccf6500cc5751e64cd9f0a5 100644 --- a/server/web/http.go +++ b/server/web/http.go @@ -24,10 +24,6 @@ import ( "goji.io/pattern" ) -var ( - logger = log.WithPrefix("server.web") -) - var ( gitHttpCounter = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "soft_serve", @@ -64,18 +60,17 @@ func (r *logWriter) WriteHeader(code int) { r.ResponseWriter.WriteHeader(code) } -func loggingMiddleware(next http.Handler) http.Handler { - logger := logger.WithPrefix("server.http") +func (s *HTTPServer) loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() writer := &logWriter{code: http.StatusOK, ResponseWriter: w} - logger.Debug("request", + s.logger.Debug("request", "method", r.Method, "uri", r.RequestURI, "addr", r.RemoteAddr) next.ServeHTTP(writer, r) elapsed := time.Since(start) - logger.Debug("response", + s.logger.Debug("response", "status", fmt.Sprintf("%d %s", writer.code, http.StatusText(writer.code)), "bytes", humanize.Bytes(uint64(writer.bytes)), "time", elapsed) @@ -84,15 +79,20 @@ func loggingMiddleware(next http.Handler) http.Handler { // HTTPServer is an http server. type HTTPServer struct { + ctx context.Context cfg *config.Config server *http.Server dirHandler http.Handler + logger *log.Logger } -func NewHTTPServer(cfg *config.Config) (*HTTPServer, error) { +func NewHTTPServer(ctx context.Context) (*HTTPServer, error) { + cfg := config.FromContext(ctx) mux := goji.NewMux() s := &HTTPServer{ + ctx: ctx, cfg: cfg, + logger: log.FromContext(ctx).WithPrefix("http"), dirHandler: http.FileServer(http.Dir(filepath.Join(cfg.DataPath, "repos"))), server: &http.Server{ Addr: cfg.HTTP.ListenAddr, @@ -104,7 +104,7 @@ func NewHTTPServer(cfg *config.Config) (*HTTPServer, error) { }, } - mux.Use(loggingMiddleware) + mux.Use(s.loggingMiddleware) for _, m := range []Matcher{ getInfoRefs, getHead, @@ -306,7 +306,7 @@ func (s *HTTPServer) handleGit(w http.ResponseWriter, r *http.Request) { repo := pat.Param(r, "repo") repo = utils.SanitizeRepo(repo) + ".git" if _, err := s.cfg.Backend.Repository(repo); err != nil { - logger.Debug("repository not found", "repo", repo, "err", err) + s.logger.Debug("repository not found", "repo", repo, "err", err) http.NotFound(w, r) return } diff --git a/ui/common/common.go b/ui/common/common.go index 78b4a992bb1ee46cb9f2edb62392e74633d8ebd3..ebcd6621289d812e28aac42c2230617372a56cad 100644 --- a/ui/common/common.go +++ b/ui/common/common.go @@ -3,6 +3,7 @@ package common import ( "context" + "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/config" "github.com/charmbracelet/soft-serve/ui/keymap" @@ -30,6 +31,7 @@ type Common struct { KeyMap *keymap.KeyMap Zone *zone.Manager Output *termenv.Output + Logger *log.Logger } // NewCommon returns a new Common struct. @@ -45,6 +47,7 @@ func NewCommon(ctx context.Context, out *termenv.Output, width, height int) Comm Styles: styles.DefaultStyles(), KeyMap: keymap.DefaultKeyMap(), Zone: zone.New(), + Logger: log.FromContext(ctx).WithPrefix("ui"), } } diff --git a/ui/pages/repo/log.go b/ui/pages/repo/log.go index 93c1aa5dd58e673f8863f567d8cb5bfcf112512a..e59e886f7c397e071a259cea362672aafb46b94b 100644 --- a/ui/pages/repo/log.go +++ b/ui/pages/repo/log.go @@ -393,7 +393,7 @@ func (l *Log) countCommitsCmd() tea.Msg { } count, err := r.CountCommits(l.ref) if err != nil { - logger.Debugf("ui: error counting commits: %v", err) + l.common.Logger.Debugf("ui: error counting commits: %v", err) return common.ErrorMsg(err) } return LogCountMsg(count) @@ -423,7 +423,7 @@ func (l *Log) updateCommitsCmd() tea.Msg { // CommitsByPage pages start at 1 cc, err := r.CommitsByPage(l.ref, page+1, limit) if err != nil { - logger.Debugf("ui: error loading commits: %v", err) + l.common.Logger.Debugf("ui: error loading commits: %v", err) return common.ErrorMsg(err) } for i, c := range cc { @@ -445,12 +445,12 @@ func (l *Log) selectCommitCmd(commit *git.Commit) tea.Cmd { func (l *Log) loadDiffCmd() tea.Msg { r, err := l.repo.Open() if err != nil { - logger.Debugf("ui: error loading diff repository: %v", err) + l.common.Logger.Debugf("ui: error loading diff repository: %v", err) return common.ErrorMsg(err) } diff, err := r.Diff(l.selectedCommit) if err != nil { - logger.Debugf("ui: error loading diff: %v", err) + l.common.Logger.Debugf("ui: error loading diff: %v", err) return common.ErrorMsg(err) } return LogDiffMsg(diff) diff --git a/ui/pages/repo/refs.go b/ui/pages/repo/refs.go index 90b05c22e6145218d6f1cd98e6b15b8832a3315d..c8a1bc5d726a7122fda694e729bd41947161b341 100644 --- a/ui/pages/repo/refs.go +++ b/ui/pages/repo/refs.go @@ -181,7 +181,7 @@ func (r *Refs) updateItemsCmd() tea.Msg { } refs, err := rr.References() if err != nil { - logger.Debugf("ui: error getting references: %v", err) + r.common.Logger.Debugf("ui: error getting references: %v", err) return common.ErrorMsg(err) } for _, ref := range refs { @@ -228,10 +228,8 @@ func UpdateRefCmd(repo backend.Repository) tea.Cmd { } ref, err := r.HEAD() if err != nil { - logger.Debugf("ui: error getting HEAD reference: %v", err) return common.ErrorMsg(err) } - logger.Debugf("HEAD: %s", ref.Name()) return RefMsg(ref) } } diff --git a/ui/pages/repo/repo.go b/ui/pages/repo/repo.go index 5e280b2b24242390147845bf6cbf054f4469d9fa..87f715e610d9ec46a5b833918c76646c659da926 100644 --- a/ui/pages/repo/repo.go +++ b/ui/pages/repo/repo.go @@ -8,7 +8,6 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/ui/common" @@ -17,10 +16,6 @@ import ( "github.com/charmbracelet/soft-serve/ui/components/tabs" ) -var ( - logger = log.WithPrefix("ui.repo") -) - type state int const ( @@ -92,6 +87,7 @@ func New(c common.Common) *Repo { for i, t := range []tab{readmeTab, filesTab, commitsTab, branchesTab, tagsTab} { ts[i] = t.String() } + c.Logger = c.Logger.WithPrefix("ui.repo") tb := tabs.New(c, ts) readme := NewReadme(c) log := NewLog(c) diff --git a/ui/pages/selection/selection.go b/ui/pages/selection/selection.go index 2c7957c6aacaae4eb380dbc3447cf95de6b21580..5860d9d8be78a6437d0accb61878e1a9204a7b61 100644 --- a/ui/pages/selection/selection.go +++ b/ui/pages/selection/selection.go @@ -8,7 +8,6 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/ui/common" "github.com/charmbracelet/soft-serve/ui/components/code" @@ -20,10 +19,6 @@ const ( defaultNoContent = "No readme found.\n\nCreate a `.soft-serve` repository and add a `README.md` file to display readme." ) -var ( - logger = log.WithPrefix("ui.selection") -) - type pane int const ( @@ -219,7 +214,7 @@ func (s *Selection) Init() tea.Cmd { if al >= backend.ReadOnlyAccess { item, err := NewItem(r, cfg) if err != nil { - logger.Debugf("ui: failed to create item for %s: %v", r.Name(), err) + s.common.Logger.Debugf("ui: failed to create item for %s: %v", r.Name(), err) continue } sortedItems = append(sortedItems, item) diff --git a/ui/ui.go b/ui/ui.go index 78b92b8434249be1791defa69f5c0f85a097c764..9a2553809a8529510d9d8eef0b884615fabe49d2 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -7,7 +7,6 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/log" "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/ui/common" "github.com/charmbracelet/soft-serve/ui/components/footer" @@ -17,10 +16,6 @@ import ( "github.com/charmbracelet/soft-serve/ui/pages/selection" ) -var ( - logger = log.WithPrefix("ui") -) - type page int const ( @@ -165,7 +160,7 @@ func (ui *UI) IsFiltering() bool { // Update implements tea.Model. func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - logger.Debugf("msg received: %T", msg) + ui.common.Logger.Debugf("msg received: %T", msg) cmds := make([]tea.Cmd, 0) switch msg := msg.(type) { case tea.WindowSizeMsg: @@ -295,7 +290,7 @@ func (ui *UI) openRepo(rn string) (backend.Repository, error) { } repos, err := cfg.Backend.Repositories() if err != nil { - logger.Debugf("ui: failed to list repos: %v", err) + ui.common.Logger.Debugf("ui: failed to list repos: %v", err) return nil, err } for _, r := range repos {