summaryrefslogtreecommitdiff
path: root/libpod/events/logfile.go
diff options
context:
space:
mode:
authorNiall Crowe <nicrowe@redhat.com>2022-02-25 09:25:30 +0000
committerNiall Crowe <nicrowe@redhat.com>2022-04-14 09:35:29 +0100
commit3da3afa5764bcec2d50b219446579b5770051f32 (patch)
tree66b2319d84d1eac23169b131ddbb96a6e9c3c4b6 /libpod/events/logfile.go
parent15712c76fb198cec7509ff0cf401e357401d2d7d (diff)
downloadpodman-3da3afa5764bcec2d50b219446579b5770051f32.tar.gz
podman-3da3afa5764bcec2d50b219446579b5770051f32.tar.bz2
podman-3da3afa5764bcec2d50b219446579b5770051f32.zip
Add log rotation based on log size
Add new functions to logfile.go for rotating and truncating the events log file once the log file and its contents exceed the maximum size limit while keeping 50% of the log file's content Also add tests to verify log rotation and truncation Signed-off-by: Niall Crowe <nicrowe@redhat.com> Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
Diffstat (limited to 'libpod/events/logfile.go')
-rw-r--r--libpod/events/logfile.go156
1 files changed, 152 insertions, 4 deletions
diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go
index 76173cde9..5091f3723 100644
--- a/libpod/events/logfile.go
+++ b/libpod/events/logfile.go
@@ -1,15 +1,24 @@
+//go:build linux
+// +build linux
+
package events
import (
+ "bufio"
"context"
"fmt"
+ "io"
+ "io/ioutil"
"os"
+ "path"
"time"
"github.com/containers/podman/v4/pkg/util"
"github.com/containers/storage/pkg/lockfile"
+ "github.com/nxadm/tail"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
+ "golang.org/x/sys/unix"
)
// EventLogFile is the structure for event writing to a logfile. It contains the eventer
@@ -27,21 +36,55 @@ func (e EventLogFile) Write(ee Event) error {
}
lock.Lock()
defer lock.Unlock()
- f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
+
+ eventJSONString, err := ee.ToJSONString()
if err != nil {
return err
}
- defer f.Close()
- eventJSONString, err := ee.ToJSONString()
+
+ rotated, err := rotateLog(e.options.LogFilePath, eventJSONString, e.options.LogFileMaxSize)
+ if err != nil {
+ return fmt.Errorf("rotating log file: %w", err)
+ }
+
+ if rotated {
+ rEvent := NewEvent(Rotate)
+ rEvent.Type = System
+ rEvent.Name = e.options.LogFilePath
+ rotateJSONString, err := rEvent.ToJSONString()
+ if err != nil {
+ return err
+ }
+ if err := e.writeString(rotateJSONString); err != nil {
+ return err
+ }
+ }
+
+ return e.writeString(eventJSONString)
+}
+
+func (e EventLogFile) writeString(s string) error {
+ f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
if err != nil {
return err
}
- if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil {
+ if _, err := f.WriteString(s + "\n"); err != nil {
return err
}
return nil
}
+func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) {
+ reopen := true
+ seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END}
+ if options.FromStart || !options.Stream {
+ seek.Whence = 0
+ reopen = false
+ }
+ stream := options.Stream
+ return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true})
+}
+
// Reads from the log file
func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
defer close(options.EventChannel)
@@ -107,3 +150,108 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
func (e EventLogFile) String() string {
return LogFile.String()
}
+
+// Rotates the log file if the log file size and content exceeds limit
+func rotateLog(logfile string, content string, limit uint64) (bool, error) {
+ if limit == 0 {
+ return false, nil
+ }
+ file, err := os.Stat(logfile)
+ if err != nil {
+ return false, err
+ }
+ var filesize = uint64(file.Size())
+ var contentsize = uint64(len([]rune(content)))
+ if filesize+contentsize < limit {
+ return false, nil
+ }
+
+ if err := truncate(logfile); err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// Truncates the log file and saves 50% of content to new log file
+func truncate(filePath string) error {
+ orig, err := os.Open(filePath)
+ if err != nil {
+ return err
+ }
+ defer orig.Close()
+
+ origFinfo, err := orig.Stat()
+ if err != nil {
+ return err
+ }
+
+ size := origFinfo.Size()
+ threshold := size / 2
+
+ tmp, err := ioutil.TempFile(path.Dir(filePath), "")
+ if err != nil {
+ // Retry in /tmp in case creating a tmp file in the same
+ // directory has failed.
+ tmp, err = ioutil.TempFile("", "")
+ if err != nil {
+ return err
+ }
+ }
+ defer tmp.Close()
+
+ // Jump directly to the threshold, drop the first line and copy the remainder
+ if _, err := orig.Seek(threshold, 0); err != nil {
+ return err
+ }
+ reader := bufio.NewReader(orig)
+ if _, err := reader.ReadString('\n'); err != nil {
+ if !errors.Is(err, io.EOF) {
+ return err
+ }
+ }
+ if _, err := reader.WriteTo(tmp); err != nil {
+ return fmt.Errorf("writing truncated contents: %w", err)
+ }
+
+ if err := renameLog(tmp.Name(), filePath); err != nil {
+ return fmt.Errorf("writing back %s to %s: %w", tmp.Name(), filePath, err)
+ }
+
+ return nil
+}
+
+// Renames from, to
+func renameLog(from, to string) error {
+ err := os.Rename(from, to)
+ if err == nil {
+ return nil
+ }
+
+ if !errors.Is(err, unix.EXDEV) {
+ return err
+ }
+
+ // Files are not on the same partition, so we need to copy them the
+ // hard way.
+ fFrom, err := os.Open(from)
+ if err != nil {
+ return err
+ }
+ defer fFrom.Close()
+
+ fTo, err := os.Create(to)
+ if err != nil {
+ return err
+ }
+ defer fTo.Close()
+
+ if _, err := io.Copy(fTo, fFrom); err != nil {
+ return fmt.Errorf("writing back from temporary file: %w", err)
+ }
+
+ if err := os.Remove(from); err != nil {
+ return fmt.Errorf("removing temporary file: %w", err)
+ }
+
+ return nil
+}