Skip to content

Commit 7cd18d2

Browse files
AkihiroSudaclaude
andcommitted
logs: wait for logging binary before reading log file
When a container has exited, `nerdctl logs` now calls WaitForLogger() before reading the JSON log file. This ensures the logging binary (a separate process) has finished writing all log entries to disk. Previously, WaitForLogger was only called in the follow (-f) code path. For non-follow reads like `nerdctl logs --since 60s`, the log file was read immediately without waiting, causing flaky test failures when the logging binary hadn't finished processing the final container output. This fixes TestLogs/since_60s and TestLogs/until_60s which failed intermittently because the log file was empty or incomplete at read time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent aed2960 commit 7cd18d2

File tree

1 file changed

+38
-28
lines changed

1 file changed

+38
-28
lines changed

pkg/cmd/container/logs.go

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -71,36 +71,46 @@ func Logs(ctx context.Context, client *containerd.Client, container string, opti
7171
}
7272

7373
follow := options.Follow
74-
if follow {
75-
task, err := found.Container.Task(ctx, nil)
74+
running := false
75+
task, err := found.Container.Task(ctx, nil)
76+
if err != nil {
77+
if !errdefs.IsNotFound(err) {
78+
return err
79+
}
80+
} else {
81+
status, err := task.Status(ctx)
7682
if err != nil {
77-
if !errdefs.IsNotFound(err) {
78-
return err
79-
}
80-
follow = false
81-
} else {
82-
status, err := task.Status(ctx)
83-
if err != nil {
84-
return err
85-
}
86-
if status.Status != containerd.Running {
87-
follow = false
88-
} else {
89-
waitCh, err := task.Wait(ctx)
90-
if err != nil {
91-
return fmt.Errorf("failed to get wait channel for task %#v: %w", task, err)
92-
}
83+
return err
84+
}
85+
running = status.Status == containerd.Running
86+
}
9387

94-
// Setup goroutine to send stop event if container task finishes:
95-
go func() {
96-
<-waitCh
97-
// Wait for logger to process remaining logs after container exit
98-
if err = logging.WaitForLogger(dataStore, l[labels.Namespace], found.Container.ID()); err != nil {
99-
log.G(ctx).WithError(err).Error("failed to wait for logger shutdown")
100-
}
101-
log.G(ctx).Debugf("container task has finished, sending kill signal to log viewer")
102-
stopChannel <- os.Interrupt
103-
}()
88+
if follow && running {
89+
waitCh, err := task.Wait(ctx)
90+
if err != nil {
91+
return fmt.Errorf("failed to get wait channel for task %#v: %w", task, err)
92+
}
93+
94+
// Setup goroutine to send stop event if container task finishes:
95+
go func() {
96+
<-waitCh
97+
// Wait for logger to process remaining logs after container exit
98+
if err = logging.WaitForLogger(dataStore, l[labels.Namespace], found.Container.ID()); err != nil {
99+
log.G(ctx).WithError(err).Error("failed to wait for logger shutdown")
100+
}
101+
log.G(ctx).Debugf("container task has finished, sending kill signal to log viewer")
102+
stopChannel <- os.Interrupt
103+
}()
104+
} else {
105+
follow = false
106+
if !running {
107+
// Container is not running. Wait for the logging binary
108+
// to finish writing all log entries before reading the
109+
// log file. Without this, we may read an incomplete log
110+
// file because the logging binary (a separate process)
111+
// may still be processing the final container output.
112+
if err := logging.WaitForLogger(dataStore, l[labels.Namespace], found.Container.ID()); err != nil {
113+
log.G(ctx).WithError(err).Warn("failed to wait for logger")
104114
}
105115
}
106116
}

0 commit comments

Comments
 (0)