diff --git a/client/client_test.go b/client/client_test.go index b29cd51..ff1f4b9 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -3265,4 +3265,29 @@ func TestInstallSkipConfigModification(t *testing.T) { } } +func TestConfigLogLevel(t *testing.T) { + markTestForSharding(t, 16) + defer testutils.BackupAndRestore(t)() + tester := zshTester{} + installHishtory(t, tester, "") + + // Check default log level + out := tester.RunInteractiveShell(t, `hishtory config-get log-level`) + require.Equal(t, "info\n", out) + + // Set log level to debug + tester.RunInteractiveShell(t, `hishtory config-set log-level debug`) + + // Verify log level was changed + out = tester.RunInteractiveShell(t, `hishtory config-get log-level`) + require.Equal(t, "debug\n", out) + + // Set back to default + tester.RunInteractiveShell(t, `hishtory config-set log-level info`) + + // Verify log level was changed back + out = tester.RunInteractiveShell(t, `hishtory config-get log-level`) + require.Equal(t, "info\n", out) +} + // TODO: somehow test/confirm that hishtory works even if only bash/only zsh is installed diff --git a/client/cmd/configGet.go b/client/cmd/configGet.go index 0141b79..b777993 100644 --- a/client/cmd/configGet.go +++ b/client/cmd/configGet.go @@ -183,4 +183,15 @@ func init() { configGetCmd.AddCommand(getDefaultFilterCmd) configGetCmd.AddCommand(getAiCompletionEndpoint) configGetCmd.AddCommand(getCompactMode) + configGetCmd.AddCommand(getLogLevelCmd) +} + +var getLogLevelCmd = &cobra.Command{ + Use: "log-level", + Short: "Get the current log level for hishtory logs", + Run: func(cmd *cobra.Command, args []string) { + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + fmt.Println(config.LogLevel.String()) + }, } diff --git a/client/cmd/configSet.go b/client/cmd/configSet.go index 5d4e446..3b7d586 100644 --- a/client/cmd/configSet.go +++ b/client/cmd/configSet.go @@ -9,6 +9,7 @@ import ( "github.com/ddworken/hishtory/client/hctx" "github.com/ddworken/hishtory/client/lib" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -244,6 +245,23 @@ var setAiCompletionEndpoint = &cobra.Command{ }, } +var setLogLevelCmd = &cobra.Command{ + Use: "log-level", + Short: "Set the log level for hishtory logs", + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + ValidArgs: []string{"error", "warn", "info", "debug"}, + Run: func(cmd *cobra.Command, args []string) { + level, err := logrus.ParseLevel(args[0]) + if err != nil { + lib.CheckFatalError(fmt.Errorf("invalid log level: %v", err)) + } + ctx := hctx.MakeContext() + config := hctx.GetConf(ctx) + config.LogLevel = level + lib.CheckFatalError(hctx.SetConfig(config)) + }, +} + func init() { rootCmd.AddCommand(configSetCmd) configSetCmd.AddCommand(setEnableControlRCmd) @@ -258,6 +276,7 @@ func init() { configSetCmd.AddCommand(setDefaultFilterCommand) configSetCmd.AddCommand(setAiCompletionEndpoint) configSetCmd.AddCommand(compactMode) + configSetCmd.AddCommand(setLogLevelCmd) setColorSchemeCmd.AddCommand(setColorSchemeSelectedText) setColorSchemeCmd.AddCommand(setColorSchemeSelectedBackground) setColorSchemeCmd.AddCommand(setColorSchemeBorderColor) diff --git a/client/cmd/query.go b/client/cmd/query.go index 1dc4e61..4da2629 100644 --- a/client/cmd/query.go +++ b/client/cmd/query.go @@ -113,13 +113,13 @@ var updateLocalDbFromRemoteCmd = &cobra.Command{ if config.BetaMode { lib.CheckFatalError(err) } else if err != nil { - hctx.GetLogger().Infof("updateLocalDbFromRemote: Failed to RetrieveAdditionalEntriesFromRemote: %v", err) + hctx.GetLogger().Warnf("updateLocalDbFromRemote: Failed to RetrieveAdditionalEntriesFromRemote: %v", err) } err = lib.ProcessDeletionRequests(ctx) if config.BetaMode { lib.CheckFatalError(err) } else if err != nil { - hctx.GetLogger().Infof("updateLocalDbFromRemote: Failed to ProcessDeletionRequests: %v", err) + hctx.GetLogger().Warnf("updateLocalDbFromRemote: Failed to ProcessDeletionRequests: %v", err) } } }, diff --git a/client/cmd/saveHistoryEntry.go b/client/cmd/saveHistoryEntry.go index 4b70301..6ca51ed 100644 --- a/client/cmd/saveHistoryEntry.go +++ b/client/cmd/saveHistoryEntry.go @@ -124,7 +124,7 @@ func maybeUploadSkippedHistoryEntries(ctx context.Context) error { func handlePotentialUploadFailure(ctx context.Context, err error, config *hctx.ClientConfig, entryTimestamp time.Time) { if err != nil { if lib.IsOfflineError(ctx, err) { - hctx.GetLogger().Infof("Failed to remotely persist hishtory entry because we failed to connect to the remote server! This is likely because the device is offline, but also could be because the remote server is having reliability issues. Original error: %v", err) + hctx.GetLogger().Warnf("Failed to remotely persist hishtory entry because we failed to connect to the remote server! This is likely because the device is offline, but also could be because the remote server is having reliability issues. Original error: %v", err) if !config.HaveMissedUploads { config.HaveMissedUploads = true // Set MissedUploadTimestamp to `entry timestamp - 1` so that the current entry will get @@ -261,7 +261,7 @@ func deletePresavedEntries(ctx context.Context, entry *data.HistoryEntry, isRetr // this function after a short delay. If it still is empty, then we assume we are in case #1. if isRetry { // Already retried, assume we're in case #1 - hctx.GetLogger().Infof("failed to find presaved entry matching cmd=%#v even with retry, skipping delete", entry.Command) + hctx.GetLogger().Warnf("failed to find presaved entry matching cmd=%#v even with retry, skipping delete", entry.Command) return nil } else { time.Sleep(500 * time.Millisecond) diff --git a/client/hctx/hctx.go b/client/hctx/hctx.go index d025cf3..c1f209d 100644 --- a/client/hctx/hctx.go +++ b/client/hctx/hctx.go @@ -52,8 +52,14 @@ func GetLogger() *logrus.Logger { hishtoryLogger = logrus.New() hishtoryLogger.SetFormatter(logFormatter) - hishtoryLogger.SetLevel(logrus.InfoLevel) hishtoryLogger.SetOutput(lumberjackLogger) + + // Configure the log level from the config file, if the config file exists + hishtoryLogger.SetLevel(logrus.InfoLevel) + cfg, err := GetConfig() + if err == nil { + hishtoryLogger.SetLevel(cfg.LogLevel) + } }) return hishtoryLogger } @@ -213,6 +219,8 @@ type ClientConfig struct { AiCompletionEndpoint string `json:"ai_completion_endpoint"` // Custom key bindings for the TUI KeyBindings keybindings.SerializableKeyMap `json:"key_bindings"` + // The log level for hishtory (e.g., "debug", "info", "warn", "error") + LogLevel logrus.Level `json:"log_level"` } type ColorScheme struct { @@ -284,6 +292,9 @@ func GetConfig() (ClientConfig, error) { if config.AiCompletionEndpoint == "" { config.AiCompletionEndpoint = "https://api.openai.com/v1/chat/completions" } + if config.LogLevel == logrus.Level(0) { + config.LogLevel = logrus.InfoLevel + } return config, nil } diff --git a/client/lib/slsa.go b/client/lib/slsa.go index 37b54c7..effc136 100644 --- a/client/lib/slsa.go +++ b/client/lib/slsa.go @@ -55,7 +55,7 @@ func VerifyBinary(ctx context.Context, binaryPath, attestationPath, versionTag s } resp, err := ApiGet(ctx, "/api/v1/slsa-status?newVersion="+versionTag) if err != nil { - hctx.GetLogger().Infof("Failed to query SLSA status (err=%#v), assuming that SLSA is currently working", err) + hctx.GetLogger().Warnf("Failed to query SLSA status (err=%#v), assuming that SLSA is currently working", err) } if err == nil && string(resp) != "OK" { slsa_status_error: diff --git a/client/tui/tui.go b/client/tui/tui.go index 024042e..11f5d97 100644 --- a/client/tui/tui.go +++ b/client/tui/tui.go @@ -146,7 +146,7 @@ func initialModel(ctx context.Context, shellName, initialQuery string) model { if err == nil { queryInput.Width = width } else { - hctx.GetLogger().Infof("getTerminalSize() return err=%#v, defaulting queryInput to a width of 50", err) + hctx.GetLogger().Warnf("getTerminalSize() return err=%#v, defaulting queryInput to a width of 50", err) queryInput.Width = 50 } if initialQuery != "" { @@ -378,7 +378,7 @@ func (m model) View() string { if strings.HasPrefix(changeDir, "~/") { homedir, err := os.UserHomeDir() if err != nil { - hctx.GetLogger().Infof("UserHomeDir() return err=%v, skipping replacing ~/", err) + hctx.GetLogger().Warnf("UserHomeDir() return err=%v, skipping replacing ~/", err) } else { strippedChangeDir, _ := strings.CutPrefix(changeDir, "~/") changeDir = filepath.Join(homedir, strippedChangeDir) @@ -425,7 +425,7 @@ func isExtraCompactHeightMode(ctx context.Context) bool { } _, height, err := getTerminalSize() if err != nil { - hctx.GetLogger().Infof("got err=%v when retrieving terminal dimensions, assuming the terminal is reasonably tall", err) + hctx.GetLogger().Warnf("got err=%v when retrieving terminal dimensions, assuming the terminal is reasonably tall", err) return false } return height < 15 @@ -437,7 +437,7 @@ func isCompactHeightMode(ctx context.Context) bool { } _, height, err := getTerminalSize() if err != nil { - hctx.GetLogger().Infof("got err=%v when retrieving terminal dimensions, assuming the terminal is reasonably tall", err) + hctx.GetLogger().Warnf("got err=%v when retrieving terminal dimensions, assuming the terminal is reasonably tall", err) return false } return height < 25 @@ -467,7 +467,7 @@ func renderNullableTable(m model, helpText string) string { func getRowsFromAiSuggestions(ctx context.Context, columnNames []string, shellName, query string) ([]table.Row, []*data.HistoryEntry, error) { suggestions, err := ai.DebouncedGetAiSuggestions(ctx, shellName, strings.TrimPrefix(query, "?"), 5) if err != nil { - hctx.GetLogger().Infof("failed to get AI query suggestions: %v", err) + hctx.GetLogger().Warnf("failed to get AI query suggestions: %v", err) return nil, nil, fmt.Errorf("failed to get AI query suggestions: %w", err) } var rows []table.Row @@ -716,7 +716,7 @@ func makeTable(ctx context.Context, shellName string, rows []table.Row) (table.M if err != nil { // Failed to compile the regex for highlighting matches, this should never happen. In this // case, just use a regexp that matches nothing to ensure that the TUI doesn't crash. - hctx.GetLogger().Infof("Failed to compile regex %#v for query %#v, disabling highlighting of matches", queryRegex, CURRENT_QUERY_FOR_HIGHLIGHTING) + hctx.GetLogger().Warnf("Failed to compile regex %#v for query %#v, disabling highlighting of matches", queryRegex, CURRENT_QUERY_FOR_HIGHLIGHTING) re = MATCH_NOTHING_REGEXP } else { re = r