github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/libpod/events/logfile.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package events
     5  
     6  import (
     7  	"bufio"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path"
    14  	"time"
    15  
    16  	"github.com/hanks177/podman/v4/pkg/util"
    17  	"github.com/containers/storage/pkg/lockfile"
    18  	"github.com/nxadm/tail"
    19  	"github.com/pkg/errors"
    20  	"github.com/sirupsen/logrus"
    21  	"golang.org/x/sys/unix"
    22  )
    23  
    24  // EventLogFile is the structure for event writing to a logfile. It contains the eventer
    25  // options and the event itself.  Methods for reading and writing are also defined from it.
    26  type EventLogFile struct {
    27  	options EventerOptions
    28  }
    29  
    30  // Writes to the log file
    31  func (e EventLogFile) Write(ee Event) error {
    32  	// We need to lock events file
    33  	lock, err := lockfile.GetLockfile(e.options.LogFilePath + ".lock")
    34  	if err != nil {
    35  		return err
    36  	}
    37  	lock.Lock()
    38  	defer lock.Unlock()
    39  
    40  	eventJSONString, err := ee.ToJSONString()
    41  	if err != nil {
    42  		return err
    43  	}
    44  
    45  	rotated, err := rotateLog(e.options.LogFilePath, eventJSONString, e.options.LogFileMaxSize)
    46  	if err != nil {
    47  		return fmt.Errorf("rotating log file: %w", err)
    48  	}
    49  
    50  	if rotated {
    51  		rEvent := NewEvent(Rotate)
    52  		rEvent.Type = System
    53  		rEvent.Name = e.options.LogFilePath
    54  		rotateJSONString, err := rEvent.ToJSONString()
    55  		if err != nil {
    56  			return err
    57  		}
    58  		if err := e.writeString(rotateJSONString); err != nil {
    59  			return err
    60  		}
    61  	}
    62  
    63  	return e.writeString(eventJSONString)
    64  }
    65  
    66  func (e EventLogFile) writeString(s string) error {
    67  	f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	if _, err := f.WriteString(s + "\n"); err != nil {
    72  		return err
    73  	}
    74  	return nil
    75  }
    76  
    77  func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) {
    78  	reopen := true
    79  	seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}
    80  	if options.FromStart || !options.Stream {
    81  		seek.Whence = 0
    82  		reopen = false
    83  	}
    84  	stream := options.Stream
    85  	return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true})
    86  }
    87  
    88  // Reads from the log file
    89  func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
    90  	defer close(options.EventChannel)
    91  	filterMap, err := generateEventFilters(options.Filters, options.Since, options.Until)
    92  	if err != nil {
    93  		return errors.Wrapf(err, "failed to parse event filters")
    94  	}
    95  	t, err := e.getTail(options)
    96  	if err != nil {
    97  		return err
    98  	}
    99  	if len(options.Until) > 0 {
   100  		untilTime, err := util.ParseInputTime(options.Until, false)
   101  		if err != nil {
   102  			return err
   103  		}
   104  		go func() {
   105  			time.Sleep(time.Until(untilTime))
   106  			if err := t.Stop(); err != nil {
   107  				logrus.Errorf("Stopping logger: %v", err)
   108  			}
   109  		}()
   110  	}
   111  	funcDone := make(chan bool)
   112  	copy := true
   113  	go func() {
   114  		select {
   115  		case <-funcDone:
   116  			// Do nothing
   117  		case <-ctx.Done():
   118  			copy = false
   119  			t.Kill(errors.New("hangup by client"))
   120  		}
   121  	}()
   122  	for line := range t.Lines {
   123  		select {
   124  		case <-ctx.Done():
   125  			// the consumer has cancelled
   126  			return nil
   127  		default:
   128  			// fallthrough
   129  		}
   130  
   131  		event, err := newEventFromJSONString(line.Text)
   132  		if err != nil {
   133  			return err
   134  		}
   135  		switch event.Type {
   136  		case Image, Volume, Pod, System, Container, Network:
   137  		//	no-op
   138  		default:
   139  			return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath)
   140  		}
   141  		if copy && applyFilters(event, filterMap) {
   142  			options.EventChannel <- event
   143  		}
   144  	}
   145  	funcDone <- true
   146  	return nil
   147  }
   148  
   149  // String returns a string representation of the logger
   150  func (e EventLogFile) String() string {
   151  	return LogFile.String()
   152  }
   153  
   154  // Rotates the log file if the log file size and content exceeds limit
   155  func rotateLog(logfile string, content string, limit uint64) (bool, error) {
   156  	if limit == 0 {
   157  		return false, nil
   158  	}
   159  	file, err := os.Stat(logfile)
   160  	if err != nil {
   161  		if errors.Is(err, os.ErrNotExist) {
   162  			// The logfile does not exist yet.
   163  			return false, nil
   164  		}
   165  		return false, err
   166  	}
   167  	var filesize = uint64(file.Size())
   168  	var contentsize = uint64(len([]rune(content)))
   169  	if filesize+contentsize < limit {
   170  		return false, nil
   171  	}
   172  
   173  	if err := truncate(logfile); err != nil {
   174  		return false, err
   175  	}
   176  	return true, nil
   177  }
   178  
   179  // Truncates the log file and saves 50% of content to new log file
   180  func truncate(filePath string) error {
   181  	orig, err := os.Open(filePath)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	defer orig.Close()
   186  
   187  	origFinfo, err := orig.Stat()
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	size := origFinfo.Size()
   193  	threshold := size / 2
   194  
   195  	tmp, err := ioutil.TempFile(path.Dir(filePath), "")
   196  	if err != nil {
   197  		// Retry in /tmp in case creating a tmp file in the same
   198  		// directory has failed.
   199  		tmp, err = ioutil.TempFile("", "")
   200  		if err != nil {
   201  			return err
   202  		}
   203  	}
   204  	defer tmp.Close()
   205  
   206  	// Jump directly to the threshold, drop the first line and copy the remainder
   207  	if _, err := orig.Seek(threshold, 0); err != nil {
   208  		return err
   209  	}
   210  	reader := bufio.NewReader(orig)
   211  	if _, err := reader.ReadString('\n'); err != nil {
   212  		if !errors.Is(err, io.EOF) {
   213  			return err
   214  		}
   215  	}
   216  	if _, err := reader.WriteTo(tmp); err != nil {
   217  		return fmt.Errorf("writing truncated contents: %w", err)
   218  	}
   219  
   220  	if err := renameLog(tmp.Name(), filePath); err != nil {
   221  		return fmt.Errorf("writing back %s to %s: %w", tmp.Name(), filePath, err)
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  // Renames from, to
   228  func renameLog(from, to string) error {
   229  	err := os.Rename(from, to)
   230  	if err == nil {
   231  		return nil
   232  	}
   233  
   234  	if !errors.Is(err, unix.EXDEV) {
   235  		return err
   236  	}
   237  
   238  	// Files are not on the same partition, so we need to copy them the
   239  	// hard way.
   240  	fFrom, err := os.Open(from)
   241  	if err != nil {
   242  		return err
   243  	}
   244  	defer fFrom.Close()
   245  
   246  	fTo, err := os.Create(to)
   247  	if err != nil {
   248  		return err
   249  	}
   250  	defer fTo.Close()
   251  
   252  	if _, err := io.Copy(fTo, fFrom); err != nil {
   253  		return fmt.Errorf("writing back from temporary file: %w", err)
   254  	}
   255  
   256  	if err := os.Remove(from); err != nil {
   257  		return fmt.Errorf("removing temporary file: %w", err)
   258  	}
   259  
   260  	return nil
   261  }