github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/logs/log.go (about)

     1  package logs
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/containers/podman/v2/libpod/logs/reversereader"
    12  	"github.com/hpcloud/tail"
    13  	"github.com/pkg/errors"
    14  	"github.com/sirupsen/logrus"
    15  )
    16  
    17  const (
    18  	// LogTimeFormat is the time format used in the log.
    19  	// It is a modified version of RFC3339Nano that guarantees trailing
    20  	// zeroes are not trimmed, taken from
    21  	// https://github.com/golang/go/issues/19635
    22  	LogTimeFormat = "2006-01-02T15:04:05.000000000Z07:00"
    23  
    24  	// PartialLogType signifies a log line that exceeded the buffer
    25  	// length and needed to spill into a new line
    26  	PartialLogType = "P"
    27  
    28  	// FullLogType signifies a log line is full
    29  	FullLogType = "F"
    30  )
    31  
    32  // LogOptions is the options you can use for logs
    33  type LogOptions struct {
    34  	Details    bool
    35  	Follow     bool
    36  	Since      time.Time
    37  	Tail       int64
    38  	Timestamps bool
    39  	Multi      bool
    40  	WaitGroup  *sync.WaitGroup
    41  	UseName    bool
    42  }
    43  
    44  // LogLine describes the information for each line of a log
    45  type LogLine struct {
    46  	Device       string
    47  	ParseLogType string
    48  	Time         time.Time
    49  	Msg          string
    50  	CID          string
    51  	CName        string
    52  }
    53  
    54  // GetLogFile returns an hp tail for a container given options
    55  func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) {
    56  	var (
    57  		whence  int
    58  		err     error
    59  		logTail []*LogLine
    60  	)
    61  	// whence 0=origin, 2=end
    62  	if options.Tail >= 0 {
    63  		whence = 2
    64  	}
    65  	if options.Tail > 0 {
    66  		logTail, err = getTailLog(path, int(options.Tail))
    67  		if err != nil {
    68  			return nil, nil, err
    69  		}
    70  	}
    71  	seek := tail.SeekInfo{
    72  		Offset: 0,
    73  		Whence: whence,
    74  	}
    75  
    76  	t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger})
    77  	return t, logTail, err
    78  }
    79  
    80  func getTailLog(path string, tail int) ([]*LogLine, error) {
    81  	var (
    82  		nlls       []*LogLine
    83  		nllCounter int
    84  		leftover   string
    85  		partial    string
    86  		tailLog    []*LogLine
    87  	)
    88  	f, err := os.Open(path)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	rr, err := reversereader.NewReverseReader(f)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	inputs := make(chan []string)
    98  	go func() {
    99  		for {
   100  			s, err := rr.Read()
   101  			if err != nil {
   102  				if errors.Cause(err) == io.EOF {
   103  					inputs <- []string{leftover}
   104  				} else {
   105  					logrus.Error(err)
   106  				}
   107  				close(inputs)
   108  				if err := f.Close(); err != nil {
   109  					logrus.Error(err)
   110  				}
   111  				break
   112  			}
   113  			line := strings.Split(s+leftover, "\n")
   114  			if len(line) > 1 {
   115  				inputs <- line[1:]
   116  			}
   117  			leftover = line[0]
   118  		}
   119  	}()
   120  
   121  	for i := range inputs {
   122  		// the incoming array is FIFO; we want FIFO so
   123  		// reverse the slice read order
   124  		for j := len(i) - 1; j >= 0; j-- {
   125  			// lines that are "" are junk
   126  			if len(i[j]) < 1 {
   127  				continue
   128  			}
   129  			// read the content in reverse and add each nll until we have the same
   130  			// number of F type messages as the desired tail
   131  			nll, err := NewLogLine(i[j])
   132  			if err != nil {
   133  				return nil, err
   134  			}
   135  			nlls = append(nlls, nll)
   136  			if !nll.Partial() {
   137  				nllCounter++
   138  			}
   139  		}
   140  		// if we have enough loglines, we can hangup
   141  		if nllCounter >= tail {
   142  			break
   143  		}
   144  	}
   145  
   146  	// re-assemble the log lines and trim (if needed) to the
   147  	// tail length
   148  	for _, nll := range nlls {
   149  		if nll.Partial() {
   150  			partial += nll.Msg
   151  		} else {
   152  			nll.Msg += partial
   153  			// prepend because we need to reverse the order again to FIFO
   154  			tailLog = append([]*LogLine{nll}, tailLog...)
   155  			partial = ""
   156  		}
   157  		if len(tailLog) == tail {
   158  			break
   159  		}
   160  	}
   161  	return tailLog, nil
   162  }
   163  
   164  // String converts a logline to a string for output given whether a detail
   165  // bool is specified.
   166  func (l *LogLine) String(options *LogOptions) string {
   167  	var out string
   168  	if options.Multi {
   169  		if options.UseName {
   170  			out = l.CName + " "
   171  		} else {
   172  			cid := l.CID
   173  			if len(cid) > 12 {
   174  				cid = cid[:12]
   175  			}
   176  			out = fmt.Sprintf("%s ", cid)
   177  		}
   178  	}
   179  	if options.Timestamps {
   180  		out += fmt.Sprintf("%s ", l.Time.Format(LogTimeFormat))
   181  	}
   182  	return out + l.Msg
   183  }
   184  
   185  // Since returns a bool as to whether a log line occurred after a given time
   186  func (l *LogLine) Since(since time.Time) bool {
   187  	return l.Time.After(since)
   188  }
   189  
   190  // NewLogLine creates a logLine struct from a container log string
   191  func NewLogLine(line string) (*LogLine, error) {
   192  	splitLine := strings.Split(line, " ")
   193  	if len(splitLine) < 4 {
   194  		return nil, errors.Errorf("'%s' is not a valid container log line", line)
   195  	}
   196  	logTime, err := time.Parse(LogTimeFormat, splitLine[0])
   197  	if err != nil {
   198  		return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0])
   199  	}
   200  	l := LogLine{
   201  		Time:         logTime,
   202  		Device:       splitLine[1],
   203  		ParseLogType: splitLine[2],
   204  		Msg:          strings.Join(splitLine[3:], " "),
   205  	}
   206  	return &l, nil
   207  }
   208  
   209  // Partial returns a bool if the log line is a partial log type
   210  func (l *LogLine) Partial() bool {
   211  	return l.ParseLogType == PartialLogType
   212  }