github.com/AbhinandanKurakure/podman/v3@v3.4.10/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/v3/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  	Until      time.Time
    38  	Tail       int64
    39  	Timestamps bool
    40  	Multi      bool
    41  	WaitGroup  *sync.WaitGroup
    42  	UseName    bool
    43  }
    44  
    45  // LogLine describes the information for each line of a log
    46  type LogLine struct {
    47  	Device       string
    48  	ParseLogType string
    49  	Time         time.Time
    50  	Msg          string
    51  	CID          string
    52  	CName        string
    53  }
    54  
    55  // GetLogFile returns an hp tail for a container given options
    56  func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) {
    57  	var (
    58  		whence  int
    59  		err     error
    60  		logTail []*LogLine
    61  	)
    62  	// whence 0=origin, 2=end
    63  	if options.Tail >= 0 {
    64  		whence = 2
    65  	}
    66  	if options.Tail > 0 {
    67  		logTail, err = getTailLog(path, int(options.Tail))
    68  		if err != nil {
    69  			return nil, nil, err
    70  		}
    71  	}
    72  	seek := tail.SeekInfo{
    73  		Offset: 0,
    74  		Whence: whence,
    75  	}
    76  
    77  	t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger, ReOpen: options.Follow})
    78  	return t, logTail, err
    79  }
    80  
    81  func getTailLog(path string, tail int) ([]*LogLine, error) {
    82  	var (
    83  		nlls       []*LogLine
    84  		nllCounter int
    85  		leftover   string
    86  		partial    string
    87  		tailLog    []*LogLine
    88  	)
    89  	f, err := os.Open(path)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	rr, err := reversereader.NewReverseReader(f)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	inputs := make(chan []string)
    99  	go func() {
   100  		for {
   101  			s, err := rr.Read()
   102  			if err != nil {
   103  				if errors.Cause(err) == io.EOF {
   104  					inputs <- []string{leftover}
   105  				} else {
   106  					logrus.Error(err)
   107  				}
   108  				close(inputs)
   109  				if err := f.Close(); err != nil {
   110  					logrus.Error(err)
   111  				}
   112  				break
   113  			}
   114  			line := strings.Split(s+leftover, "\n")
   115  			if len(line) > 1 {
   116  				inputs <- line[1:]
   117  			}
   118  			leftover = line[0]
   119  		}
   120  	}()
   121  
   122  	for i := range inputs {
   123  		// the incoming array is FIFO; we want FIFO so
   124  		// reverse the slice read order
   125  		for j := len(i) - 1; j >= 0; j-- {
   126  			// lines that are "" are junk
   127  			if len(i[j]) < 1 {
   128  				continue
   129  			}
   130  			// read the content in reverse and add each nll until we have the same
   131  			// number of F type messages as the desired tail
   132  			nll, err := NewLogLine(i[j])
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			nlls = append(nlls, nll)
   137  			if !nll.Partial() {
   138  				nllCounter++
   139  			}
   140  		}
   141  		// if we have enough log lines, we can hangup
   142  		if nllCounter >= tail {
   143  			break
   144  		}
   145  	}
   146  
   147  	// re-assemble the log lines and trim (if needed) to the
   148  	// tail length
   149  	for _, nll := range nlls {
   150  		if nll.Partial() {
   151  			partial += nll.Msg
   152  		} else {
   153  			nll.Msg += partial
   154  			// prepend because we need to reverse the order again to FIFO
   155  			tailLog = append([]*LogLine{nll}, tailLog...)
   156  			partial = ""
   157  		}
   158  		if len(tailLog) == tail {
   159  			break
   160  		}
   161  	}
   162  	return tailLog, nil
   163  }
   164  
   165  // String converts a log line to a string for output given whether a detail
   166  // bool is specified.
   167  func (l *LogLine) String(options *LogOptions) string {
   168  	var out string
   169  	if options.Multi {
   170  		if options.UseName {
   171  			out = l.CName + " "
   172  		} else {
   173  			cid := l.CID
   174  			if len(cid) > 12 {
   175  				cid = cid[:12]
   176  			}
   177  			out = fmt.Sprintf("%s ", cid)
   178  		}
   179  	}
   180  	if options.Timestamps {
   181  		out += fmt.Sprintf("%s ", l.Time.Format(LogTimeFormat))
   182  	}
   183  	return out + l.Msg
   184  }
   185  
   186  // Since returns a bool as to whether a log line occurred after a given time
   187  func (l *LogLine) Since(since time.Time) bool {
   188  	return l.Time.After(since) || since.IsZero()
   189  }
   190  
   191  // Until returns a bool as to whether a log line occurred before a given time
   192  func (l *LogLine) Until(until time.Time) bool {
   193  	return l.Time.Before(until) || until.IsZero()
   194  }
   195  
   196  // NewLogLine creates a logLine struct from a container log string
   197  func NewLogLine(line string) (*LogLine, error) {
   198  	splitLine := strings.Split(line, " ")
   199  	if len(splitLine) < 4 {
   200  		return nil, errors.Errorf("'%s' is not a valid container log line", line)
   201  	}
   202  	logTime, err := time.Parse(LogTimeFormat, splitLine[0])
   203  	if err != nil {
   204  		return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0])
   205  	}
   206  	l := LogLine{
   207  		Time:         logTime,
   208  		Device:       splitLine[1],
   209  		ParseLogType: splitLine[2],
   210  		Msg:          strings.Join(splitLine[3:], " "),
   211  	}
   212  	return &l, nil
   213  }
   214  
   215  // NewJournaldLogLine creates a LogLine from the specified line from journald.
   216  // Note that if withID is set, the first item of the message is considerred to
   217  // be the container ID and set as such.
   218  func NewJournaldLogLine(line string, withID bool) (*LogLine, error) {
   219  	splitLine := strings.Split(line, " ")
   220  	if len(splitLine) < 4 {
   221  		return nil, errors.Errorf("'%s' is not a valid container log line", line)
   222  	}
   223  	logTime, err := time.Parse(LogTimeFormat, splitLine[0])
   224  	if err != nil {
   225  		return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0])
   226  	}
   227  	var msg, id string
   228  	if withID {
   229  		id = splitLine[3]
   230  		msg = strings.Join(splitLine[4:], " ")
   231  	} else {
   232  		msg = strings.Join(splitLine[3:], " ")
   233  		// NO ID
   234  	}
   235  	l := LogLine{
   236  		Time:         logTime,
   237  		Device:       splitLine[1],
   238  		ParseLogType: splitLine[2],
   239  		Msg:          msg,
   240  		CID:          id,
   241  	}
   242  	return &l, nil
   243  }
   244  
   245  // Partial returns a bool if the log line is a partial log type
   246  func (l *LogLine) Partial() bool {
   247  	return l.ParseLogType == PartialLogType
   248  }
   249  
   250  func (l *LogLine) Write(stdout io.Writer, stderr io.Writer, logOpts *LogOptions) {
   251  	switch l.Device {
   252  	case "stdout":
   253  		if stdout != nil {
   254  			fmt.Fprintln(stdout, l.String(logOpts))
   255  		}
   256  	case "stderr":
   257  		if stderr != nil {
   258  			fmt.Fprintln(stderr, l.String(logOpts))
   259  		}
   260  	default:
   261  		// Warn the user if the device type does not match. Most likely the file is corrupted.
   262  		logrus.Warnf("unknown Device type '%s' in log file from Container %s", l.Device, l.CID)
   263  	}
   264  }