on main help sign in
add ringbuffer tailing
bdeece2Tai Groot
cmd/logs.go+62
@@ -32,6 +32,11 @@ var logsCmd = &cobra.Command{
32 return fmt.Errorf("failed to get follow flag: %v", err)
32 return fmt.Errorf("failed to get follow flag: %v", err)
33 }
33 }
34
34
35+ tailLines, err := cmd.Flags().GetInt("tail")
36+ if err != nil {
37+ return fmt.Errorf("failed to get tail flag: %v", err)
38+ }
39+
35 log.SetLevel(log.DebugLevel)
40 log.SetLevel(log.DebugLevel)
36 // Configure log to output to stdout instead of stderr
41 // Configure log to output to stdout instead of stderr
37 log.SetOutput(os.Stdout)
42 log.SetOutput(os.Stdout)
@@ -58,6 +63,15 @@ var logsCmd = &cobra.Command{
58 for line := range t.Lines {
63 for line := range t.Lines {
59 printLogLine(line.Text)
64 printLogLine(line.Text)
60 }
65 }
66+ } else if tailLines > 0 {
67+ // Tail mode - show last N lines
68+ lines, err := readLastNLines(logsFile, tailLines)
69+ if err != nil {
70+ return fmt.Errorf("failed to read last %d lines: %v", tailLines, err)
71+ }
72+ for _, line := range lines {
73+ printLogLine(line)
74+ }
61 } else {
75 } else {
62 // Oneshot mode - read the entire file once
76 // Oneshot mode - read the entire file once
63 file, err := os.Open(logsFile)
77 file, err := os.Open(logsFile)
@@ -88,9 +102,57 @@ var logsCmd = &cobra.Command{
88
102
89func init() {
103func init() {
90 logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
104 logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
105+ logsCmd.Flags().IntP("tail", "t", 0, "Show only the last N lines")
91 rootCmd.AddCommand(logsCmd)
106 rootCmd.AddCommand(logsCmd)
92}
107}
93
108
109+// readLastNLines reads the last N lines from a file using a simple circular buffer approach
110+func readLastNLines(filename string, n int) ([]string, error) {
111+ file, err := os.Open(filename)
112+ if err != nil {
113+ return nil, err
114+ }
115+ defer file.Close()
116+
117+ // Use a circular buffer to keep only the last N lines
118+ lines := make([]string, n)
119+ count := 0
120+ index := 0
121+
122+ reader := bufio.NewReader(file)
123+ for {
124+ line, err := reader.ReadString('\n')
125+ if err != nil {
126+ if err == io.EOF && line != "" {
127+ // Handle last line without newline
128+ line = strings.TrimSuffix(line, "\n")
129+ lines[index] = line
130+ count++
131+ index = (index + 1) % n
132+ }
133+ break
134+ }
135+ // Remove trailing newline
136+ line = strings.TrimSuffix(line, "\n")
137+ lines[index] = line
138+ count++
139+ index = (index + 1) % n
140+ }
141+
142+ // Extract the last N lines in correct order
143+ if count <= n {
144+ // We have fewer lines than requested, return them all
145+ return lines[:count], nil
146+ }
147+
148+ // We have more lines than requested, extract from circular buffer
149+ result := make([]string, n)
150+ for i := range n {
151+ result[i] = lines[(index+i)%n]
152+ }
153+ return result, nil
154+}
155+
94func printLogLine(lineText string) {
156func printLogLine(lineText string) {
95 var data map[string]any
157 var data map[string]any
96 if err := json.Unmarshal([]byte(lineText), &data); err != nil {
158 if err := json.Unmarshal([]byte(lineText), &data); err != nil {
internal/config/load.go+1−1
@@ -388,7 +388,7 @@ func (cfg *Config) configureSelectedModels(knownProviders []provider.Provider) e
388 }
388 }
389 model := cfg.GetModel(large.Provider, large.Model)
389 model := cfg.GetModel(large.Provider, large.Model)
390 slog.Info("Configuring selected large model", "provider", large.Provider, "model", large.Model)
390 slog.Info("Configuring selected large model", "provider", large.Provider, "model", large.Model)
391− slog.Info("MOdel configured", "model", model)
391+ slog.Info("Model configured", "model", model)
392 if model == nil {
392 if model == nil {
393 large = defaultLarge
393 large = defaultLarge
394 // override the model type to large
394 // override the model type to large