on main help sign in
fix(logs): limit the logs output
commit08c5b4c1327783b2c674d93600604e90cc4d4410 authorKujtim Hoxha <kujtimii.h@gmail.com> dateWed 16 Jul 2025, 22:31 (1 year ago) parentacf354e
1 file changed+89−79
cmd/logs.go+89−79
cmd/logs.go+89−79
@@ -1,14 +1,13 @@
11package cmd
22
33import (
4− "bufio"
4+ "context"
55 "encoding/json"
66 "fmt"
77 "io"
88 "os"
99 "path/filepath"
1010 "slices"
11− "strings"
1211 "time"
1312
1413 "github.com/charmbracelet/crush/internal/config"
@@ -17,6 +16,8 @@ import (
1716 "github.com/spf13/cobra"
1817)
1918
19+const defaultTailLines = 1000
20+
2021var logsCmd = &cobra.Command{
2122 Use: "logs",
2223 Short: "View crush logs",
@@ -38,7 +39,6 @@ var logsCmd = &cobra.Command{
3839 }
3940
4041 log.SetLevel(log.DebugLevel)
41− // Configure log to output to stdout instead of stderr
4242 log.SetOutput(os.Stdout)
4343
4444 cfg, err := config.Load(cwd, false)
@@ -53,104 +53,114 @@ var logsCmd = &cobra.Command{
5353 }
5454
5555 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)
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− }
56+ return followLogs(cmd.Context(), logsFile, tailLines)
9757 }
9858
99− return nil
59+ return showLogs(logsFile, tailLines)
10060 },
10161}
10262
10363func init() {
10464 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")
10666 rootCmd.AddCommand(logsCmd)
10767}
10868
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)
69+func followLogs(ctx context.Context, logsFile string, tailLines int) error {
70+ t, err := tail.TailFile(logsFile, tail.Config{
71+ Follow: false,
72+ ReOpen: false,
73+ Logger: tail.DiscardingLogger,
74+ })
11275 if err != nil {
113− return nil, err
76+ return fmt.Errorf("failed to tail log file: %v", err)
11477 }
115− defer file.Close()
11678
117− // Use a circular buffer to keep only the last N lines
118− lines := make([]string, n)
119− count := 0
120− index := 0
79+ var lines []string
80+ lineCount := 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()
121114
122− reader := bufio.NewReader(file)
123115 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
116+ select {
117+ case line := <-t.Lines:
118+ if line.Err != nil {
119+ continue
120+ }
121+ printLogLine(line.Text)
122+ case <-ctx.Done():
123+ return nil
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:]
132151 }
133− break
134152 }
135− // Remove trailing newline
136− line = strings.TrimSuffix(line, "\n")
137− lines[index] = line
138− count++
139− index = (index + 1) % n
140153 }
141154
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
155+ for _, line := range lines {
156+ printLogLine(line)
146157 }
147158
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]
159+ if len(lines) == tailLines {
160+ fmt.Fprintf(os.Stderr, "\nShowing last %d lines. Full logs available at: %s\n", tailLines, logsFile)
152161 }
153− return result, nil
162+
163+ return nil
154164}
155165
156166func printLogLine(lineText string) {