f388af994ec86ad17c6f69864f50e57683b7f65b
- Author
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Committer
- Ayman Bagabas <ayman.bagabas@gmail.com>
- Date
Message
Diff
This diff is truncated to protect this page.
1diff --git a/cmd/soft/hook.go b/cmd/soft/hook.go
2index fc78221722e4c0b5801cdc24fe30e9456786d325..c1f9d4233ab56846043584f052c1195bb235cffa 100644
3--- a/cmd/soft/hook.go
4+++ b/cmd/soft/hook.go
5@@ -11,6 +11,7 @@ import (
6 "path/filepath"
7 "strings"
8
9+ "github.com/charmbracelet/log"
10 "github.com/charmbracelet/soft-serve/server/backend"
11 "github.com/charmbracelet/soft-serve/server/backend/sqlite"
12 "github.com/charmbracelet/soft-serve/server/config"
13@@ -18,53 +19,57 @@ import (
14 "github.com/spf13/cobra"
15 )
16
17-var (
18- confixCtxKey = "config"
19- backendCtxKey = "backend"
20-)
21-
22 var (
23 configPath string
24
25+ logFileCtxKey = struct{}{}
26+
27 hookCmd = &cobra.Command{
28 Use: "hook",
29 Short: "Run git server hooks",
30 Long: "Handles Soft Serve git server hooks.",
31 Hidden: true,
32 PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
33+ ctx := cmd.Context()
34 cfg, err := config.ParseConfig(configPath)
35 if err != nil {
36 return fmt.Errorf("could not parse config: %w", err)
37 }
38
39- customHooksPath := filepath.Join(filepath.Dir(configPath), "hooks")
40- if _, err := os.Stat(customHooksPath); err != nil && os.IsNotExist(err) {
41- os.MkdirAll(customHooksPath, os.ModePerm)
42- // Generate update hook example without executable permissions
43- hookPath := filepath.Join(customHooksPath, "update.sample")
44- if err := os.WriteFile(hookPath, []byte(updateHookExample), 0744); err != nil {
45- return fmt.Errorf("failed to generate update hook example: %w", err)
46- }
47+ ctx = config.WithContext(ctx, cfg)
48+
49+ logPath := filepath.Join(cfg.DataPath, "log", "hooks.log")
50+ f, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
51+ if err != nil {
52+ return fmt.Errorf("opening file: %w", err)
53 }
54
55+ ctx = context.WithValue(ctx, logFileCtxKey, f)
56+ logger := log.FromContext(ctx)
57+ logger.SetOutput(f)
58+ ctx = log.WithContext(ctx, logger)
59+ cmd.SetContext(ctx)
60+
61 // Set up the backend
62 // TODO: support other backends
63- sb, err := sqlite.NewSqliteBackend(cmd.Context(), cfg)
64+ sb, err := sqlite.NewSqliteBackend(ctx)
65 if err != nil {
66 return fmt.Errorf("failed to create sqlite backend: %w", err)
67 }
68
69 cfg = cfg.WithBackend(sb)
70
71- cmd.SetContext(context.WithValue(cmd.Context(), confixCtxKey, cfg))
72- cmd.SetContext(context.WithValue(cmd.Context(), backendCtxKey, sb))
73-
74 return nil
75 },
76+ PersistentPostRunE: func(cmd *cobra.Command, _ []string) error {
77+ f := cmd.Context().Value(logFileCtxKey).(*os.File)
78+ return f.Close()
79+ },
80 }
81
82 hooksRunE = func(cmd *cobra.Command, args []string) error {
83- cfg := cmd.Context().Value(confixCtxKey).(*config.Config)
84+ ctx := cmd.Context()
85+ cfg := config.FromContext(ctx)
86 hks := cfg.Backend.(backend.Hooks)
87
88 // This is set in the server before invoking git-receive-pack/git-upload-pack
89@@ -119,7 +124,7 @@ var (
90 // Custom hooks
91 if stat, err := os.Stat(customHookPath); err == nil && !stat.IsDir() && stat.Mode()&0o111 != 0 {
92 // If the custom hook is executable, run it
93- if err := runCommand(cmd.Context(), &buf, stdout, stderr, customHookPath, args...); err != nil {
94+ if err := runCommand(ctx, &buf, stdout, stderr, customHookPath, args...); err != nil {
95 return fmt.Errorf("failed to run custom hook: %w", err)
96 }
97 }
98diff --git a/cmd/soft/migrate_config.go b/cmd/soft/migrate_config.go
99index a20cc9d41a51d71aaac9b4433150b52986892177..888c722aa44d82bd9e37bb9fd67510d685534ddc 100644
100--- a/cmd/soft/migrate_config.go
101+++ b/cmd/soft/migrate_config.go
102@@ -2,6 +2,7 @@ package main
103
104 import (
105 "encoding/json"
106+ "errors"
107 "fmt"
108 "io"
109 "os"
110@@ -27,15 +28,18 @@ var (
111 Short: "Migrate config to new format",
112 Hidden: true,
113 RunE: func(cmd *cobra.Command, _ []string) error {
114+ ctx := cmd.Context()
115+
116+ logger := log.FromContext(ctx)
117 // Disable logging timestamp
118- log.SetReportTimestamp(false)
119+ logger.SetReportTimestamp(false)
120
121 keyPath := os.Getenv("SOFT_SERVE_KEY_PATH")
122 reposPath := os.Getenv("SOFT_SERVE_REPO_PATH")
123 bindAddr := os.Getenv("SOFT_SERVE_BIND_ADDRESS")
124- ctx := cmd.Context()
125 cfg := config.DefaultConfig()
126- sb, err := sqlite.NewSqliteBackend(ctx, cfg)
127+ ctx = config.WithContext(ctx, cfg)
128+ sb, err := sqlite.NewSqliteBackend(ctx)
129 if err != nil {
130 return fmt.Errorf("failed to create sqlite backend: %w", err)
131 }
132@@ -43,13 +47,13 @@ var (
133 cfg = cfg.WithBackend(sb)
134
135 // Set SSH listen address
136- log.Info("Setting SSH listen address...")
137+ logger.Info("Setting SSH listen address...")
138 if bindAddr != "" {
139 cfg.SSH.ListenAddr = bindAddr
140 }
141
142 // Copy SSH host key
143- log.Info("Copying SSH host key...")
144+ logger.Info("Copying SSH host key...")
145 if keyPath != "" {
146 if err := os.MkdirAll(filepath.Join(cfg.DataPath, "ssh"), os.ModePerm); err != nil {
147 return fmt.Errorf("failed to create ssh directory: %w", err)
148@@ -60,14 +64,14 @@ var (
149 }
150
151 if err := copyFile(keyPath+".pub", filepath.Join(cfg.DataPath, "ssh", filepath.Base(keyPath))+".pub"); err != nil {
152- log.Errorf("failed to copy ssh key: %s", err)
153+ logger.Errorf("failed to copy ssh key: %s", err)
154 }
155
156 cfg.SSH.KeyPath = filepath.Join(cfg.DataPath, "ssh", filepath.Base(keyPath))
157 }
158
159 // Read config
160- log.Info("Reading config repository...")
161+ logger.Info("Reading config repository...")
162 r, err := git.Open(filepath.Join(reposPath, "config"))
163 if err != nil {
164 return fmt.Errorf("failed to open config repo: %w", err)
165@@ -119,7 +123,7 @@ var (
166 cfg.SSH.PublicURL = fmt.Sprintf("ssh://%s:%d", ocfg.Host, ocfg.Port)
167
168 // Set server settings
169- log.Info("Setting server settings...")
170+ logger.Info("Setting server settings...")
171 if cfg.Backend.SetAllowKeyless(ocfg.AllowKeyless) != nil {
172 fmt.Fprintf(os.Stderr, "failed to set allow keyless\n")
173 }
174@@ -132,7 +136,7 @@ var (
175
176 // Copy repos
177 if reposPath != "" {
178- log.Info("Copying repos...")
179+ logger.Info("Copying repos...")
180 if err := os.MkdirAll(filepath.Join(cfg.DataPath, "repos"), os.ModePerm); err != nil {
181 return fmt.Errorf("failed to create repos directory: %w", err)
182 }
183@@ -151,7 +155,7 @@ var (
184 continue
185 }
186
187- log.Infof(" Copying repo %s", dir.Name())
188+ logger.Infof(" Copying repo %s", dir.Name())
189 src := filepath.Join(reposPath, utils.SanitizeRepo(dir.Name()))
190 dst := filepath.Join(cfg.DataPath, "repos", utils.SanitizeRepo(dir.Name())) + ".git"
191 if err := os.MkdirAll(dst, os.ModePerm); err != nil {
192@@ -168,7 +172,7 @@ var (
193 }
194
195 if hasReadme {
196- log.Infof(" Copying readme from \"config\" to \".soft-serve\"")
197+ logger.Infof(" Copying readme from \"config\" to \".soft-serve\"")
198
199 // Switch to main branch
200 bcmd := git.NewCommand("branch", "-M", "main")
201@@ -236,7 +240,7 @@ var (
202diff --git a/cmd/soft/root.go b/cmd/soft/root.go
203index 44b6e8ab84f9631fa0fcfd069a90e17b60183f86..b47e9cdd04bf8393c4b537e1599d0ed2b47e3dcc 100644
204--- a/cmd/soft/root.go
205+++ b/cmd/soft/root.go
206@@ -4,9 +4,11 @@ import (
207 "context"
208 "os"
209 "runtime/debug"
210+ "strconv"
211+ "strings"
212+ "time"
213
214 "github.com/charmbracelet/log"
215- _ "github.com/charmbracelet/soft-serve/log"
216 "github.com/spf13/cobra"
217 )
218
219@@ -51,15 +53,24 @@ func init() {
220 }
221
222 func main() {
223+ ctx := context.Background()
224 logger := log.NewWithOptions(os.Stderr, log.Options{
225 ReportTimestamp: true,
226- TimeFormat: "2006-01-02",
227+ TimeFormat: time.DateOnly,
228 })
229- if os.Getenv("SOFT_SERVE_DEBUG") == "true" {
230+ if debug, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_DEBUG")); debug {
231 logger.SetLevel(log.DebugLevel)
232 }
233
234- ctx := context.Background()
235+ switch strings.ToLower(os.Getenv("SOFT_SERVE_LOG_FORMAT")) {
236+ case "json":
237+ logger.SetFormatter(log.JSONFormatter)
238+ case "logfmt":
239+ logger.SetFormatter(log.LogfmtFormatter)
240+ case "text":
241+ logger.SetFormatter(log.TextFormatter)
242+ }
243+
244 ctx = log.WithContext(ctx, logger)
245 if err := rootCmd.ExecuteContext(ctx); err != nil {
246 os.Exit(1)
247diff --git a/cmd/soft/serve.go b/cmd/soft/serve.go
248index 55e0d2aca2c24c381d795c754149ad269edd5f83..b9a9b2937a1be656acbe38b32325161cee61c572 100644
249--- a/cmd/soft/serve.go
250+++ b/cmd/soft/serve.go
251@@ -5,10 +5,10 @@ import (
252 "fmt"
253 "os"
254 "os/signal"
255+ "path/filepath"
256 "syscall"
257 "time"
258
259- _ "github.com/charmbracelet/soft-serve/log"
260 "github.com/charmbracelet/soft-serve/server"
261 "github.com/charmbracelet/soft-serve/server/config"
262 "github.com/spf13/cobra"
263@@ -20,10 +20,31 @@ var (
264 Short: "Start the server",
265 Long: "Start the server",
266 Args: cobra.NoArgs,
267- RunE: func(cmd *cobra.Command, args []string) error {
268+ RunE: func(cmd *cobra.Command, _ []string) error {
269 ctx := cmd.Context()
270 cfg := config.DefaultConfig()
271- s, err := server.NewServer(ctx, cfg)
272+ ctx = config.WithContext(ctx, cfg)
273+ cmd.SetContext(ctx)
274+
275+ // Create custom hooks directory if it doesn't exist
276+ customHooksPath := filepath.Join(cfg.DataPath, "hooks")
277+ if _, err := os.Stat(customHooksPath); err != nil && os.IsNotExist(err) {
278+ os.MkdirAll(customHooksPath, os.ModePerm) // nolint: errcheck
279+ // Generate update hook example without executable permissions
280+ hookPath := filepath.Join(customHooksPath, "update.sample")
281+ // nolint: gosec
282+ if err := os.WriteFile(hookPath, []byte(updateHookExample), 0744); err != nil {
283+ return fmt.Errorf("failed to generate update hook example: %w", err)
284+ }
285+ }
286+
287+ // Create log directory if it doesn't exist
288+ logPath := filepath.Join(cfg.DataPath, "log")
289+ if _, err := os.Stat(logPath); err != nil && os.IsNotExist(err) {
290+ os.MkdirAll(logPath, os.ModePerm) // nolint: errcheck
291+ }
292+
293+ s, err := server.NewServer(ctx)
294 if err != nil {
295 return fmt.Errorf("start server: %w", err)
296 }
297diff --git a/examples/setuid/main.go b/examples/setuid/main.go
298index e9cc1f751a431c961e36d050fa62f809479bd8c1..4b6c4770d5d6d2a759b1dd291bc67faf655faa4d 100644
299--- a/examples/setuid/main.go
300+++ b/examples/setuid/main.go
301@@ -46,8 +46,9 @@ func main() {
302 }
303 ctx := context.Background()
304 cfg := config.DefaultConfig()
305+ ctx = config.WithContext(ctx, cfg)
306 cfg.SSH.ListenAddr = fmt.Sprintf(":%d", *port)
307- s, err := server.NewServer(ctx, cfg)
308+ s, err := server.NewServer(ctx)
309 if err != nil {
310 log.Fatal(err)
311 }
312diff --git a/log/log.go b/log/log.go
313deleted file mode 100644
314index d2df5bbb46ab00b34f37bceb8c52a55f3a1e4269..0000000000000000000000000000000000000000
315--- a/log/log.go
316+++ /dev/null
317@@ -1,14 +0,0 @@
318-// Package log initializes the logger for Soft Serve modules.
319-package log
320-
321-import (
322- "os"
323-
324- "github.com/charmbracelet/log"
325-)
326-
327-func init() {
328- if os.Getenv("SOFT_SERVE_DEBUG") == "true" {
329- log.SetLevel(log.DebugLevel)
330- }
331-}
332diff --git a/server/backend/sqlite/db.go b/server/backend/sqlite/db.go
333index 839d9105db96bc4a624cca5c1dca357758fd5a8a..fac0394f4309af33f7c26cc84dcb94904013f512 100644
334--- a/server/backend/sqlite/db.go
335+++ b/server/backend/sqlite/db.go
336@@ -66,7 +66,7 @@ func (d *SqliteBackend) init() error {
337 for _, k := range d.cfg.InitialAdminKeys {
338 pk, _, err := backend.ParseAuthorizedKey(k)
339 if err != nil {
340- logger.Error("error parsing initial admin key, skipping", "key", k, "err", err)
341+ d.logger.Error("error parsing initial admin key, skipping", "key", k, "err", err)
342 continue
343 }
344
345diff --git a/server/backend/sqlite/hooks.go b/server/backend/sqlite/hooks.go
346index cf82c2c009a3b72415d0ba09b72152521eabe823..0f9d61658d0b061bc37c14e807c614444f5cc6c7 100644
347--- a/server/backend/sqlite/hooks.go
348+++ b/server/backend/sqlite/hooks.go
349@@ -4,7 +4,6 @@ import (
350 "io"
351 "sync"
352
353- "github.com/charmbracelet/log"
354 "github.com/charmbracelet/soft-serve/server/backend"
355 )
356
357@@ -12,28 +11,28 @@ import (
358 //
359 // It implements Hooks.
360 func (d *SqliteBackend) PostReceive(stdout io.Writer, stderr io.Writer, repo string, args []backend.HookArg) {
361- log.WithPrefix("backend.sqlite.hooks").Debug("post-receive hook called", "repo", repo, "args", args)
362+ d.logger.Debug("post-receive hook called", "repo", repo, "args", args)
363 }
364
365 // PreReceive is called by the git pre-receive hook.
366 //
367 // It implements Hooks.
368 func (d *SqliteBackend) PreReceive(stdout io.Writer, stderr io.Writer, repo string, args []backend.HookArg) {
369- log.WithPrefix("backend.sqlite.hooks").Debug("pre-receive hook called", "repo", repo, "args", args)
370+ d.logger.Debug("pre-receive hook called", "repo", repo, "args", args)
371 }
372
373 // Update is called by the git update hook.
374 //
375 // It implements Hooks.
376 func (d *SqliteBackend) Update(stdout io.Writer, stderr io.Writer, repo string, arg backend.HookArg) {
377- log.WithPrefix("backend.sqlite.hooks").Debug("update hook called", "repo", repo, "arg", arg)
378+ d.logger.Debug("update hook called", "repo", repo, "arg", arg)
379 }
380
381 // PostUpdate is called by the git post-update hook.
382 //
383 // It implements Hooks.
384 func (d *SqliteBackend) PostUpdate(stdout io.Writer, stderr io.Writer, repo string, args ...string) {
385- log.WithPrefix("backend.sqlite.hooks").Debug("post-update hook called", "repo", repo, "args", args)
386+ d.logger.Debug("post-update hook called", "repo", repo, "args", args)
387
388 var wg sync.WaitGroup
389
390@@ -44,18 +43,18 @@ func (d *SqliteBackend) PostUpdate(stdout io.Writer, stderr io.Writer, repo stri
391
392 rr, err := d.Repository(repo)
393 if err != nil {
394- log.WithPrefix("backend.sqlite.hooks").Error("error getting repository", "repo", repo, "err", err)
395+ d.logger.Error("error getting repository", "repo", repo, "err", err)
396 return
397 }
398
399 r, err := rr.Open()
400 if err != nil {
401- log.WithPrefix("backend.sqlite.hooks").Error("error opening repository", "repo", repo, "err", err)
402+ d.logger.Error("error opening repository", "repo", repo, "err", err)
403 return
404 }
405
406 if err := r.UpdateServerInfo(); err != nil {
407- log.WithPrefix("backend.sqlite.hooks").Error("error updating server-info", "repo", repo, "err", err)
408+ d.logger.Error("error updating server-info", "repo", repo, "err", err)
409 return
410 }
411 }()
412diff --git a/server/backend/sqlite/sqlite.go b/server/backend/sqlite/sqlite.go
413index 0720a09710bed98a2a3062482740ae3ddaafa050..8b0a0c98a359e9282e614cabc66dceb76e9a9f6f 100644
414--- a/server/backend/sqlite/sqlite.go
415+++ b/server/backend/sqlite/sqlite.go
416@@ -17,17 +17,14 @@ import (
417 _ "modernc.org/sqlite"
418 )
419
420-var (
421- logger = log.WithPrefix("backend.sqlite")
422-)
423-
424 // SqliteBackend is a backend that uses a SQLite database as a Soft Serve
425 // backend.
426 type SqliteBackend struct {
427- cfg *config.Config
428- ctx context.Context
429- dp string
430- db *sqlx.DB
431+ cfg *config.Config
432+ ctx context.Context
433+ dp string
434+ db *sqlx.DB
435+ logger *log.Logger
436 }
437
438 var _ backend.Backend = (*SqliteBackend)(nil)
439@@ -37,7 +34,8 @@ func (d *SqliteBackend) reposPath() string {
440 }
441
442 // NewSqliteBackend creates a new SqliteBackend.
443-func NewSqliteBackend(ctx context.Context, cfg *config.Config) (*SqliteBackend, error) {
444+func NewSqliteBackend(ctx context.Context) (*SqliteBackend, error) {
445+ cfg := config.FromContext(ctx)
446 dataPath := cfg.DataPath
447 if err := os.MkdirAll(dataPath, os.ModePerm); err != nil {
448 return nil, err
449@@ -50,10 +48,11 @@ func NewSqliteBackend(ctx context.Context, cfg *config.Config) (*SqliteBackend,
450 }
451
452 d := &SqliteBackend{
453- cfg: cfg,
454- ctx: ctx,
455- dp: dataPath,
456- db: db,
457+ cfg: cfg,
458+ ctx: ctx,
459+ dp: dataPath,
460+ db: db,
461+ logger: log.FromContext(ctx).WithPrefix("sqlite"),
462 }
463
464 if err := d.init(); err != nil {
465@@ -137,13 +136,13 @@ func (d *SqliteBackend) CreateRepository(name string, opts backend.RepositoryOpt
466
467 rr, err := git.Init(rp, true)
468 if err != nil {
469- logger.Debug("failed to create repository", "err", err)
470+ d.logger.Debug("failed to create repository", "err", err)
471 cleanup() // nolint: errcheck
472 return nil, err
473 }
474
475 if err := rr.UpdateServerInfo(); err != nil {
476- logger.Debug("failed to update server info", "err", err)
477+ d.logger.Debug("failed to update server info", "err", err)
478 cleanup() // nolint: errcheck
479 return nil, err
480 }
481@@ -154,7 +153,7 @@ func (d *SqliteBackend) CreateRepository(name string, opts backend.RepositoryOpt
482 name, opts.ProjectName, opts.Description, opts.Private, opts.Mirror, opts.Hidden)
483 return err
484 }); err != nil {
485- logger.Debug("failed to create repository in database", "err", err)
486+ d.logger.Debug("failed to create repository in database", "err", err)
487 return nil, wrapDbErr(err)
488 }
489
490@@ -192,7 +191,7 @@ func (d *SqliteBackend) ImportRepository(name string, remote string, opts backen
491 }
492
493 if err := git.Clone(remote, rp, copts); err != nil {
494- logger.Error("failed to clone repository", "err", err, "mirror", opts.Mirror, "remote", remote, "path", rp)
495+ d.logger.Error("failed to clone repository", "err", err, "mirror", opts.Mirror, "remote", remote, "path", rp)
496 return nil, err
497 }
498
499@@ -311,7 +310,7 @@ func (d *SqliteBackend) Repository(repo string) (backend.Repository, error) {
500 }
501
502 if count == 0 {
503- logger.Warn("repository exists but not found in database", "repo", repo)
504+ d.logger.Warn("repository exists but not found in database", "repo", repo)
505 return nil, ErrRepoNotExist
506 }
507
508diff --git a/server/backend/sqlite/user.go b/server/backend/sqlite/user.go
509index 456581d4d74c43a507d33194089db8d0611c98dd..7306c5cfa4e7d6f57ffd58258960eb15eadb8b55 100644
510--- a/server/backend/sqlite/user.go
511+++ b/server/backend/sqlite/user.go
512@@ -180,7 +180,7 @@ func (d *SqliteBackend) CreateUser(username string, opts backend.UserOptions) (b
513 if len(opts.PublicKeys) > 0 {
514 userID, err := r.LastInsertId()
515 if err != nil {
516- logger.Error("error getting last insert id")
517+ d.logger.Error("error getting last insert id")
518 return err
519 }
520
521diff --git a/server/config/config.go b/server/config/config.go
522index 7bd696e0e0d18220f3726dea9d781635b6f24fec..2c68bda24328db19d129749abe0166b954ab469d 100644
523--- a/server/config/config.go
524+++ b/server/config/config.go
525@@ -1,6 +1,7 @@
526 package config
527
528 import (
529+ "context"
530 "errors"
531 "fmt"
532 "os"
533@@ -88,6 +89,10 @@ type Config struct {
534 // Stats is the configuration for the stats server.
535 Stats StatsConfig `envPrefix:"STATS_" yaml:"stats"`
536
537+ // LogFormat is the format of the logs.
538+ // Valid values are "json", "logfmt", and "text".
539+ LogFormat string `env:"LOG_FORMAT" yaml:"log_format"`
540+
541 // InitialAdminKeys is a list of public keys that will be added to the list of admins.
542 InitialAdminKeys []string `env:"INITIAL_ADMIN_KEYS" envSeparator:"\n" yaml:"initial_admin_keys"`
543
544@@ -101,8 +106,9 @@ type Config struct {
545 func parseConfig(path string) (*Config, error) {
546 dataPath := filepath.Dir(path)
547 cfg := &Config{
548- Name: "Soft Serve",
549- DataPath: dataPath,
550+ Name: "Soft Serve",
551+ LogFormat: "text",
552+ DataPath: dataPath,
553 SSH: SSHConfig{
554 ListenAddr: ":23231",
555 PublicURL: "ssh://localhost:23231",
556@@ -273,3 +279,21 @@ func parseAuthKeys(aks []string) []ssh.PublicKey {
557 func (c *Config) AdminKeys() []ssh.PublicKey {
558 return parseAuthKeys(c.InitialAdminKeys)
559 }
560+
561+var (
562+ configCtxKey = struct{ string }{"config"}
563+)
564+
565+// WithContext returns a new context with the configuration attached.
566+func WithContext(ctx context.Context, cfg *Config) context.Context {
567+ return context.WithValue(ctx, configCtxKey, cfg)
568+}
569+
570+// FromContext returns the configuration from the context.
571+func FromContext(ctx context.Context) *Config {
572+ if c, ok := ctx.Value(configCtxKey).(*Config); ok {
573+ return c
574+ }
575+
576+ return DefaultConfig()
577+}
578diff --git a/server/config/file.go b/server/config/file.go
579index 1869dd87fcfd72aca7c71fb50cf6a19e49231f9e..11e239fb094d30b5e06591c1f6a8603a29b331b6 100644
580--- a/server/config/file.go
581+++ b/server/config/file.go
582@@ -12,6 +12,9 @@ var (
583 # This is the name that will be displayed in the UI.
584 name: "{{ .Name }}"
585
586+# Log format to use. Valid values are "json", "logfmt", and "text".
587+log_format: "{{ .LogFormat }}"
588+
589 # The SSH server configuration.
590 ssh:
591 # The address on which the SSH server will listen.
592diff --git a/server/cron/cron.go b/server/cron/cron.go
593index 27e052dee20e8aeaa4dc77d2530a4f4ab87ef02d..af4baf0ad5f83d8398e403ffe15c124833b1dbda 100644
594--- a/server/cron/cron.go
595+++ b/server/cron/cron.go
596@@ -39,7 +39,7 @@ func (l cronLogger) Error(err error, msg string, keysAndValues ...interface{}) {
597
598 // NewCronScheduler returns a new Cron.
599 func NewCronScheduler(ctx context.Context) *CronScheduler {
600- logger := cronLogger{log.FromContext(ctx).WithPrefix("server.cron")}
601+ logger := cronLogger{log.FromContext(ctx).WithPrefix("cron")}
602 return &CronScheduler{
603 Cron: cron.New(cron.WithLogger(logger)),
604 }
605diff --git a/server/daemon/daemon.go b/server/daemon/daemon.go
606index c3e08381502b6571fb4d3595627790680d053965..cf71ae6730ac445fa8122160c86c401edd79cba7 100644
607--- a/server/daemon/daemon.go
608+++ b/server/daemon/daemon.go
609@@ -19,10 +19,6 @@ import (
610 "github.com/prometheus/client_golang/prometheus/promauto"
611 )
612
613-var (
614- logger = log.WithPrefix("server.daemon")
615-)
616-
617 var (
618 uploadPackGitCounter = promauto.NewCounterVec(prometheus.CounterOpts{
619 Namespace: "soft_serve",
620@@ -81,6 +77,7 @@ func (m *connections) CloseAll() {
621
622 // GitDaemon represents a Git daemon.
623 type GitDaemon struct {
624+ ctx context.Context
625 listener net.Listener
626 addr string
627 finished chan struct{}
628@@ -88,16 +85,20 @@ type GitDaemon struct {
629 cfg *config.Config
630 wg sync.WaitGroup
631 once sync.Once
632+ logger *log.Logger
633 }
634
635 // NewDaemon returns a new Git daemon.
636-func NewGitDaemon(cfg *config.Config) (*GitDaemon, error) {
637+func NewGitDaemon(ctx context.Context) (*GitDaemon, error) {
638+ cfg := config.FromContext(ctx)
639 addr := cfg.Git.ListenAddr
640 d := &GitDaemon{
641+ ctx: ctx,
642 addr: addr,
643 finished: make(chan struct{}, 1),
644 cfg: cfg,
645 conns: connections{m: make(map[net.Conn]struct{})},
646+ logger: log.FromContext(ctx).WithPrefix("gitdaemon"),
647 }
648 listener, err := net.Listen("tcp", d.addr)
649 if err != nil {
650@@ -122,7 +123,7 @@ func (d *GitDaemon) Start() error {
651 case <-d.finished:
652 return ErrServerClosed
653 default:
654- logger.Debugf("git: error accepting connection: %v", err)
655+ d.logger.Debugf("git: error accepting connection: %v", err)
656 }
657 if ne, ok := err.(net.Error); ok && ne.Temporary() {
658 if tempDelay == 0 {
659@@ -141,8 +142,8 @@ func (d *GitDaemon) Start() error {
660
661 // Close connection if there are too many open connections.
662 if d.conns.Size()+1 >= d.cfg.Git.MaxConnections {
663- logger.Debugf("git: max connections reached, closing %s", conn.RemoteAddr())
664- fatal(conn, git.ErrMaxConnections)
665+ d.logger.Debugf("git: max connections reached, closing %s", conn.RemoteAddr())
666+ d.fatal(conn, git.ErrMaxConnections)
667 continue
668 }
669
670@@ -154,10 +155,10 @@ func (d *GitDaemon) Start() error {
671 }
672 }
673
674-func fatal(c net.Conn, err error) {
675+func (d *GitDaemon) fatal(c net.Conn, err error) {
676 git.WritePktline(c, err)
677 if err := c.Close(); err != nil {
678- logger.Debugf("git: error closing connection: %v", err)
679+ d.logger.Debugf("git: error closing connection: %v", err)
680 }
681 }
682
683@@ -185,10 +186,10 @@ func (d *GitDaemon) handleClient(conn net.Conn) {
684 if !s.Scan() {
685 if err := s.Err(); err != nil {
686 if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
687- fatal(c, git.ErrTimeout)
688+ d.fatal(c, git.ErrTimeout)
689 } else {
690- logger.Debugf("git: error scanning pktline: %v", err)
691- fatal(c, git.ErrSystemMalfunction)
692+ d.logger.Debugf("git: error scanning pktline: %v", err)
693+ d.fatal(c, git.ErrSystemMalfunction)
694 }
695 }
696 return
697@@ -199,14 +200,14 @@ func (d *GitDaemon) handleClient(conn net.Conn) {
698 select {
699 case <-ctx.Done():
700 if err := ctx.Err(); err != nil {
701- logger.Debugf("git: connection context error: %v", err)
702+ d.logger.Debugf("git: connection context error: %v", err)
703 }
704 return
705 case <-readc:
706 line := s.Bytes()
707 split := bytes.SplitN(line, []byte{' '}, 2)
708 if len(split) != 2 {
709diff --git a/server/daemon/daemon_test.go b/server/daemon/daemon_test.go
710index e728af6e3597804ec568ce22aa5385a7d370c51d..b06decbf4724ad3ab3a62e6632bddbf32a0078bd 100644
711--- a/server/daemon/daemon_test.go
712+++ b/server/daemon/daemon_test.go
713@@ -32,13 +32,14 @@ func TestMain(m *testing.M) {
714 os.Setenv("SOFT_SERVE_GIT_MAX_TIMEOUT", "100")
715 os.Setenv("SOFT_SERVE_GIT_IDLE_TIMEOUT", "1")
716 os.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", test.RandomPort()))
717+ ctx := context.TODO()
718 cfg := config.DefaultConfig()
719- d, err := NewGitDaemon(cfg)
720+ ctx = config.WithContext(ctx, cfg)
721+ d, err := NewGitDaemon(ctx)
722 if err != nil {
723 log.Fatal(err)
724 }
725- ctx := context.TODO()
726- fb, err := sqlite.NewSqliteBackend(ctx, cfg)
727+ fb, err := sqlite.NewSqliteBackend(ctx)
728 if err != nil {
729 log.Fatal(err)
730 }
731diff --git a/server/git/git.go b/server/git/git.go
732index 9a8b5ed9d401616151e7a47ced47a5ad9ff40dbc..5a49b3dc41f7e71b488ec3f511d1d665aa2a3a29 100644
733--- a/server/git/git.go
734+++ b/server/git/git.go
735@@ -12,6 +12,7 @@ import (
736
737 "github.com/charmbracelet/log"
738 "github.com/charmbracelet/soft-serve/git"
739+ "github.com/charmbracelet/soft-serve/server/config"
740 "github.com/go-git/go-git/v5/plumbing/format/pktline"
741 "golang.org/x/sync/errgroup"
742 )
743@@ -78,12 +79,16 @@ func ReceivePack(ctx context.Context, in io.Reader, out io.Writer, er io.Writer,
744
745 // RunGit runs a git command in the given repo.
746 func RunGit(ctx context.Context, in io.Reader, out io.Writer, er io.Writer, dir string, envs []string, args ...string) error {
747- logger := log.WithPrefix("server.git")
748+ cfg := config.FromContext(ctx)
749+ logger := log.FromContext(ctx).WithPrefix("rungit")
750 c := exec.CommandContext(ctx, "git", args...)
751 c.Dir = dir
752 c.Env = append(c.Env, envs...)
753- c.Env = append(c.Env, "SOFT_SERVE_DEBUG="+os.Getenv("SOFT_SERVE_DEBUG"))
754 c.Env = append(c.Env, "PATH="+os.Getenv("PATH"))
755+ c.Env = append(c.Env, "SOFT_SERVE_DEBUG="+os.Getenv("SOFT_SERVE_DEBUG"))
756+ if cfg != nil {
757+ c.Env = append(c.Env, "SOFT_SERVE_LOG_FORMAT="+cfg.LogFormat)
758+ }
759
760 stdin, err := c.StdinPipe()
761 if err != nil {
762diff --git a/server/jobs.go b/server/jobs.go
763index 5f239be583961649353568d5048042e497f8bfb6..06656d1ccec5b53b6868fb613168b170802962db 100644
764--- a/server/jobs.go
765+++ b/server/jobs.go
766@@ -5,7 +5,6 @@ import (
767 "path/filepath"
768
769 "github.com/charmbracelet/soft-serve/git"
770- "github.com/charmbracelet/soft-serve/server/config"
771 )
772
773 var (
774@@ -15,9 +14,10 @@ var (
775 )
776
777 // mirrorJob runs the (pull) mirror job task.
778-func mirrorJob(cfg *config.Config) func() {
779+func (s *Server) mirrorJob() func() {
780+ cfg := s.Config
781 b := cfg.Backend
782- logger := logger.WithPrefix("server.mirrorJob")
783+ logger := s.logger
784 return func() {
785 repos, err := b.Repositories()
786 if err != nil {
787diff --git a/server/server.go b/server/server.go
788index 182b20f6662cb49c3a8655f84b5b06d806f047cd..012ea05192cf94ad8bc5f120eaf3c041a40ca62d 100644
789--- a/server/server.go
790+++ b/server/server.go
791@@ -20,10 +20,6 @@ import (
792 "golang.org/x/sync/errgroup"
793 )
794
795-var (
796- logger = log.WithPrefix("server")
797-)
798-
799 // Server is the Soft Serve server.
800 type Server struct {
801 SSHServer *sshsrv.SSHServer
802@@ -33,7 +29,9 @@ type Server struct {
803 Cron *cron.CronScheduler
804 Config *config.Config
805 Backend backend.Backend
806- ctx context.Context
807+
808+ logger *log.Logger
809+ ctx context.Context
810 }
811
812 // NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
813@@ -41,10 +39,12 @@ type Server struct {
814 // key can be provided with authKey. If authKey is provided, access will be
815 // restricted to that key. If authKey is not provided, the server will be
816 // publicly writable until configured otherwise by cloning the `config` repo.
817-func NewServer(ctx context.Context, cfg *config.Config) (*Server, error) {
818+func NewServer(ctx context.Context) (*Server, error) {
819+ cfg := config.FromContext(ctx)
820+
821 var err error
822 if cfg.Backend == nil {
823- sb, err := sqlite.NewSqliteBackend(ctx, cfg)
824+ sb, err := sqlite.NewSqliteBackend(ctx)
825 if err != nil {
826 return nil, fmt.Errorf("create backend: %w", err)
827 }
828@@ -56,28 +56,29 @@ func NewServer(ctx context.Context, cfg *config.Config) (*Server, error) {
829 Cron: cron.NewCronScheduler(ctx),
830 Config: cfg,
831 Backend: cfg.Backend,
832+ logger: log.FromContext(ctx).WithPrefix("server"),
833 ctx: ctx,
834 }
835
836 // Add cron jobs.
837- srv.Cron.AddFunc(jobSpecs["mirror"], mirrorJob(cfg))
838+ srv.Cron.AddFunc(jobSpecs["mirror"], srv.mirrorJob())
839
840- srv.SSHServer, err = sshsrv.NewSSHServer(cfg)
841+ srv.SSHServer, err = sshsrv.NewSSHServer(ctx)
842 if err != nil {
843 return nil, fmt.Errorf("create ssh server: %w", err)
844 }
845
846- srv.GitDaemon, err = daemon.NewGitDaemon(cfg)
847+ srv.GitDaemon, err = daemon.NewGitDaemon(ctx)
848 if err != nil {
849 return nil, fmt.Errorf("create git daemon: %w", err)
850 }
851
852- srv.HTTPServer, err = web.NewHTTPServer(cfg)
853+ srv.HTTPServer, err = web.NewHTTPServer(ctx)
854 if err != nil {
855 return nil, fmt.Errorf("create http server: %w", err)
856 }
857
858- srv.StatsServer, err = stats.NewStatsServer(cfg)
859+ srv.StatsServer, err = stats.NewStatsServer(ctx)
860 if err != nil {
861 return nil, fmt.Errorf("create stats server: %w", err)
862 }
863@@ -101,38 +102,37 @@ func start(ctx context.Context, fn func() error) error {
864
865 // Start starts the SSH server.
866 func (s *Server) Start() error {
867- logger := log.FromContext(s.ctx).WithPrefix("server")
868 errg, ctx := errgroup.WithContext(s.ctx)
869 errg.Go(func() error {
870- logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
871+ s.logger.Print("Starting Git daemon", "addr", s.Config.Git.ListenAddr)
872 if err := start(ctx, s.GitDaemon.Start); !errors.Is(err, daemon.ErrServerClosed) {
873 return err
874 }
875 return nil
876 })
877 errg.Go(func() error {
878- logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
879+ s.logger.Print("Starting HTTP server", "addr", s.Config.HTTP.ListenAddr)
880 if err := start(ctx, s.HTTPServer.ListenAndServe); !errors.Is(err, http.ErrServerClosed) {
881 return err
882 }
883 return nil
884 })
885 errg.Go(func() error {
886- logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
887+ s.logger.Print("Starting SSH server", "addr", s.Config.SSH.ListenAddr)
888 if err := start(ctx, s.SSHServer.ListenAndServe); !errors.Is(err, ssh.ErrServerClosed) {
889 return err
890 }
891diff --git a/server/server_test.go b/server/server_test.go
892index a3986b2d3b985ad49ce9c91bbf555e5d00f89bed..a856f1f4c33aeec35607d0b19420378eb0432776 100644
893--- a/server/server_test.go
894+++ b/server/server_test.go
895@@ -25,10 +25,11 @@ func setupServer(tb testing.TB) (*Server, *config.Config, string) {
896 tb.Setenv("SOFT_SERVE_INITIAL_ADMIN_KEY", authorizedKey(pub))
897 tb.Setenv("SOFT_SERVE_SSH_LISTEN_ADDR", sshPort)
898 tb.Setenv("SOFT_SERVE_GIT_LISTEN_ADDR", fmt.Sprintf(":%d", test.RandomPort()))
899+ ctx := context.TODO()
900 cfg := config.DefaultConfig()
901+ ctx = config.WithContext(ctx, cfg)
902 tb.Log("configuring server")
903- ctx := context.TODO()
904- s, err := NewServer(ctx, cfg)
905+ s, err := NewServer(ctx)
906 if err != nil {
907 tb.Fatal(err)
908 }
909diff --git a/server/ssh/session_test.go b/server/ssh/session_test.go
910index a39d500d25823a1b07e496ea75f111076f209a17..995ab20fd122f060479562dba0139f83fe871902 100644
911--- a/server/ssh/session_test.go
912+++ b/server/ssh/session_test.go
913@@ -59,7 +59,8 @@ func setup(tb testing.TB) (*gossh.Session, func() error) {
914 })
915 ctx := context.TODO()
916 cfg := config.DefaultConfig()
917- fb, err := sqlite.NewSqliteBackend(ctx, cfg)
918+ ctx = config.WithContext(ctx, cfg)
919+ fb, err := sqlite.NewSqliteBackend(ctx)
920 if err != nil {
921 log.Fatal(err)
922 }
923diff --git a/server/ssh/ssh.go b/server/ssh/ssh.go
924index 419ab241d2e335f8b6c4ca8ae6330177c77dce4f..9c55aea185f4556de3ef88cd70f9d73cb4fd96b7 100644
925--- a/server/ssh/ssh.go
926+++ b/server/ssh/ssh.go
927@@ -26,10 +26,6 @@ import (
928 gossh "golang.org/x/crypto/ssh"
929 )
930
931-var (
932- logger = log.WithPrefix("server.ssh")
933-)
934-
935 var (
936 publicKeyCounter = promauto.NewCounterVec(prometheus.CounterOpts{
937 Namespace: "soft_serve",
938@@ -76,15 +72,22 @@ var (
939
940 // SSHServer is a SSH server that implements the git protocol.
941 type SSHServer struct {
942- srv *ssh.Server
943- cfg *config.Config
944+ srv *ssh.Server
945+ cfg *config.Config
946+ ctx context.Context
947+ logger *log.Logger
948 }
949
950 // NewSSHServer returns a new SSHServer.
951-func NewSSHServer(cfg *config.Config) (*SSHServer, error) {
952+func NewSSHServer(ctx context.Context) (*SSHServer, error) {
953+ cfg := config.FromContext(ctx)
954 var err error
955- s := &SSHServer{cfg: cfg}
956- logger := logger.StandardLog(log.StandardLogOptions{ForceLevel: log.DebugLevel})
957+ s := &SSHServer{
958+ cfg: cfg,
959+ ctx: ctx,
960+ logger: log.FromContext(ctx).WithPrefix("ssh"),
961+ }
962+ logger := s.logger.StandardLog(log.StandardLogOptions{ForceLevel: log.DebugLevel})
963 mw := []wish.Middleware{
964 rm.MiddlewareWithLogger(
965 logger,
966@@ -151,7 +154,7 @@ func (s *SSHServer) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) (allowed
967 }(&allowed)
968
969 ac := s.cfg.Backend.AccessLevelByPublicKey("", pk)
970- logger.Debugf("access level for %q: %s", ak, ac)
971+ s.logger.Debugf("access level for %q: %s", ak, ac)
972 allowed = ac >= backend.ReadOnlyAccess
973 return
974 }
975@@ -168,7 +171,7 @@ func (s *SSHServer) KeyboardInteractiveHandler(ctx ssh.Context, _ gossh.Keyboard
976 // checked for access on a per repo basis for a ssh.Session public key.
977 // Hooks.Push and Hooks.Fetch will be called on successful completion of
978 // their commands.
979-func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
980+func (ss *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
981 return func(sh ssh.Handler) ssh.Handler {
982 return func(s ssh.Session) {
983 func() {
984@@ -196,7 +199,7 @@ func (s *SSHServer) Middleware(cfg *config.Config) wish.Middleware {
985 "SOFT_SERVE_PUBLIC_KEY=" + ak,
986 }
987
988- logger.Debug("git middleware", "cmd", gc, "access", access.String())
989+ ss.logger.Debug("git middleware", "cmd", gc, "access", access.String())
990 repoDir := filepath.Join(reposDir, repo)
991 switch gc {
992 case git.ReceivePackBin:
993diff --git a/server/stats/stats.go b/server/stats/stats.go
994index d0029d65aed0b1d0d68aa7e4d625a10838b4d512..b515f1b52a816fd111b40c7842573cc592c0e8fa 100644
995--- a/server/stats/stats.go
996+++ b/server/stats/stats.go
997@@ -11,15 +11,18 @@ import (
998
999 // StatsServer is a server for collecting and reporting statistics.
1000 type StatsServer struct {
1001+ ctx context.Context
1002 cfg *config.Config
1003 server *http.Server
1004 }
1005
1006 // NewStatsServer returns a new StatsServer.
1007-func NewStatsServer(cfg *config.Config) (*StatsServer, error) {
1008+func NewStatsServer(ctx context.Context) (*StatsServer, error) {
1009+ cfg := config.FromContext(ctx)
1010 mux := http.NewServeMux()
1011 mux.Handle("/metrics", promhttp.Handler())
1012 return &StatsServer{
1013+ ctx: ctx,
1014 cfg: cfg,
1015 server: &http.Server{
1016 Addr: cfg.Stats.ListenAddr,
1017diff --git a/server/web/http.go b/server/web/http.go
1018index 694f58807f0750ea75ff84ed2637c0f977ef00ae..c3e33e0f9960eacccccf6500cc5751e64cd9f0a5 100644
1019--- a/server/web/http.go
1020+++ b/server/web/http.go
1021@@ -24,10 +24,6 @@ import (
1022 "goji.io/pattern"
1023 )
1024
1025-var (
1026- logger = log.WithPrefix("server.web")
1027-)
1028-
1029 var (
1030 gitHttpCounter = promauto.NewCounterVec(prometheus.CounterOpts{
1031 Namespace: "soft_serve",
1032@@ -64,18 +60,17 @@ func (r *logWriter) WriteHeader(code int) {
1033 r.ResponseWriter.WriteHeader(code)
1034 }
1035
1036-func loggingMiddleware(next http.Handler) http.Handler {
1037- logger := logger.WithPrefix("server.http")
1038+func (s *HTTPServer) loggingMiddleware(next http.Handler) http.Handler {
1039 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1040 start := time.Now()
1041 writer := &logWriter{code: http.StatusOK, ResponseWriter: w}
1042- logger.Debug("request",
1043+ s.logger.Debug("request",
1044 "method", r.Method,
1045 "uri", r.RequestURI,
1046 "addr", r.RemoteAddr)
1047 next.ServeHTTP(writer, r)
1048 elapsed := time.Since(start)
1049- logger.Debug("response",
1050+ s.logger.Debug("response",
1051 "status", fmt.Sprintf("%d %s", writer.code, http.StatusText(writer.code)),
1052 "bytes", humanize.Bytes(uint64(writer.bytes)),
1053 "time", elapsed)
1054@@ -84,15 +79,20 @@ func loggingMiddleware(next http.Handler) http.Handler {
1055
1056 // HTTPServer is an http server.
1057 type HTTPServer struct {
1058+ ctx context.Context
1059 cfg *config.Config
1060 server *http.Server
1061 dirHandler http.Handler
1062+ logger *log.Logger
1063 }
1064
1065-func NewHTTPServer(cfg *config.Config) (*HTTPServer, error) {
1066+func NewHTTPServer(ctx context.Context) (*HTTPServer, error) {
1067+ cfg := config.FromContext(ctx)
1068 mux := goji.NewMux()
1069 s := &HTTPServer{
1070+ ctx: ctx,
1071 cfg: cfg,
1072+ logger: log.FromContext(ctx).WithPrefix("http"),
1073 dirHandler: http.FileServer(http.Dir(filepath.Join(cfg.DataPath, "repos"))),
1074 server: &http.Server{
1075 Addr: cfg.HTTP.ListenAddr,
1076@@ -104,7 +104,7 @@ func NewHTTPServer(cfg *config.Config) (*HTTPServer, error) {
1077 },
1078 }
1079
1080- mux.Use(loggingMiddleware)
1081+ mux.Use(s.loggingMiddleware)
1082 for _, m := range []Matcher{
1083 getInfoRefs,
1084 getHead,
1085@@ -306,7 +306,7 @@ func (s *HTTPServer) handleGit(w http.ResponseWriter, r *http.Request) {
1086 repo := pat.Param(r, "repo")
1087 repo = utils.SanitizeRepo(repo) + ".git"
1088 if _, err := s.cfg.Backend.Repository(repo); err != nil {
1089- logger.Debug("repository not found", "repo", repo, "err", err)
1090+ s.logger.Debug("repository not found", "repo", repo, "err", err)
1091 http.NotFound(w, r)
1092 return
1093 }
1094diff --git a/ui/common/common.go b/ui/common/common.go
1095index 78b4a992bb1ee46cb9f2edb62392e74633d8ebd3..ebcd6621289d812e28aac42c2230617372a56cad 100644
1096--- a/ui/common/common.go
1097+++ b/ui/common/common.go
1098@@ -3,6 +3,7 @@ package common
1099 import (
1100 "context"
1101
1102+ "github.com/charmbracelet/log"
1103 "github.com/charmbracelet/soft-serve/git"
1104 "github.com/charmbracelet/soft-serve/server/config"
1105 "github.com/charmbracelet/soft-serve/ui/keymap"
1106@@ -30,6 +31,7 @@ type Common struct {
1107 KeyMap *keymap.KeyMap
1108 Zone *zone.Manager
1109 Output *termenv.Output
1110+ Logger *log.Logger
1111 }
1112
1113 // NewCommon returns a new Common struct.
1114@@ -45,6 +47,7 @@ func NewCommon(ctx context.Context, out *termenv.Output, width, height int) Comm
1115 Styles: styles.DefaultStyles(),
1116 KeyMap: keymap.DefaultKeyMap(),
1117 Zone: zone.New(),
1118+ Logger: log.FromContext(ctx).WithPrefix("ui"),
1119 }
1120 }
1121
1122diff --git a/ui/pages/repo/log.go b/ui/pages/repo/log.go
1123index 93c1aa5dd58e673f8863f567d8cb5bfcf112512a..e59e886f7c397e071a259cea362672aafb46b94b 100644
1124--- a/ui/pages/repo/log.go
1125+++ b/ui/pages/repo/log.go
1126@@ -393,7 +393,7 @@ func (l *Log) countCommitsCmd() tea.Msg {
1127 }
1128 count, err := r.CountCommits(l.ref)
1129 if err != nil {
1130- logger.Debugf("ui: error counting commits: %v", err)
1131+ l.common.Logger.Debugf("ui: error counting commits: %v", err)
1132 return common.ErrorMsg(err)
1133 }
1134 return LogCountMsg(count)
1135@@ -423,7 +423,7 @@ func (l *Log) updateCommitsCmd() tea.Msg {
1136 // CommitsByPage pages start at 1
1137 cc, err := r.CommitsByPage(l.ref, page+1, limit)
1138 if err != nil {
1139- logger.Debugf("ui: error loading commits: %v", err)
1140+ l.common.Logger.Debugf("ui: error loading commits: %v", err)
1141 return common.ErrorMsg(err)
1142 }
1143 for i, c := range cc {
1144@@ -445,12 +445,12 @@ func (l *Log) selectCommitCmd(commit *git.Commit) tea.Cmd {
1145 func (l *Log) loadDiffCmd() tea.Msg {
1146 r, err := l.repo.Open()
1147 if err != nil {
1148- logger.Debugf("ui: error loading diff repository: %v", err)
1149+ l.common.Logger.Debugf("ui: error loading diff repository: %v", err)
1150 return common.ErrorMsg(err)
1151 }
1152 diff, err := r.Diff(l.selectedCommit)
1153 if err != nil {
1154- logger.Debugf("ui: error loading diff: %v", err)
1155+ l.common.Logger.Debugf("ui: error loading diff: %v", err)
1156 return common.ErrorMsg(err)
1157 }
1158 return LogDiffMsg(diff)
1159diff --git a/ui/pages/repo/refs.go b/ui/pages/repo/refs.go
1160index 90b05c22e6145218d6f1cd98e6b15b8832a3315d..c8a1bc5d726a7122fda694e729bd41947161b341 100644
1161--- a/ui/pages/repo/refs.go
1162+++ b/ui/pages/repo/refs.go
1163@@ -181,7 +181,7 @@ func (r *Refs) updateItemsCmd() tea.Msg {
1164 }
1165 refs, err := rr.References()
1166 if err != nil {
1167- logger.Debugf("ui: error getting references: %v", err)
1168+ r.common.Logger.Debugf("ui: error getting references: %v", err)
1169 return common.ErrorMsg(err)
1170 }
1171 for _, ref := range refs {
1172@@ -228,10 +228,8 @@ func UpdateRefCmd(repo backend.Repository) tea.Cmd {
1173 }
1174 ref, err := r.HEAD()
1175 if err != nil {
1176- logger.Debugf("ui: error getting HEAD reference: %v", err)
1177 return common.ErrorMsg(err)
1178 }
1179- logger.Debugf("HEAD: %s", ref.Name())
1180 return RefMsg(ref)
1181 }
1182 }
1183diff --git a/ui/pages/repo/repo.go b/ui/pages/repo/repo.go
1184index 5e280b2b24242390147845bf6cbf054f4469d9fa..87f715e610d9ec46a5b833918c76646c659da926 100644
1185--- a/ui/pages/repo/repo.go
1186+++ b/ui/pages/repo/repo.go
1187@@ -8,7 +8,6 @@ import (
1188 "github.com/charmbracelet/bubbles/spinner"
1189 tea "github.com/charmbracelet/bubbletea"
1190 "github.com/charmbracelet/lipgloss"
1191- "github.com/charmbracelet/log"
1192 "github.com/charmbracelet/soft-serve/git"
1193 "github.com/charmbracelet/soft-serve/server/backend"
1194 "github.com/charmbracelet/soft-serve/ui/common"
1195@@ -17,10 +16,6 @@ import (
1196 "github.com/charmbracelet/soft-serve/ui/components/tabs"
1197 )
1198
1199-var (
1200- logger = log.WithPrefix("ui.repo")
1201-)
1202-
1203 type state int
1204
1205 const (
1206@@ -92,6 +87,7 @@ func New(c common.Common) *Repo {
1207 for i, t := range []tab{readmeTab, filesTab, commitsTab, branchesTab, tagsTab} {
1208 ts[i] = t.String()
1209 }
1210+ c.Logger = c.Logger.WithPrefix("ui.repo")
1211 tb := tabs.New(c, ts)
1212 readme := NewReadme(c)
1213 log := NewLog(c)
1214diff --git a/ui/pages/selection/selection.go b/ui/pages/selection/selection.go
1215index 2c7957c6aacaae4eb380dbc3447cf95de6b21580..5860d9d8be78a6437d0accb61878e1a9204a7b61 100644
1216--- a/ui/pages/selection/selection.go
1217+++ b/ui/pages/selection/selection.go
1218@@ -8,7 +8,6 @@ import (
1219 "github.com/charmbracelet/bubbles/list"
1220 tea "github.com/charmbracelet/bubbletea"
1221 "github.com/charmbracelet/lipgloss"
1222- "github.com/charmbracelet/log"
1223 "github.com/charmbracelet/soft-serve/server/backend"
1224 "github.com/charmbracelet/soft-serve/ui/common"
1225 "github.com/charmbracelet/soft-serve/ui/components/code"
1226@@ -20,10 +19,6 @@ const (
1227 defaultNoContent = "No readme found.\n\nCreate a `.soft-serve` repository and add a `README.md` file to display readme."
1228 )
1229
1230-var (
1231- logger = log.WithPrefix("ui.selection")
1232-)
1233-
1234 type pane int
1235
1236 const (
1237@@ -219,7 +214,7 @@ func (s *Selection) Init() tea.Cmd {
1238 if al >= backend.ReadOnlyAccess {
1239 item, err := NewItem(r, cfg)
1240 if err != nil {
1241- logger.Debugf("ui: failed to create item for %s: %v", r.Name(), err)
1242+ s.common.Logger.Debugf("ui: failed to create item for %s: %v", r.Name(), err)
1243 continue
1244 }
1245 sortedItems = append(sortedItems, item)
1246diff --git a/ui/ui.go b/ui/ui.go
1247index 78b92b8434249be1791defa69f5c0f85a097c764..9a2553809a8529510d9d8eef0b884615fabe49d2 100644
1248--- a/ui/ui.go
1249+++ b/ui/ui.go
1250@@ -7,7 +7,6 @@ import (
1251 "github.com/charmbracelet/bubbles/list"
1252 tea "github.com/charmbracelet/bubbletea"
1253 "github.com/charmbracelet/lipgloss"
1254- "github.com/charmbracelet/log"
1255 "github.com/charmbracelet/soft-serve/server/backend"
1256 "github.com/charmbracelet/soft-serve/ui/common"
1257 "github.com/charmbracelet/soft-serve/ui/components/footer"
1258@@ -17,10 +16,6 @@ import (
1259 "github.com/charmbracelet/soft-serve/ui/pages/selection"
1260 )
1261
1262-var (
1263- logger = log.WithPrefix("ui")
1264-)
1265-
1266 type page int
1267
1268 const (
1269@@ -165,7 +160,7 @@ func (ui *UI) IsFiltering() bool {
1270
1271 // Update implements tea.Model.
1272 func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
1273- logger.Debugf("msg received: %T", msg)
1274+ ui.common.Logger.Debugf("msg received: %T", msg)
1275 cmds := make([]tea.Cmd, 0)
1276 switch msg := msg.(type) {
1277 case tea.WindowSizeMsg:
1278@@ -295,7 +290,7 @@ func (ui *UI) openRepo(rn string) (backend.Repository, error) {
1279 }
1280 repos, err := cfg.Backend.Repositories()
1281 if err != nil {
1282- logger.Debugf("ui: failed to list repos: %v", err)
1283+ ui.common.Logger.Debugf("ui: failed to list repos: %v", err)
1284 return nil, err
1285 }
1286 for _, r := range repos {