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 }