mirror of https://github.com/containers/podman.git
				
				
				
			
		
			
				
	
	
		
			230 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Go
		
	
	
	
| package libpod
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/hpcloud/tail"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// logTimeFormat is the time format used in the log.
 | |
| 	// It is a modified version of RFC3339Nano that guarantees trailing
 | |
| 	// zeroes are not trimmed, taken from
 | |
| 	// https://github.com/golang/go/issues/19635
 | |
| 	logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00"
 | |
| 
 | |
| 	// partialLogType signifies a log line that exceeded the buffer
 | |
| 	// length and needed to spill into a new line
 | |
| 	partialLogType = "P"
 | |
| 
 | |
| 	// fullLogType signifies a log line is full
 | |
| 	fullLogType = "F"
 | |
| )
 | |
| 
 | |
| // LogOptions is the options you can use for logs
 | |
| type LogOptions struct {
 | |
| 	Details    bool
 | |
| 	Follow     bool
 | |
| 	Since      time.Time
 | |
| 	Tail       uint64
 | |
| 	Timestamps bool
 | |
| 	Multi      bool
 | |
| 	WaitGroup  *sync.WaitGroup
 | |
| }
 | |
| 
 | |
| // LogLine describes the information for each line of a log
 | |
| type LogLine struct {
 | |
| 	Device       string
 | |
| 	ParseLogType string
 | |
| 	Time         time.Time
 | |
| 	Msg          string
 | |
| 	CID          string
 | |
| }
 | |
| 
 | |
| // Log is a runtime function that can read one or more container logs.
 | |
| func (r *Runtime) Log(containers []*Container, options *LogOptions, logChannel chan *LogLine) error {
 | |
| 	for _, ctr := range containers {
 | |
| 		if err := ctr.ReadLog(options, logChannel); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // ReadLog reads a containers log based on the input options and returns loglines over a channel
 | |
| func (c *Container) ReadLog(options *LogOptions, logChannel chan *LogLine) error {
 | |
| 	// TODO Skip sending logs until journald logs can be read
 | |
| 	// TODO make this not a magic string
 | |
| 	if c.LogDriver() == JournaldLogging {
 | |
| 		return c.readFromJournal(options, logChannel)
 | |
| 	}
 | |
| 	return c.readFromLogFile(options, logChannel)
 | |
| }
 | |
| 
 | |
| func (c *Container) readFromLogFile(options *LogOptions, logChannel chan *LogLine) error {
 | |
| 	t, tailLog, err := 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()
 | |
| 			if nll.Since(options.Since) {
 | |
| 				logChannel <- nll
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	go func() {
 | |
| 		var partial string
 | |
| 		for line := range t.Lines {
 | |
| 			nll, err := newLogLine(line.Text)
 | |
| 			if err != nil {
 | |
| 				logrus.Error(err)
 | |
| 				continue
 | |
| 			}
 | |
| 			if nll.Partial() {
 | |
| 				partial = partial + nll.Msg
 | |
| 				continue
 | |
| 			} else if !nll.Partial() && len(partial) > 1 {
 | |
| 				nll.Msg = partial
 | |
| 				partial = ""
 | |
| 			}
 | |
| 			nll.CID = c.ID()
 | |
| 			if nll.Since(options.Since) {
 | |
| 				logChannel <- nll
 | |
| 			}
 | |
| 		}
 | |
| 		options.WaitGroup.Done()
 | |
| 	}()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // getLogFile returns an hp tail for a container given options
 | |
| func getLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) {
 | |
| 	var (
 | |
| 		whence  int
 | |
| 		err     error
 | |
| 		logTail []*LogLine
 | |
| 	)
 | |
| 	// whence 0=origin, 2=end
 | |
| 	if options.Tail > 0 {
 | |
| 		whence = 2
 | |
| 		logTail, err = getTailLog(path, int(options.Tail))
 | |
| 		if err != nil {
 | |
| 			return nil, nil, err
 | |
| 		}
 | |
| 	}
 | |
| 	seek := tail.SeekInfo{
 | |
| 		Offset: 0,
 | |
| 		Whence: whence,
 | |
| 	}
 | |
| 
 | |
| 	t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger})
 | |
| 	return t, logTail, err
 | |
| }
 | |
| 
 | |
| func getTailLog(path string, tail int) ([]*LogLine, error) {
 | |
| 	var (
 | |
| 		tailLog     []*LogLine
 | |
| 		nlls        []*LogLine
 | |
| 		tailCounter int
 | |
| 		partial     string
 | |
| 	)
 | |
| 	content, err := ioutil.ReadFile(path)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	splitContent := strings.Split(string(content), "\n")
 | |
| 	// We read the content in reverse and add each nll until we have the same
 | |
| 	// number of F type messages as the desired tail
 | |
| 	for i := len(splitContent) - 1; i >= 0; i-- {
 | |
| 		if len(splitContent[i]) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		nll, err := newLogLine(splitContent[i])
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		nlls = append(nlls, nll)
 | |
| 		if !nll.Partial() {
 | |
| 			tailCounter = tailCounter + 1
 | |
| 		}
 | |
| 		if tailCounter == tail {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	// Now we iterate the results and assemble partial messages to become full messages
 | |
| 	for _, nll := range nlls {
 | |
| 		if nll.Partial() {
 | |
| 			partial = partial + nll.Msg
 | |
| 		} else {
 | |
| 			nll.Msg = nll.Msg + partial
 | |
| 			tailLog = append(tailLog, nll)
 | |
| 			partial = ""
 | |
| 		}
 | |
| 	}
 | |
| 	return tailLog, nil
 | |
| }
 | |
| 
 | |
| // String converts a logline to a string for output given whether a detail
 | |
| // bool is specified.
 | |
| func (l *LogLine) String(options *LogOptions) string {
 | |
| 	var out string
 | |
| 	if options.Multi {
 | |
| 		cid := l.CID
 | |
| 		if len(cid) > 12 {
 | |
| 			cid = cid[:12]
 | |
| 		}
 | |
| 		out = fmt.Sprintf("%s ", cid)
 | |
| 	}
 | |
| 	if options.Timestamps {
 | |
| 		out = out + fmt.Sprintf("%s ", l.Time.Format(logTimeFormat))
 | |
| 	}
 | |
| 	return out + l.Msg
 | |
| }
 | |
| 
 | |
| // Since returns a bool as to whether a log line occurred after a given time
 | |
| func (l *LogLine) Since(since time.Time) bool {
 | |
| 	return l.Time.After(since)
 | |
| }
 | |
| 
 | |
| // newLogLine creates a logLine struct from a container log string
 | |
| func newLogLine(line string) (*LogLine, error) {
 | |
| 	splitLine := strings.Split(line, " ")
 | |
| 	if len(splitLine) < 4 {
 | |
| 		return nil, errors.Errorf("'%s' is not a valid container log line", line)
 | |
| 	}
 | |
| 	logTime, err := time.Parse(logTimeFormat, splitLine[0])
 | |
| 	if err != nil {
 | |
| 		return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0])
 | |
| 	}
 | |
| 	l := LogLine{
 | |
| 		Time:         logTime,
 | |
| 		Device:       splitLine[1],
 | |
| 		ParseLogType: splitLine[2],
 | |
| 		Msg:          strings.Join(splitLine[3:], " "),
 | |
| 	}
 | |
| 	return &l, nil
 | |
| }
 | |
| 
 | |
| // Partial returns a bool if the log line is a partial log type
 | |
| func (l *LogLine) Partial() bool {
 | |
| 	if l.ParseLogType == partialLogType {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 |