diff --git a/cmd/logs.go b/cmd/logs.go index 94ba0509d27bb8cefdbf9ffcef65409f2074557a..bb0aaf9d7b8c2cbcf1da8823c2848002f6d2e252 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -32,6 +32,11 @@ var logsCmd = &cobra.Command{ return fmt.Errorf("failed to get follow flag: %v", err) } + tailLines, err := cmd.Flags().GetInt("tail") + if err != nil { + return fmt.Errorf("failed to get tail flag: %v", err) + } + log.SetLevel(log.DebugLevel) // Configure log to output to stdout instead of stderr log.SetOutput(os.Stdout) @@ -58,6 +63,15 @@ var logsCmd = &cobra.Command{ for line := range t.Lines { printLogLine(line.Text) } + } else if tailLines > 0 { + // Tail mode - show last N lines + lines, err := readLastNLines(logsFile, tailLines) + if err != nil { + return fmt.Errorf("failed to read last %d lines: %v", tailLines, err) + } + for _, line := range lines { + printLogLine(line) + } } else { // Oneshot mode - read the entire file once file, err := os.Open(logsFile) @@ -88,9 +102,57 @@ var logsCmd = &cobra.Command{ func init() { logsCmd.Flags().BoolP("follow", "f", false, "Follow log output") + logsCmd.Flags().IntP("tail", "t", 0, "Show only the last N lines") rootCmd.AddCommand(logsCmd) } +// readLastNLines reads the last N lines from a file using a simple circular buffer approach +func readLastNLines(filename string, n int) ([]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + // Use a circular buffer to keep only the last N lines + lines := make([]string, n) + count := 0 + index := 0 + + reader := bufio.NewReader(file) + for { + line, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF && line != "" { + // Handle last line without newline + line = strings.TrimSuffix(line, "\n") + lines[index] = line + count++ + index = (index + 1) % n + } + break + } + // Remove trailing newline + line = strings.TrimSuffix(line, "\n") + lines[index] = line + count++ + index = (index + 1) % n + } + + // Extract the last N lines in correct order + if count <= n { + // We have fewer lines than requested, return them all + return lines[:count], nil + } + + // We have more lines than requested, extract from circular buffer + result := make([]string, n) + for i := range n { + result[i] = lines[(index+i)%n] + } + return result, nil +} + func printLogLine(lineText string) { var data map[string]any if err := json.Unmarshal([]byte(lineText), &data); err != nil { diff --git a/internal/config/load.go b/internal/config/load.go index cc9191fcda5ebfb875fefbac899b21c3597ef0e2..9f2b5e55f1ccc0a687d46083b67e81d6e5fa212a 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -388,7 +388,7 @@ func (cfg *Config) configureSelectedModels(knownProviders []provider.Provider) e } model := cfg.GetModel(large.Provider, large.Model) slog.Info("Configuring selected large model", "provider", large.Provider, "model", large.Model) - slog.Info("MOdel configured", "model", model) + slog.Info("Model configured", "model", model) if model == nil { large = defaultLarge // override the model type to large