Skip to content

Commit 1c9c66d

Browse files
authored
Merge pull request #3200 from xyz-li/main
fix: output log even log is rotated
2 parents d864690 + 4b50583 commit 1c9c66d

File tree

10 files changed

+534
-145
lines changed

10 files changed

+534
-145
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
github.com/fahedouch/go-logrotate v0.2.1
3838
github.com/fatih/color v1.17.0
3939
github.com/fluent/fluent-logger-golang v1.9.0
40+
github.com/fsnotify/fsnotify v1.7.0
4041
github.com/ipfs/go-cid v0.4.1
4142
github.com/klauspost/compress v1.17.9
4243
github.com/mattn/go-isatty v0.0.20

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
109109
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
110110
github.com/fluent/fluent-logger-golang v1.9.0 h1:zUdY44CHX2oIUc7VTNZc+4m+ORuO/mldQDA7czhWXEg=
111111
github.com/fluent/fluent-logger-golang v1.9.0/go.mod h1:2/HCT/jTy78yGyeNGQLGQsjF3zzzAuy6Xlk6FCMV5eU=
112+
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
113+
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
112114
github.com/go-jose/go-jose/v4 v4.0.3 h1:o8aphO8Hv6RPmH+GfzVuyf7YXSBibp+8YyHdOoDESGo=
113115
github.com/go-jose/go-jose/v4 v4.0.3/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
114116
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=

pkg/logging/cri_logger.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ package logging
2525
import (
2626
"bufio"
2727
"bytes"
28+
"context"
2829
"errors"
2930
"fmt"
3031
"io"
@@ -35,6 +36,7 @@ import (
3536

3637
"github.com/containerd/log"
3738
"github.com/containerd/nerdctl/v2/pkg/logging/tail"
39+
"github.com/fsnotify/fsnotify"
3840
)
3941

4042
// LogStreamType is the type of the stream in CRI container log.
@@ -45,6 +47,9 @@ const (
4547
Stdout LogStreamType = "stdout"
4648
// Stderr is the stream type for stderr.
4749
Stderr LogStreamType = "stderr"
50+
51+
// logForceCheckPeriod is the period to check for a new read
52+
logForceCheckPeriod = 1 * time.Second
4853
)
4954

5055
// LogTag is the tag of a log line in CRI container log.
@@ -89,7 +94,9 @@ func ReadLogs(opts *LogViewOptions, stdout, stderr io.Writer, stopChannel chan o
8994
if err != nil {
9095
return fmt.Errorf("failed to open log file %q: %v", logPath, err)
9196
}
92-
defer f.Close()
97+
defer func() {
98+
f.Close()
99+
}()
93100

94101
// Search start point based on tail line.
95102
start, err := tail.FindTailLineStartIndex(f, opts.Tail)
@@ -101,6 +108,8 @@ func ReadLogs(opts *LogViewOptions, stdout, stderr io.Writer, stopChannel chan o
101108
return fmt.Errorf("failed to seek in log file %q: %v", logPath, err)
102109
}
103110

111+
var watcher *fsnotify.Watcher
112+
104113
limitedMode := (opts.Tail > 0) && (!opts.Follow)
105114
limitedNum := opts.Tail
106115
// Start parsing the logs.
@@ -110,6 +119,9 @@ func ReadLogs(opts *LogViewOptions, stdout, stderr io.Writer, stopChannel chan o
110119
isNewLine := true
111120
writer := newLogWriter(stdout, stderr, opts)
112121
msg := &logMessage{}
122+
baseName := filepath.Base(logPath)
123+
dir := filepath.Dir(logPath)
124+
113125
for {
114126
select {
115127
case <-stopChannel:
@@ -126,13 +138,48 @@ func ReadLogs(opts *LogViewOptions, stdout, stderr io.Writer, stopChannel chan o
126138
return fmt.Errorf("failed to read log file %q: %v", logPath, err)
127139
}
128140
if opts.Follow {
129-
130141
// Reset seek so that if this is an incomplete line,
131142
// it will be read again.
132143
if _, err := f.Seek(-int64(len(l)), io.SeekCurrent); err != nil {
133144
return fmt.Errorf("failed to reset seek in log file %q: %v", logPath, err)
134145
}
135146

147+
if watcher == nil {
148+
// Initialize the watcher if it has not been initialized yet.
149+
if watcher, err = NewLogFileWatcher(dir); err != nil {
150+
return err
151+
}
152+
defer watcher.Close()
153+
// If we just created the watcher, try again to read as we might have missed
154+
// the event.
155+
continue
156+
}
157+
158+
var recreated bool
159+
// Wait until the next log change.
160+
recreated, err = startTail(context.Background(), baseName, watcher)
161+
if err != nil {
162+
return err
163+
}
164+
if recreated {
165+
newF, err := openFileShareDelete(logPath)
166+
if err != nil {
167+
if errors.Is(err, os.ErrNotExist) {
168+
//If the user application outputs logs too quickly,
169+
//There is a slight possibility that nerdctl has just rotated the log file,
170+
//try opening it once more.
171+
time.Sleep(10 * time.Millisecond)
172+
}
173+
newF, err = openFileShareDelete(logPath)
174+
if err != nil {
175+
return fmt.Errorf("failed to open cri logfile %q: %w", logPath, err)
176+
}
177+
}
178+
f.Close()
179+
f = newF
180+
r = bufio.NewReader(f)
181+
}
182+
136183
// If the container exited consume data until the next EOF
137184
continue
138185
}

pkg/logging/cri_logger_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"fmt"
2929
"io"
3030
"os"
31+
"path/filepath"
3132
"reflect"
3233
"testing"
3334
"time"
@@ -230,3 +231,85 @@ func TestReadLogsLimitsWithTimestamps(t *testing.T) {
230231
t.Errorf("should have two lines, lineCount= %d", lineCount)
231232
}
232233
}
234+
235+
func TestReadRotatedLog(t *testing.T) {
236+
tmpDir := t.TempDir()
237+
file, err := os.CreateTemp(tmpDir, "logfile")
238+
if err != nil {
239+
t.Errorf("unable to create temp file, error: %s", err.Error())
240+
}
241+
stdoutBuf := &bytes.Buffer{}
242+
stderrBuf := &bytes.Buffer{}
243+
containerStoped := make(chan os.Signal)
244+
// Start to follow the container's log.
245+
fileName := file.Name()
246+
go func() {
247+
lvOpts := &LogViewOptions{
248+
Follow: true,
249+
LogPath: fileName,
250+
}
251+
_ = ReadLogs(lvOpts, stdoutBuf, stderrBuf, containerStoped)
252+
}()
253+
254+
// log in stdout
255+
expectedStdout := "line0line2line4line6line8"
256+
// log in stderr
257+
expectedStderr := "line1line3line5line7line9"
258+
259+
dir := filepath.Dir(file.Name())
260+
baseName := filepath.Base(file.Name())
261+
262+
// Write 10 lines to log file.
263+
// Let ReadLogs start.
264+
time.Sleep(50 * time.Millisecond)
265+
266+
for line := 0; line < 10; line++ {
267+
// Write the first three lines to log file
268+
now := time.Now().Format(time.RFC3339Nano)
269+
if line%2 == 0 {
270+
file.WriteString(fmt.Sprintf(
271+
"%s stdout P line%d\n", now, line))
272+
} else {
273+
file.WriteString(fmt.Sprintf(
274+
"%s stderr P line%d\n", now, line))
275+
}
276+
277+
time.Sleep(1 * time.Millisecond)
278+
279+
if line == 5 {
280+
file.Close()
281+
// Pretend to rotate the log.
282+
rotatedName := fmt.Sprintf("%s.%s", baseName, time.Now().Format("220060102-150405"))
283+
rotatedName = filepath.Join(dir, rotatedName)
284+
if err := os.Rename(filepath.Join(dir, baseName), rotatedName); err != nil {
285+
t.Errorf("failed to rotate log %q to %q, error: %s", file.Name(), rotatedName, err.Error())
286+
return
287+
}
288+
289+
time.Sleep(20 * time.Millisecond)
290+
newF := filepath.Join(dir, baseName)
291+
if file, err = os.Create(newF); err != nil {
292+
t.Errorf("unable to create new log file, error: %s", err.Error())
293+
return
294+
}
295+
}
296+
}
297+
298+
// Finished writing into the file, close it, so we can delete it later.
299+
err = file.Close()
300+
if err != nil {
301+
t.Errorf("could not close file, error: %s", err.Error())
302+
}
303+
304+
time.Sleep(2 * time.Second)
305+
// Make the function ReadLogs end.
306+
close(containerStoped)
307+
308+
if expectedStdout != stdoutBuf.String() {
309+
t.Errorf("expected: %s, acoutal: %s", expectedStdout, stdoutBuf.String())
310+
}
311+
312+
if expectedStderr != stderrBuf.String() {
313+
t.Errorf("expected: %s, acoutal: %s", expectedStderr, stderrBuf.String())
314+
}
315+
}

0 commit comments

Comments
 (0)