aboutsummaryrefslogtreecommitdiff
path: root/pkg/logs/logs.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/logs/logs.go')
-rw-r--r--pkg/logs/logs.go356
1 files changed, 0 insertions, 356 deletions
diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go
deleted file mode 100644
index 89e4e5686..000000000
--- a/pkg/logs/logs.go
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
-This package picks up CRI parsing and writer for the logs from the kubernetes
-logs package. These two bits have been modified to fit the requirements of libpod.
-
-Copyright 2017 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package logs
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "math"
- "os"
- "time"
-
- "github.com/containers/libpod/libpod"
- "github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/errorhandling"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-const (
- // timeFormat 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
- timeFormat = "2006-01-02T15:04:05.000000000Z07:00"
-)
-
-// LogStreamType is the type of the stream in CRI container log.
-type LogStreamType string
-
-const (
- // Stdout is the stream type for stdout.
- Stdout LogStreamType = "stdout"
- // Stderr is the stream type for stderr.
- Stderr LogStreamType = "stderr"
-)
-
-// LogTag is the tag of a log line in CRI container log.
-// Currently defined log tags:
-// * First tag: Partial/Full - P/F.
-// The field in the container log format can be extended to include multiple
-// tags by using a delimiter, but changes should be rare.
-type LogTag string
-
-const (
- // LogTagPartial means the line is part of multiple lines.
- LogTagPartial LogTag = "P"
- // LogTagFull means the line is a single full line or the end of multiple lines.
- LogTagFull LogTag = "F"
- // LogTagDelimiter is the delimiter for different log tags.
- LogTagDelimiter = ":"
-)
-
-var (
- // eol is the end-of-line sign in the log.
- eol = []byte{'\n'}
- // delimiter is the delimiter for timestamp and stream type in log line.
- delimiter = []byte{' '}
- // tagDelimiter is the delimiter for log tags.
- tagDelimiter = []byte(LogTagDelimiter)
-)
-
-// logMessage is the CRI internal log type.
-type logMessage struct {
- timestamp time.Time
- stream LogStreamType
- log []byte
-}
-
-// LogOptions is the options you can use for logs
-type LogOptions struct {
- Details bool
- Follow bool
- Since time.Time
- Tail uint64
- Timestamps bool
- bytes int64
-}
-
-// reset resets the log to nil.
-func (l *logMessage) reset() {
- l.timestamp = time.Time{}
- l.stream = ""
- l.log = nil
-}
-
-// parseCRILog parses logs in CRI log format. CRI Log format example:
-// 2016-10-06T00:17:09.669794202Z stdout P log content 1
-// 2016-10-06T00:17:09.669794203Z stderr F log content 2
-func parseCRILog(log []byte, msg *logMessage) error {
- var err error
- // Parse timestamp
- idx := bytes.Index(log, delimiter)
- if idx < 0 {
- return fmt.Errorf("timestamp is not found")
- }
- msg.timestamp, err = time.Parse(timeFormat, string(log[:idx]))
- if err != nil {
- return fmt.Errorf("unexpected timestamp format %q: %v", timeFormat, err)
- }
-
- // Parse stream type
- log = log[idx+1:]
- idx = bytes.Index(log, delimiter)
- if idx < 0 {
- return fmt.Errorf("stream type is not found")
- }
- msg.stream = LogStreamType(log[:idx])
- if msg.stream != Stdout && msg.stream != Stderr {
- return fmt.Errorf("unexpected stream type %q", msg.stream)
- }
-
- // Parse log tag
- log = log[idx+1:]
- idx = bytes.Index(log, delimiter)
- if idx < 0 {
- return fmt.Errorf("log tag is not found")
- }
- // Keep this forward compatible.
- tags := bytes.Split(log[:idx], tagDelimiter)
- partial := LogTag(tags[0]) == LogTagPartial
- // Trim the tailing new line if this is a partial line.
- if partial && len(log) > 0 && log[len(log)-1] == '\n' {
- log = log[:len(log)-1]
- }
-
- // Get log content
- msg.log = log[idx+1:]
-
- return nil
-}
-
-// ReadLogs reads in the logs from the logPath
-func ReadLogs(logPath string, ctr *libpod.Container, opts *LogOptions) error {
- file, err := os.Open(logPath)
- if err != nil {
- return errors.Wrapf(err, "failed to open log file %q", logPath)
- }
- defer errorhandling.CloseQuiet(file)
-
- msg := &logMessage{}
- opts.bytes = -1
- writer := newLogWriter(opts)
- reader := bufio.NewReader(file)
-
- if opts.Follow {
- err = followLog(reader, writer, opts, ctr, msg, logPath)
- } else {
- err = dumpLog(reader, writer, opts, msg, logPath)
- }
- return err
-}
-
-func followLog(reader *bufio.Reader, writer *logWriter, opts *LogOptions, ctr *libpod.Container, msg *logMessage, logPath string) error {
- var cacheOutput []string
- firstPass := false
- if opts.Tail > 0 {
- firstPass = true
- }
- // We need to read the entire file in here until we reach EOF
- // and then dump it out in the case that the user also wants
- // tail output
- for {
- line, err := reader.ReadString(eol[0])
- if err == io.EOF && opts.Follow {
- if firstPass {
- firstPass = false
- cacheLen := int64(len(cacheOutput))
- start := int64(0)
- if cacheLen > int64(opts.Tail) {
- start = cacheLen - int64(opts.Tail)
- }
- for i := start; i < cacheLen; i++ {
- msg.reset()
- if err := parseCRILog([]byte(cacheOutput[i]), msg); err != nil {
- return errors.Wrapf(err, "error parsing log line")
- }
- // Write the log line into the stream.
- if err := writer.write(msg); err != nil {
- if err == errMaximumWrite {
- logrus.Infof("Finish parsing log file %q, hit bytes limit %d(bytes)", logPath, opts.bytes)
- return nil
- }
- logrus.Errorf("Failed with err %v when writing log for log file %q: %+v", err, logPath, msg)
- return err
- }
- }
- continue
- }
- time.Sleep(1 * time.Second)
- // Check if container is still running or paused
- state, err := ctr.State()
- if err != nil {
- return err
- }
- if state != define.ContainerStateRunning && state != define.ContainerStatePaused {
- break
- }
- continue
- }
- // exits
- if err != nil {
- break
- }
- if firstPass {
- cacheOutput = append(cacheOutput, line)
- continue
- }
- msg.reset()
- if err := parseCRILog([]byte(line), msg); err != nil {
- return errors.Wrapf(err, "error parsing log line")
- }
- // Write the log line into the stream.
- if err := writer.write(msg); err != nil {
- if err == errMaximumWrite {
- logrus.Infof("Finish parsing log file %q, hit bytes limit %d(bytes)", logPath, opts.bytes)
- return nil
- }
- logrus.Errorf("Failed with err %v when writing log for log file %q: %+v", err, logPath, msg)
- return err
- }
- }
- return nil
-}
-
-func dumpLog(reader *bufio.Reader, writer *logWriter, opts *LogOptions, msg *logMessage, logPath string) error {
- output := readLog(reader, opts)
- for _, line := range output {
- msg.reset()
- if err := parseCRILog([]byte(line), msg); err != nil {
- return errors.Wrapf(err, "error parsing log line")
- }
- // Write the log line into the stream.
- if err := writer.write(msg); err != nil {
- if err == errMaximumWrite {
- logrus.Infof("Finish parsing log file %q, hit bytes limit %d(bytes)", logPath, opts.bytes)
- return nil
- }
- logrus.Errorf("Failed with err %v when writing log for log file %q: %+v", err, logPath, msg)
- return err
- }
- }
-
- return nil
-}
-
-func readLog(reader *bufio.Reader, opts *LogOptions) []string {
- var output []string
- for {
- line, err := reader.ReadString(eol[0])
- if err != nil {
- break
- }
- output = append(output, line)
- }
- start := 0
- if opts.Tail > 0 {
- if len(output) > int(opts.Tail) {
- start = len(output) - int(opts.Tail)
- }
- }
- return output[start:]
-}
-
-// logWriter controls the writing into the stream based on the log options.
-type logWriter struct {
- stdout io.Writer
- stderr io.Writer
- opts *LogOptions
- remain int64
- doAppend bool
-}
-
-// errMaximumWrite is returned when all bytes have been written.
-var errMaximumWrite = errors.New("maximum write")
-
-// errShortWrite is returned when the message is not fully written.
-var errShortWrite = errors.New("short write")
-
-func newLogWriter(opts *LogOptions) *logWriter {
- w := &logWriter{
- stdout: os.Stdout,
- stderr: os.Stderr,
- opts: opts,
- remain: math.MaxInt64, // initialize it as infinity
- }
- if opts.bytes >= 0 {
- w.remain = opts.bytes
- }
- return w
-}
-
-// writeLogs writes logs into stdout, stderr.
-func (w *logWriter) write(msg *logMessage) error {
- if msg.timestamp.Before(w.opts.Since) {
- // Skip the line because it's older than since
- return nil
- }
- line := msg.log
- if w.opts.Timestamps && !w.doAppend {
- prefix := append([]byte(msg.timestamp.Format(timeFormat)), delimiter[0])
- line = append(prefix, line...)
- if len(line) > 0 && line[len(line)-1] != '\n' {
- w.doAppend = true
- }
- }
- if w.doAppend && len(line) > 0 && line[len(line)-1] == '\n' {
- w.doAppend = false
- }
- // If the line is longer than the remaining bytes, cut it.
- if int64(len(line)) > w.remain {
- line = line[:w.remain]
- }
- // Get the proper stream to write to.
- var stream io.Writer
- switch msg.stream {
- case Stdout:
- stream = w.stdout
- case Stderr:
- stream = w.stderr
- default:
- return fmt.Errorf("unexpected stream type %q", msg.stream)
- }
- n, err := stream.Write(line)
- w.remain -= int64(n)
- if err != nil {
- return err
- }
- // If the line has not been fully written, return errShortWrite
- if n < len(line) {
- return errShortWrite
- }
- // If there are no more bytes left, return errMaximumWrite
- if w.remain <= 0 {
- return errMaximumWrite
- }
- return nil
-}