fix(server): log to stderr if it's a terminal

Ayman Bagabas created

Change summary

internal/cmd/server.go |  8 +++++++-
internal/log/log.go    | 24 ++++++++++++++++++++----
2 files changed, 27 insertions(+), 5 deletions(-)

Detailed changes

internal/cmd/server.go 🔗

@@ -13,6 +13,7 @@ import (
 	"github.com/charmbracelet/crush/internal/config"
 	crushlog "github.com/charmbracelet/crush/internal/log"
 	"github.com/charmbracelet/crush/internal/server"
+	"github.com/charmbracelet/x/term"
 	"github.com/spf13/cobra"
 )
 
@@ -42,7 +43,12 @@ var serverCmd = &cobra.Command{
 		}
 
 		logFile := filepath.Join(config.GlobalCacheDir(), "server-"+safeNameRegexp.ReplaceAllString(serverHost, "_"), "crush.log")
-		crushlog.Setup(logFile, debug)
+
+		if term.IsTerminal(os.Stderr.Fd()) {
+			crushlog.Setup(logFile, debug, os.Stderr)
+		} else {
+			crushlog.Setup(logFile, debug)
+		}
 
 		hostURL, err := server.ParseHostURL(serverHost)
 		if err != nil {

internal/log/log.go 🔗

@@ -2,6 +2,7 @@ package log
 
 import (
 	"fmt"
+	"io"
 	"log/slog"
 	"os"
 	"runtime/debug"
@@ -10,6 +11,7 @@ import (
 	"time"
 
 	"github.com/charmbracelet/crush/internal/event"
+	"github.com/charmbracelet/x/term"
 	"gopkg.in/natefinch/lumberjack.v2"
 )
 
@@ -18,7 +20,7 @@ var (
 	initialized atomic.Bool
 )
 
-func Setup(logFile string, debug bool) {
+func Setup(logFile string, debug bool, ws ...io.Writer) {
 	initOnce.Do(func() {
 		logRotator := &lumberjack.Logger{
 			Filename:   logFile,
@@ -33,12 +35,26 @@ func Setup(logFile string, debug bool) {
 			level = slog.LevelDebug
 		}
 
-		logger := slog.NewJSONHandler(logRotator, &slog.HandlerOptions{
+		opts := &slog.HandlerOptions{
 			Level:     level,
 			AddSource: true,
-		})
+		}
+
+		var handlers []slog.Handler
+		handlers = append(handlers, slog.NewJSONHandler(logRotator, opts))
+
+		for _, w := range ws {
+			if w == nil {
+				continue
+			}
+			if f, ok := w.(term.File); ok && term.IsTerminal(f.Fd()) {
+				handlers = append(handlers, slog.NewTextHandler(w, opts))
+			} else {
+				handlers = append(handlers, slog.NewJSONHandler(w, opts))
+			}
+		}
 
-		slog.SetDefault(slog.New(logger))
+		slog.SetDefault(slog.New(slog.NewMultiHandler(handlers...)))
 		initialized.Store(true)
 	})
 }