Skip to content

Commit b129b24

Browse files
authored
Merge pull request #743 from doringeman/fix-logs
fix(cli): resolve infinite loop and output bugs in log merging
2 parents 1775970 + 00dc756 commit b129b24

2 files changed

Lines changed: 106 additions & 29 deletions

File tree

cmd/cli/commands/logs.go

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -78,25 +78,29 @@ func newLogsCmd() *cobra.Command {
7878
}
7979

8080
if noEngines {
81-
err = printMergedLog(serviceLogPath, "")
81+
err = printMergedLog(cmd.OutOrStdout(), serviceLogPath, "")
8282
if err != nil {
8383
return err
8484
}
8585
} else {
86-
err = printMergedLog(serviceLogPath, runtimeLogPath)
86+
err = printMergedLog(cmd.OutOrStdout(), serviceLogPath, runtimeLogPath)
8787
if err != nil {
8888
return err
8989
}
9090
}
9191

92+
if !follow {
93+
return nil
94+
}
95+
9296
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
9397
defer cancel()
9498

9599
g, ctx := errgroup.WithContext(ctx)
96100

97101
g.Go(func() error {
98102
t, err := tail.TailFile(
99-
serviceLogPath, tail.Config{Location: &tail.SeekInfo{Offset: 0, Whence: io.SeekEnd}, Follow: follow, ReOpen: follow},
103+
serviceLogPath, tail.Config{Location: &tail.SeekInfo{Offset: 0, Whence: io.SeekEnd}, Follow: true, ReOpen: true},
100104
)
101105
if err != nil {
102106
return err
@@ -107,19 +111,17 @@ func newLogsCmd() *cobra.Command {
107111
if !ok {
108112
return nil
109113
}
110-
fmt.Println(line.Text)
114+
cmd.Println(line.Text)
111115
case <-ctx.Done():
112116
return t.Stop()
113117
}
114118
}
115119
})
116120

117-
if follow && !noEngines {
118-
// Show inference engines logs if `follow` is enabled
119-
// and the engines logs have not been skipped by setting `--no-engines`.
121+
if !noEngines {
120122
g.Go(func() error {
121123
t, err := tail.TailFile(
122-
runtimeLogPath, tail.Config{Location: &tail.SeekInfo{Offset: 0, Whence: io.SeekEnd}, Follow: follow, ReOpen: follow},
124+
runtimeLogPath, tail.Config{Location: &tail.SeekInfo{Offset: 0, Whence: io.SeekEnd}, Follow: true, ReOpen: true},
123125
)
124126
if err != nil {
125127
return err
@@ -131,7 +133,7 @@ func newLogsCmd() *cobra.Command {
131133
if !ok {
132134
return nil
133135
}
134-
fmt.Println(line.Text)
136+
cmd.Println(line.Text)
135137
case <-ctx.Done():
136138
return t.Stop()
137139
}
@@ -152,7 +154,7 @@ var timestampRe = regexp.MustCompile(`\[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d
152154

153155
const timeFmt = "2006-01-02T15:04:05.000000000Z"
154156

155-
func printTillFirstTimestamp(logScanner *bufio.Scanner) (time.Time, string) {
157+
func advanceToNextTimestamp(w io.Writer, logScanner *bufio.Scanner) (time.Time, string) {
156158
if logScanner == nil {
157159
return time.Time{}, ""
158160
}
@@ -163,18 +165,18 @@ func printTillFirstTimestamp(logScanner *bufio.Scanner) (time.Time, string) {
163165
if len(match) == 2 {
164166
timestamp, err := time.Parse(timeFmt, match[1])
165167
if err != nil {
166-
println(text)
168+
fmt.Fprintln(w, text)
167169
continue
168170
}
169171
return timestamp, text
170172
} else {
171-
println(text)
173+
fmt.Fprintln(w, text)
172174
}
173175
}
174176
return time.Time{}, ""
175177
}
176178

177-
func printMergedLog(logPath1, logPath2 string) error {
179+
func printMergedLog(w io.Writer, logPath1, logPath2 string) error {
178180
var logScanner1 *bufio.Scanner
179181
if logPath1 != "" {
180182
logFile1, err := os.Open(logPath1)
@@ -195,31 +197,32 @@ func printMergedLog(logPath1, logPath2 string) error {
195197

196198
var timestamp1 time.Time
197199
var timestamp2 time.Time
198-
var log1Line string
199-
var log2Name string
200+
var line1 string
201+
var line2 string
200202

201-
timestamp1, log1Line = printTillFirstTimestamp(logScanner1)
202-
timestamp2, log2Name = printTillFirstTimestamp(logScanner2)
203+
timestamp1, line1 = advanceToNextTimestamp(w, logScanner1)
204+
timestamp2, line2 = advanceToNextTimestamp(w, logScanner2)
203205

204-
for log1Line != "" && log2Name != "" {
205-
for log1Line != "" && timestamp1.Before(timestamp2) {
206-
println(log1Line)
207-
timestamp1, log1Line = printTillFirstTimestamp(logScanner1)
208-
}
209-
for log2Name != "" && timestamp2.Before(timestamp1) {
210-
println(log2Name)
211-
timestamp2, log2Name = printTillFirstTimestamp(logScanner2)
206+
for line1 != "" && line2 != "" {
207+
if !timestamp2.Before(timestamp1) {
208+
fmt.Fprintln(w, line1)
209+
timestamp1, line1 = advanceToNextTimestamp(w, logScanner1)
210+
} else {
211+
fmt.Fprintln(w, line2)
212+
timestamp2, line2 = advanceToNextTimestamp(w, logScanner2)
212213
}
213214
}
214215

215-
if log1Line != "" {
216+
if line1 != "" {
217+
fmt.Fprintln(w, line1)
216218
for logScanner1.Scan() {
217-
println(logScanner1.Text())
219+
fmt.Fprintln(w, logScanner1.Text())
218220
}
219221
}
220-
if log2Name != "" {
222+
if line2 != "" {
223+
fmt.Fprintln(w, line2)
221224
for logScanner2.Scan() {
222-
println(logScanner2.Text())
225+
fmt.Fprintln(w, logScanner2.Text())
223226
}
224227
}
225228

cmd/cli/commands/logs_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package commands
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
"time"
11+
)
12+
13+
func TestMergedLogEqualTimestamps(t *testing.T) {
14+
f1 := filepath.Join(t.TempDir(), "a.log")
15+
if err := os.WriteFile(f1, []byte("[2026-03-09T12:00:00.000000000Z] line a\n"), 0644); err != nil {
16+
t.Fatal(err)
17+
}
18+
f2 := filepath.Join(t.TempDir(), "b.log")
19+
if err := os.WriteFile(f2, []byte("[2026-03-09T12:00:00.000000000Z] line b\n"), 0644); err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
done := make(chan error, 1)
24+
go func() { done <- printMergedLog(io.Discard, f1, f2) }()
25+
26+
select {
27+
case <-done:
28+
// ok
29+
case <-time.After(3 * time.Second):
30+
t.Fatal("printMergedLog hung — equal timestamp deadlock")
31+
}
32+
}
33+
34+
func TestMergedLogInterleavedTimestamps(t *testing.T) {
35+
f1 := filepath.Join(t.TempDir(), "a.log")
36+
if err := os.WriteFile(f1, []byte(strings.Join([]string{
37+
"[2026-03-09T12:00:00.000000000Z] a1",
38+
"[2026-03-09T12:00:02.000000000Z] a2",
39+
"[2026-03-09T12:00:04.000000000Z] a3",
40+
}, "\n")+"\n"), 0644); err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
f2 := filepath.Join(t.TempDir(), "b.log")
45+
if err := os.WriteFile(f2, []byte(strings.Join([]string{
46+
"[2026-03-09T12:00:00.000000000Z] b1",
47+
"[2026-03-09T12:00:01.000000000Z] b2",
48+
"[2026-03-09T12:00:03.000000000Z] b3",
49+
"[2026-03-09T12:00:05.000000000Z] b4",
50+
}, "\n")+"\n"), 0644); err != nil {
51+
t.Fatal(err)
52+
}
53+
54+
var buf bytes.Buffer
55+
err := printMergedLog(&buf, f1, f2)
56+
if err != nil {
57+
t.Fatal(err)
58+
}
59+
60+
got := strings.TrimSpace(buf.String())
61+
want := strings.Join([]string{
62+
"[2026-03-09T12:00:00.000000000Z] a1",
63+
"[2026-03-09T12:00:00.000000000Z] b1",
64+
"[2026-03-09T12:00:01.000000000Z] b2",
65+
"[2026-03-09T12:00:02.000000000Z] a2",
66+
"[2026-03-09T12:00:03.000000000Z] b3",
67+
"[2026-03-09T12:00:04.000000000Z] a3",
68+
"[2026-03-09T12:00:05.000000000Z] b4",
69+
}, "\n")
70+
71+
if got != want {
72+
t.Errorf("wrong merge order:\ngot:\n%s\nwant:\n%s", got, want)
73+
}
74+
}

0 commit comments

Comments
 (0)