mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00
implement reverse reader for log reads
in cases where the log file exceeds the available memory of a system, we had a bug that triggered an oom because the entire logfile was being read when the tail parameter was given. this reads in chunks and is more or less memory safe. fixes: #5131 Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
@ -97,7 +97,7 @@ keys=[k for k in env if "ENCRYPTED" not in str(env[k])]
|
|||||||
for k,v in env.items():
|
for k,v in env.items():
|
||||||
v=str(v)
|
v=str(v)
|
||||||
if "ENCRYPTED" not in v:
|
if "ENCRYPTED" not in v:
|
||||||
print "{0}=\"{1}\"".format(k, v),
|
print("{0}=\"{1}\"".format(k, v)),
|
||||||
'
|
'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,13 +2,16 @@ package logs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/libpod/libpod/logs/reversereader"
|
||||||
"github.com/hpcloud/tail"
|
"github.com/hpcloud/tail"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -74,43 +77,84 @@ func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error
|
|||||||
|
|
||||||
func getTailLog(path string, tail int) ([]*LogLine, error) {
|
func getTailLog(path string, tail int) ([]*LogLine, error) {
|
||||||
var (
|
var (
|
||||||
tailLog []*LogLine
|
nlls []*LogLine
|
||||||
nlls []*LogLine
|
nllCounter int
|
||||||
tailCounter int
|
leftover string
|
||||||
partial string
|
partial string
|
||||||
|
tailLog []*LogLine
|
||||||
)
|
)
|
||||||
content, err := ioutil.ReadFile(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
splitContent := strings.Split(string(content), "\n")
|
rr, err := reversereader.NewReverseReader(f)
|
||||||
// We read the content in reverse and add each nll until we have the same
|
if err != nil {
|
||||||
// number of F type messages as the desired tail
|
return nil, err
|
||||||
for i := len(splitContent) - 1; i >= 0; i-- {
|
}
|
||||||
if len(splitContent[i]) == 0 {
|
|
||||||
continue
|
inputs := make(chan []string)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
s, err := rr.Read()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Cause(err) == io.EOF {
|
||||||
|
inputs <- []string{leftover}
|
||||||
|
close(inputs)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logrus.Error(err)
|
||||||
|
close(inputs)
|
||||||
|
}
|
||||||
|
line := strings.Split(s+leftover, "\n")
|
||||||
|
if len(line) > 1 {
|
||||||
|
inputs <- line[1:]
|
||||||
|
}
|
||||||
|
leftover = line[0]
|
||||||
}
|
}
|
||||||
nll, err := NewLogLine(splitContent[i])
|
}()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
for i := range inputs {
|
||||||
|
// the incoming array is FIFO; we want FIFO so
|
||||||
|
// reverse the slice read order
|
||||||
|
for j := len(i) - 1; j >= 0; j-- {
|
||||||
|
// lines that are "" are junk
|
||||||
|
if len(i[j]) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// read the content in reverse and add each nll until we have the same
|
||||||
|
// number of F type messages as the desired tail
|
||||||
|
nll, err := NewLogLine(i[j])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nlls = append(nlls, nll)
|
||||||
|
if !nll.Partial() {
|
||||||
|
nllCounter++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nlls = append(nlls, nll)
|
// if we have enough loglines, we can hangup
|
||||||
if !nll.Partial() {
|
if nllCounter >= tail {
|
||||||
tailCounter++
|
if err := f.Close(); err != nil {
|
||||||
}
|
logrus.Error(err)
|
||||||
if tailCounter == tail {
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Now we iterate the results and assemble partial messages to become full messages
|
|
||||||
|
// re-assemble the log lines and trim (if needed) to the
|
||||||
|
// tail length
|
||||||
for _, nll := range nlls {
|
for _, nll := range nlls {
|
||||||
if nll.Partial() {
|
if nll.Partial() {
|
||||||
partial += nll.Msg
|
partial += nll.Msg
|
||||||
} else {
|
} else {
|
||||||
nll.Msg += partial
|
nll.Msg += partial
|
||||||
tailLog = append(tailLog, nll)
|
// prepend because we need to reverse the order again to FIFO
|
||||||
|
tailLog = append([]*LogLine{nll}, tailLog...)
|
||||||
partial = ""
|
partial = ""
|
||||||
}
|
}
|
||||||
|
if len(tailLog) == tail {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tailLog, nil
|
return tailLog, nil
|
||||||
}
|
}
|
||||||
|
66
libpod/logs/reversereader/reversereader.go
Normal file
66
libpod/logs/reversereader/reversereader.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package reversereader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReverseReader structure for reading a file backwards
|
||||||
|
type ReverseReader struct {
|
||||||
|
reader *os.File
|
||||||
|
offset int64
|
||||||
|
readSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReverseReader returns a reader that reads from the end of a file
|
||||||
|
// rather than the beginning. It sets the readsize to pagesize and determines
|
||||||
|
// the first offset using using modulus.
|
||||||
|
func NewReverseReader(reader *os.File) (*ReverseReader, error) {
|
||||||
|
// pagesize should be safe for memory use and file reads should be on page
|
||||||
|
// boundaries as well
|
||||||
|
pageSize := int64(os.Getpagesize())
|
||||||
|
stat, err := reader.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// figure out the last page boundary
|
||||||
|
remainder := stat.Size() % pageSize
|
||||||
|
end, err := reader.Seek(0, 2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// set offset (starting position) to the last page boundary or
|
||||||
|
// zero if fits in one page
|
||||||
|
startOffset := end - remainder
|
||||||
|
if startOffset < 0 {
|
||||||
|
startOffset = 0
|
||||||
|
}
|
||||||
|
rr := ReverseReader{
|
||||||
|
reader: reader,
|
||||||
|
offset: startOffset,
|
||||||
|
readSize: pageSize,
|
||||||
|
}
|
||||||
|
return &rr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReverseReader reads from a given offset to the previous offset and
|
||||||
|
// then sets the newoff set one pagesize less than the previous read.
|
||||||
|
func (r *ReverseReader) Read() (string, error) {
|
||||||
|
if r.offset < 0 {
|
||||||
|
return "", errors.Wrap(io.EOF, "at beginning of file")
|
||||||
|
}
|
||||||
|
// Read from given offset
|
||||||
|
b := make([]byte, r.readSize)
|
||||||
|
n, err := r.reader.ReadAt(b, r.offset)
|
||||||
|
if err != nil && errors.Cause(err) != io.EOF {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if int64(n) < r.readSize {
|
||||||
|
b = b[0:n]
|
||||||
|
}
|
||||||
|
// Set to the next page boundary
|
||||||
|
r.offset = -r.readSize
|
||||||
|
return string(b), nil
|
||||||
|
}
|
@ -338,7 +338,11 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *logs.LogOptions) er
|
|||||||
if tailLen < 0 {
|
if tailLen < 0 {
|
||||||
tailLen = 0
|
tailLen = 0
|
||||||
}
|
}
|
||||||
logChannel := make(chan *logs.LogLine, tailLen*len(c.InputArgs)+1)
|
numContainers := len(c.InputArgs)
|
||||||
|
if numContainers == 0 {
|
||||||
|
numContainers = 1
|
||||||
|
}
|
||||||
|
logChannel := make(chan *logs.LogLine, tailLen*numContainers+1)
|
||||||
containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
|
containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
Reference in New Issue
Block a user