on main help sign in
fix(logs): limit the logs output
08c5b4cKujtim Hoxha
cmd/logs.go+89−79
@@ -1,14 +1,13 @@
1package cmd
1package cmd
2
2
3import (
3import (
4− "bufio"
4+ "context"
5 "encoding/json"
5 "encoding/json"
6 "fmt"
6 "fmt"
7 "io"
7 "io"
8 "os"
8 "os"
9 "path/filepath"
9 "path/filepath"
10 "slices"
10 "slices"
11− "strings"
12 "time"
11 "time"
13
12
14 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/config"
@@ -17,6 +16,8 @@ import (
17 "github.com/spf13/cobra"
16 "github.com/spf13/cobra"
18)
17)
19
18
19+const defaultTailLines = 1000
20+
20var logsCmd = &cobra.Command{
21var logsCmd = &cobra.Command{
21 Use: "logs",
22 Use: "logs",
22 Short: "View crush logs",
23 Short: "View crush logs",
@@ -38,7 +39,6 @@ var logsCmd = &cobra.Command{
38 }
39 }
39
40
40 log.SetLevel(log.DebugLevel)
41 log.SetLevel(log.DebugLevel)
41− // Configure log to output to stdout instead of stderr
42 log.SetOutput(os.Stdout)
42 log.SetOutput(os.Stdout)
43
43
44 cfg, err := config.Load(cwd, false)
44 cfg, err := config.Load(cwd, false)
@@ -53,104 +53,114 @@ var logsCmd = &cobra.Command{
53 }
53 }
54
54
55 if follow {
55 if follow {
56− // Follow mode - tail the file continuously
57− t, err := tail.TailFile(logsFile, tail.Config{Follow: true, ReOpen: true, Logger: tail.DiscardingLogger})
58− if err != nil {
59− return fmt.Errorf("failed to tail log file: %v", err)
60− }
61−
62− // Print the text of each received line
63− for line := range t.Lines {
64− printLogLine(line.Text)
65− }
66− } else if tailLines > 0 {
67− // Tail mode - show last N lines
68− lines, err := readLastNLines(logsFile, tailLines)
56+ return followLogs(cmd.Context(), 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− }
75− } else {
76− // Oneshot mode - read the entire file once
77− file, err := os.Open(logsFile)
78− if err != nil {
79− return fmt.Errorf("failed to open log file: %v", err)
80− }
81− defer file.Close()
82−
83− reader := bufio.NewReader(file)
84− for {
85− line, err := reader.ReadString('\n')
86− if err != nil {
87− if err == io.EOF && line != "" {
88− // Handle last line without newline
89− printLogLine(line)
90− }
91− break
92− }
93− // Remove trailing newline
94− line = strings.TrimSuffix(line, "\n")
95− printLogLine(line)
96− }
97 }
57 }
98
58
99− return nil
59+ return showLogs(logsFile, tailLines)
100 },
60 },
101}
61}
102
62
103func init() {
63func init() {
104 logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
64 logsCmd.Flags().BoolP("follow", "f", false, "Follow log output")
105− logsCmd.Flags().IntP("tail", "t", 0, "Show only the last N lines")
65+ logsCmd.Flags().IntP("tail", "t", defaultTailLines, "Show only the last N lines default: 1000 for performance")
106 rootCmd.AddCommand(logsCmd)
66 rootCmd.AddCommand(logsCmd)
107}
67}
108
68
109−// readLastNLines reads the last N lines from a file using a simple circular buffer approach
69+func followLogs(ctx context.Context, logsFile string, tailLines int) error {
110−func readLastNLines(filename string, n int) ([]string, error) {
70+ t, err := tail.TailFile(logsFile, tail.Config{
111− file, err := os.Open(filename)
71+ Follow: false,
72+ ReOpen: false,
73+ Logger: tail.DiscardingLogger,
74+ })
112 if err != nil {
75 if err != nil {
113− return nil, err
76+ return fmt.Errorf("failed to tail log file: %v", err)
114 }
77 }
115− defer file.Close()
116
78
117− // Use a circular buffer to keep only the last N lines
118− lines := make([]string, n)
79+ var lines []string
119− count := 0
80+ lineCount := 0
120− index := 0
81+ for line := range t.Lines {
82+ if line.Err != nil {
83+ continue
84+ }
85+ lines = append(lines, line.Text)
86+ lineCount++
87+ if lineCount >= tailLines {
88+ if len(lines) > tailLines {
89+ lines = lines[len(lines)-tailLines:]
90+ }
91+ }
92+ }
93+ t.Stop()
94+
95+ for _, line := range lines {
96+ printLogLine(line)
97+ }
98+
99+ if len(lines) == tailLines {
100+ fmt.Fprintf(os.Stderr, "\nShowing last %d lines. Full logs available at: %s\n", tailLines, logsFile)
101+ fmt.Fprintf(os.Stderr, "Following new log entries...\n\n")
102+ }
103+
104+ t, err = tail.TailFile(logsFile, tail.Config{
105+ Follow: true,
106+ ReOpen: true,
107+ Logger: tail.DiscardingLogger,
108+ Location: &tail.SeekInfo{Offset: 0, Whence: io.SeekEnd},
109+ })
110+ if err != nil {
111+ return fmt.Errorf("failed to tail log file: %v", err)
112+ }
113+ defer t.Stop()
121
114
122− reader := bufio.NewReader(file)
123 for {
115 for {
124− line, err := reader.ReadString('\n')
116+ select {
117+ case line := <-t.Lines:
125− if err != nil {
118+ if line.Err != nil {
126− if err == io.EOF && line != "" {
119+ continue
127− // Handle last line without newline
120+ }
128− line = strings.TrimSuffix(line, "\n")
121+ printLogLine(line.Text)
129− lines[index] = line
122+ case <-ctx.Done():
130− count++
123+ return nil
131− index = (index + 1) % n
124+ }
125+ }
126+}
127+
128+func showLogs(logsFile string, tailLines int) error {
129+ t, err := tail.TailFile(logsFile, tail.Config{
130+ Follow: false,
131+ ReOpen: false,
132+ Logger: tail.DiscardingLogger,
133+ MaxLineSize: 0,
134+ })
135+ if err != nil {
136+ return fmt.Errorf("failed to tail log file: %v", err)
137+ }
138+ defer t.Stop()
139+
140+ var lines []string
141+ lineCount := 0
142+ for line := range t.Lines {
143+ if line.Err != nil {
144+ continue
145+ }
146+ lines = append(lines, line.Text)
147+ lineCount++
148+ if lineCount >= tailLines {
149+ if len(lines) > tailLines {
150+ lines = lines[len(lines)-tailLines:]
132 }
151 }
133− break
134 }
152 }
135− // Remove trailing newline
136− line = strings.TrimSuffix(line, "\n")
137− lines[index] = line
138− count++
139− index = (index + 1) % n
140 }
153 }
141
154
142− // Extract the last N lines in correct order
155+ for _, line := range lines {
143− if count <= n {
156+ printLogLine(line)
144− // We have fewer lines than requested, return them all
145− return lines[:count], nil
146 }
157 }
147
158
148− // We have more lines than requested, extract from circular buffer
159+ if len(lines) == tailLines {
149− result := make([]string, n)
160+ fmt.Fprintf(os.Stderr, "\nShowing last %d lines. Full logs available at: %s\n", tailLines, logsFile)
150− for i := range n {
151− result[i] = lines[(index+i)%n]
152 }
161 }
162+
153− return result, nil
163+ return nil
154}
164}
155
165
156func printLogLine(lineText string) {
166func printLogLine(lineText string) {