github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/container_log_linux.go (about)

     1  //+build linux
     2  //+build systemd
     3  
     4  package libpod
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/containers/libpod/libpod/logs"
    14  	journal "github.com/coreos/go-systemd/v22/sdjournal"
    15  	"github.com/pkg/errors"
    16  	"github.com/sirupsen/logrus"
    17  )
    18  
    19  const (
    20  	// journaldLogOut is the journald priority signifying stdout
    21  	journaldLogOut = "6"
    22  
    23  	// journaldLogErr is the journald priority signifying stderr
    24  	journaldLogErr = "3"
    25  
    26  	// bufLen is the length of the buffer to read from a k8s-file
    27  	// formatted log line
    28  	// let's set it as 2k just to be safe if k8s-file format ever changes
    29  	bufLen = 16384
    30  )
    31  
    32  func (c *Container) readFromJournal(options *logs.LogOptions, logChannel chan *logs.LogLine) error {
    33  	var config journal.JournalReaderConfig
    34  	if options.Tail < 0 {
    35  		config.NumFromTail = math.MaxUint64
    36  	} else {
    37  		config.NumFromTail = uint64(options.Tail)
    38  	}
    39  	config.Formatter = journalFormatter
    40  	defaultTime := time.Time{}
    41  	if options.Since != defaultTime {
    42  		// coreos/go-systemd/sdjournal doesn't correctly handle requests for data in the future
    43  		// return nothing instead of falsely printing
    44  		if time.Now().Before(options.Since) {
    45  			return nil
    46  		}
    47  		config.Since = time.Since(options.Since)
    48  	}
    49  	config.Matches = append(config.Matches, journal.Match{
    50  		Field: "CONTAINER_ID_FULL",
    51  		Value: c.ID(),
    52  	})
    53  	options.WaitGroup.Add(1)
    54  
    55  	r, err := journal.NewJournalReader(config)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	if r == nil {
    60  		return errors.Errorf("journal reader creation failed")
    61  	}
    62  	if options.Tail == math.MaxInt64 {
    63  		r.Rewind()
    64  	}
    65  
    66  	if options.Follow {
    67  		go func() {
    68  			follower := FollowBuffer{logChannel}
    69  			err := r.Follow(nil, follower)
    70  			if err != nil {
    71  				logrus.Debugf(err.Error())
    72  			}
    73  			r.Close()
    74  			options.WaitGroup.Done()
    75  			return
    76  		}()
    77  		return nil
    78  	}
    79  
    80  	go func() {
    81  		bytes := make([]byte, bufLen)
    82  		// /me complains about no do-while in go
    83  		ec, err := r.Read(bytes)
    84  		for ec != 0 && err == nil {
    85  			// because we are reusing bytes, we need to make
    86  			// sure the old data doesn't get into the new line
    87  			bytestr := string(bytes[:ec])
    88  			logLine, err2 := logs.NewLogLine(bytestr)
    89  			if err2 != nil {
    90  				logrus.Error(err2)
    91  				continue
    92  			}
    93  			logChannel <- logLine
    94  			ec, err = r.Read(bytes)
    95  		}
    96  		if err != nil && err != io.EOF {
    97  			logrus.Error(err)
    98  		}
    99  		r.Close()
   100  		options.WaitGroup.Done()
   101  	}()
   102  	return nil
   103  }
   104  
   105  func journalFormatter(entry *journal.JournalEntry) (string, error) {
   106  	usec := entry.RealtimeTimestamp
   107  	tsString := time.Unix(0, int64(usec)*int64(time.Microsecond)).Format(logs.LogTimeFormat)
   108  	output := fmt.Sprintf("%s ", tsString)
   109  	priority, ok := entry.Fields["PRIORITY"]
   110  	if !ok {
   111  		return "", errors.Errorf("no PRIORITY field present in journal entry")
   112  	}
   113  	if priority == journaldLogOut {
   114  		output += "stdout "
   115  	} else if priority == journaldLogErr {
   116  		output += "stderr "
   117  	} else {
   118  		return "", errors.Errorf("unexpected PRIORITY field in journal entry")
   119  	}
   120  
   121  	// if CONTAINER_PARTIAL_MESSAGE is defined, the log type is "P"
   122  	if _, ok := entry.Fields["CONTAINER_PARTIAL_MESSAGE"]; ok {
   123  		output += fmt.Sprintf("%s ", logs.PartialLogType)
   124  	} else {
   125  		output += fmt.Sprintf("%s ", logs.FullLogType)
   126  	}
   127  
   128  	// Finally, append the message
   129  	msg, ok := entry.Fields["MESSAGE"]
   130  	if !ok {
   131  		return "", fmt.Errorf("no MESSAGE field present in journal entry")
   132  	}
   133  	output += strings.TrimSpace(msg)
   134  	return output, nil
   135  }
   136  
   137  type FollowBuffer struct {
   138  	logChannel chan *logs.LogLine
   139  }
   140  
   141  func (f FollowBuffer) Write(p []byte) (int, error) {
   142  	bytestr := string(p)
   143  	logLine, err := logs.NewLogLine(bytestr)
   144  	if err != nil {
   145  		return -1, err
   146  	}
   147  	f.logChannel <- logLine
   148  	return len(p), nil
   149  }