mirror of
https://github.com/containers/podman.git
synced 2025-05-20 08:36:23 +08:00

Fix a race in the k8s-file logs driver. When "following" the logs, Podman will print the container's logs until the end. Previously, Podman logged until the state transitioned into something non-running which opened up a race with the container still running, possibly in the "stopping" state. To fix the race, log until we've seen the wait event for the specific container. In that case, conmon will have finished writing all logs to the file, and Podman will read it until EOF. Further tweak the integration tests for testing `logs -f` on a running container. Previously, the test only checked for one of two lines stating that there was a race. Indeed the race was in using `run --rm` where a log file may be removed before we could fully read it. Fixes: #10596 Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
package libpod
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/containers/podman/v3/libpod/define"
|
|
"github.com/containers/podman/v3/libpod/events"
|
|
"github.com/containers/podman/v3/libpod/logs"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Log is a runtime function that can read one or more container logs.
|
|
func (r *Runtime) Log(ctx context.Context, containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
|
|
for _, ctr := range containers {
|
|
if err := ctr.ReadLog(ctx, options, logChannel); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadLog reads a containers log based on the input options and returns log lines over a channel.
|
|
func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
|
|
switch c.LogDriver() {
|
|
case define.NoLogging:
|
|
return errors.Wrapf(define.ErrNoLogs, "this container is using the 'none' log driver, cannot read logs")
|
|
case define.JournaldLogging:
|
|
return c.readFromJournal(ctx, options, logChannel)
|
|
case define.JSONLogging:
|
|
// TODO provide a separate implementation of this when Conmon
|
|
// has support.
|
|
fallthrough
|
|
case define.KubernetesLogging, "":
|
|
return c.readFromLogFile(ctx, options, logChannel)
|
|
default:
|
|
return errors.Wrapf(define.ErrInternal, "unrecognized log driver %q, cannot read logs", c.LogDriver())
|
|
}
|
|
}
|
|
|
|
func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
|
|
t, tailLog, err := logs.GetLogFile(c.LogPath(), options)
|
|
if err != nil {
|
|
// If the log file does not exist, this is not fatal.
|
|
if os.IsNotExist(errors.Cause(err)) {
|
|
return nil
|
|
}
|
|
return errors.Wrapf(err, "unable to read log file %s for %s ", c.ID(), c.LogPath())
|
|
}
|
|
options.WaitGroup.Add(1)
|
|
if len(tailLog) > 0 {
|
|
for _, nll := range tailLog {
|
|
nll.CID = c.ID()
|
|
nll.CName = c.Name()
|
|
if nll.Since(options.Since) {
|
|
logChannel <- nll
|
|
}
|
|
}
|
|
}
|
|
|
|
go func() {
|
|
defer options.WaitGroup.Done()
|
|
|
|
var partial string
|
|
for line := range t.Lines {
|
|
select {
|
|
case <-ctx.Done():
|
|
// the consumer has cancelled
|
|
return
|
|
default:
|
|
// fallthrough
|
|
}
|
|
nll, err := logs.NewLogLine(line.Text)
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
continue
|
|
}
|
|
if nll.Partial() {
|
|
partial += nll.Msg
|
|
continue
|
|
} else if !nll.Partial() && len(partial) > 0 {
|
|
nll.Msg = partial + nll.Msg
|
|
partial = ""
|
|
}
|
|
nll.CID = c.ID()
|
|
nll.CName = c.Name()
|
|
if nll.Since(options.Since) {
|
|
logChannel <- nll
|
|
}
|
|
}
|
|
}()
|
|
// Check if container is still running or paused
|
|
if options.Follow {
|
|
state, err := c.State()
|
|
if err != nil || state != define.ContainerStateRunning {
|
|
// If the container isn't running or if we encountered
|
|
// an error getting its state, instruct the logger to
|
|
// read the file until EOF.
|
|
tailError := t.StopAtEOF()
|
|
if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
|
|
logrus.Error(tailError)
|
|
}
|
|
if errors.Cause(err) != define.ErrNoSuchCtr {
|
|
logrus.Error(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// The container is running, so we need to wait until the container exited
|
|
go func() {
|
|
eventChannel := make(chan *events.Event)
|
|
eventOptions := events.ReadOptions{
|
|
EventChannel: eventChannel,
|
|
Filters: []string{"event=died", "container=" + c.ID()},
|
|
Stream: true,
|
|
}
|
|
go func() {
|
|
if err := c.runtime.Events(ctx, eventOptions); err != nil {
|
|
logrus.Errorf("Error waiting for container to exit: %v", err)
|
|
}
|
|
}()
|
|
// Now wait for the died event and signal to finish
|
|
// reading the log until EOF.
|
|
<-eventChannel
|
|
tailError := t.StopAtEOF()
|
|
if tailError != nil && fmt.Sprintf("%v", tailError) != "tail: stop at eof" {
|
|
logrus.Error(tailError)
|
|
}
|
|
}()
|
|
}
|
|
return nil
|
|
}
|