github.com/containers/podman/v4@v4.9.4/libpod/container_log.go (about)

     1  //go:build !remote
     2  // +build !remote
     3  
     4  package libpod
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"time"
    12  
    13  	"github.com/containers/podman/v4/libpod/define"
    14  	"github.com/containers/podman/v4/libpod/events"
    15  	"github.com/containers/podman/v4/libpod/logs"
    16  	systemdDefine "github.com/containers/podman/v4/pkg/systemd/define"
    17  	"github.com/nxadm/tail"
    18  	"github.com/nxadm/tail/watch"
    19  	"github.com/sirupsen/logrus"
    20  )
    21  
    22  // logDrivers stores the currently available log drivers, do not modify
    23  var logDrivers []string
    24  
    25  func init() {
    26  	logDrivers = append(logDrivers, define.KubernetesLogging, define.NoLogging, define.PassthroughLogging)
    27  }
    28  
    29  // Log is a runtime function that can read one or more container logs.
    30  func (r *Runtime) Log(ctx context.Context, containers []*Container, options *logs.LogOptions, logChannel chan *logs.LogLine) error {
    31  	for c, ctr := range containers {
    32  		if err := ctr.ReadLog(ctx, options, logChannel, int64(c)); err != nil {
    33  			return err
    34  		}
    35  	}
    36  	return nil
    37  }
    38  
    39  // ReadLog reads a container's log based on the input options and returns log lines over a channel.
    40  func (c *Container) ReadLog(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error {
    41  	switch c.LogDriver() {
    42  	case define.PassthroughLogging:
    43  		// if running under systemd fallback to a more native journald reading
    44  		if unitName, ok := c.config.Labels[systemdDefine.EnvVariable]; ok {
    45  			return c.readFromJournal(ctx, options, logChannel, colorID, unitName)
    46  		}
    47  		return fmt.Errorf("this container is using the 'passthrough' log driver, cannot read logs: %w", define.ErrNoLogs)
    48  	case define.NoLogging:
    49  		return fmt.Errorf("this container is using the 'none' log driver, cannot read logs: %w", define.ErrNoLogs)
    50  	case define.JournaldLogging:
    51  		return c.readFromJournal(ctx, options, logChannel, colorID, "")
    52  	case define.JSONLogging:
    53  		// TODO provide a separate implementation of this when Conmon
    54  		// has support.
    55  		fallthrough
    56  	case define.KubernetesLogging, "":
    57  		return c.readFromLogFile(ctx, options, logChannel, colorID)
    58  	default:
    59  		return fmt.Errorf("unrecognized log driver %q, cannot read logs: %w", c.LogDriver(), define.ErrInternal)
    60  	}
    61  }
    62  
    63  func (c *Container) readFromLogFile(ctx context.Context, options *logs.LogOptions, logChannel chan *logs.LogLine, colorID int64) error {
    64  	t, tailLog, err := logs.GetLogFile(c.LogPath(), options)
    65  	if err != nil {
    66  		// If the log file does not exist, this is not fatal.
    67  		if errors.Is(err, os.ErrNotExist) {
    68  			return nil
    69  		}
    70  		return fmt.Errorf("unable to read log file %s for %s : %w", c.ID(), c.LogPath(), err)
    71  	}
    72  	options.WaitGroup.Add(1)
    73  	go func() {
    74  		if options.Until.After(time.Now()) {
    75  			time.Sleep(time.Until(options.Until))
    76  			if err := t.Stop(); err != nil {
    77  				logrus.Errorf("Stopping logger: %v", err)
    78  			}
    79  		}
    80  	}()
    81  
    82  	go func() {
    83  		for _, nll := range tailLog {
    84  			nll.CID = c.ID()
    85  			nll.CName = c.Name()
    86  			nll.ColorID = colorID
    87  			if nll.Since(options.Since) && nll.Until(options.Until) {
    88  				logChannel <- nll
    89  			}
    90  		}
    91  		defer options.WaitGroup.Done()
    92  		var line *tail.Line
    93  		var ok bool
    94  		for {
    95  			select {
    96  			case <-ctx.Done():
    97  				// the consumer has cancelled
    98  				t.Kill(errors.New("hangup by client"))
    99  				return
   100  			case line, ok = <-t.Lines:
   101  				if !ok {
   102  					// channel was closed
   103  					return
   104  				}
   105  			}
   106  			nll, err := logs.NewLogLine(line.Text)
   107  			if err != nil {
   108  				logrus.Errorf("Getting new log line: %v", err)
   109  				continue
   110  			}
   111  			nll.CID = c.ID()
   112  			nll.CName = c.Name()
   113  			nll.ColorID = colorID
   114  			if nll.Since(options.Since) && nll.Until(options.Until) {
   115  				logChannel <- nll
   116  			}
   117  		}
   118  	}()
   119  	// Check if container is still running or paused
   120  	if options.Follow {
   121  		// If the container isn't running or if we encountered an error
   122  		// getting its state, instruct the logger to read the file
   123  		// until EOF.
   124  		state, err := c.State()
   125  		if err != nil || state != define.ContainerStateRunning {
   126  			if err != nil && !errors.Is(err, define.ErrNoSuchCtr) {
   127  				logrus.Errorf("Getting container state: %v", err)
   128  			}
   129  			go func() {
   130  				// Make sure to wait at least for the poll duration
   131  				// before stopping the file logger (see #10675).
   132  				time.Sleep(watch.POLL_DURATION)
   133  				tailError := t.StopAtEOF()
   134  				if tailError != nil && tailError.Error() != "tail: stop at eof" {
   135  					logrus.Errorf("Stopping logger: %v", tailError)
   136  				}
   137  			}()
   138  			return nil
   139  		}
   140  
   141  		// The container is running, so we need to wait until the container exited
   142  		go func() {
   143  			eventChannel := make(chan *events.Event)
   144  			eventOptions := events.ReadOptions{
   145  				EventChannel: eventChannel,
   146  				Filters:      []string{"event=died", "container=" + c.ID()},
   147  				Stream:       true,
   148  			}
   149  			go func() {
   150  				if err := c.runtime.Events(ctx, eventOptions); err != nil {
   151  					logrus.Errorf("Waiting for container to exit: %v", err)
   152  				}
   153  			}()
   154  			// Now wait for the died event and signal to finish
   155  			// reading the log until EOF.
   156  			<-eventChannel
   157  			// Make sure to wait at least for the poll duration
   158  			// before stopping the file logger (see #10675).
   159  			time.Sleep(watch.POLL_DURATION)
   160  			tailError := t.StopAtEOF()
   161  			if tailError != nil && tailError.Error() != "tail: stop at eof" {
   162  				logrus.Errorf("Stopping logger: %v", tailError)
   163  			}
   164  		}()
   165  	}
   166  	return nil
   167  }