diff --git a/cmd/picoclaw/internal/agent/helpers.go b/cmd/picoclaw/internal/agent/helpers.go index 0af743bb5..23227d56a 100644 --- a/cmd/picoclaw/internal/agent/helpers.go +++ b/cmd/picoclaw/internal/agent/helpers.go @@ -28,6 +28,8 @@ func agentCmd(message, sessionKey, model string, debug bool) error { return fmt.Errorf("error loading config: %w", err) } + logger.ConfigureFromEnv() + if debug { logger.SetLevel(logger.DEBUG) fmt.Println("🔍 Debug mode enabled") diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index eeb1436de..1bcc1cec9 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -2,6 +2,7 @@ package logger import ( "fmt" + "io" "os" "path/filepath" "runtime" @@ -100,6 +101,12 @@ func SetConsoleLevel(level LogLevel) { logger = logger.Level(level) } +func DisableConsole() { + mu.Lock() + defer mu.Unlock() + logger = zerolog.New(io.Discard).With().Timestamp().Caller().Logger() +} + func GetLevel() LogLevel { mu.RLock() defer mu.RUnlock() @@ -170,6 +177,22 @@ func DisableFileLogging() { fileLogger = zerolog.Logger{} } +func ConfigureFromEnv() { + if logFile := os.Getenv("PICOCLAW_LOG_FILE"); logFile != "" { + if strings.HasPrefix(logFile, "~/") { + if home := os.Getenv("HOME"); home != "" { + logFile = filepath.Join(home, logFile[2:]) + } + } + + if err := EnableFileLogging(logFile); err != nil { + fmt.Fprintf(os.Stderr, "failed to enable file logging: %v\n", err) + } else { + DisableConsole() + } + } +} + func getCallerSkip() int { for i := 2; i < 15; i++ { pc, file, _, ok := runtime.Caller(i) diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go index 6ad3a8dd6..1eca72607 100644 --- a/pkg/logger/logger_test.go +++ b/pkg/logger/logger_test.go @@ -4,7 +4,11 @@ import ( "bytes" "encoding/json" "errors" + "fmt" + "os" + "path/filepath" "testing" + "time" "github.com/rs/zerolog" ) @@ -365,3 +369,40 @@ func TestAppendFields_ErrorUsesErrorString(t *testing.T) { t.Fatalf("error field = %#v, want %q", got["error"], "transcription request failed") } } + +func TestDisableConsole(t *testing.T) { + DisableConsole() + Info("this should go to nowhere") +} + +func TestConfigureFromEnv(t *testing.T) { + home := os.Getenv("HOME") + if home == "" { + t.Skip("HOME not set") + } + + tmpFile := "/tmp/picoclaw_test_log_" + fmt.Sprintf("%d", time.Now().UnixNano()) + defer os.Remove(tmpFile) + + os.Setenv("PICOCLAW_LOG_FILE", tmpFile) + defer os.Unsetenv("PICOCLAW_LOG_FILE") + + ConfigureFromEnv() + + if logFile == nil { + t.Error("expected log file to be set") + } + + Info("test message") + + os.Setenv("PICOCLAW_LOG_FILE", "~/test_log") + ConfigureFromEnv() + + expanded := filepath.Join(home, "test_log") + defer os.Remove(expanded) +} + +func TestConfigureFromEnvNoEnv(t *testing.T) { + os.Unsetenv("PICOCLAW_LOG_FILE") + ConfigureFromEnv() +}