|
@@ -12,6 +12,7 @@ import (
|
|
|
"time"
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
|
+ "github.com/docker/docker/daemon/logger"
|
|
|
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
|
|
"github.com/docker/docker/pkg/jsonlog"
|
|
|
"github.com/docker/docker/pkg/stdcopy"
|
|
@@ -40,7 +41,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
|
|
format = timeutils.RFC3339NanoFixed
|
|
|
}
|
|
|
if config.Tail == "" {
|
|
|
- config.Tail = "all"
|
|
|
+ config.Tail = "latest"
|
|
|
}
|
|
|
|
|
|
container, err := daemon.Get(name)
|
|
@@ -62,13 +63,29 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
|
|
if container.LogDriverType() != jsonfilelog.Name {
|
|
|
return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
|
|
|
}
|
|
|
+
|
|
|
+ maxFile := 1
|
|
|
+ container.readHostConfig()
|
|
|
+ cfg := container.getLogConfig()
|
|
|
+ conf := cfg.Config
|
|
|
+ if val, ok := conf["max-file"]; ok {
|
|
|
+ var err error
|
|
|
+ maxFile, err = strconv.Atoi(val)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("Error reading max-file value: %s", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
logDriver, err := container.getLogger()
|
|
|
- cLog, err := logDriver.GetReader()
|
|
|
if err != nil {
|
|
|
- logrus.Errorf("Error reading logs: %s", err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ _, ok := logDriver.(logger.Reader)
|
|
|
+ if !ok {
|
|
|
+ logrus.Errorf("Cannot read logs of the [%s] driver", logDriver.Name())
|
|
|
} else {
|
|
|
// json-file driver
|
|
|
- if config.Tail != "all" {
|
|
|
+ if config.Tail != "all" && config.Tail != "latest" {
|
|
|
var err error
|
|
|
lines, err = strconv.Atoi(config.Tail)
|
|
|
if err != nil {
|
|
@@ -78,42 +95,50 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
|
|
}
|
|
|
|
|
|
if lines != 0 {
|
|
|
- if lines > 0 {
|
|
|
- f := cLog.(*os.File)
|
|
|
- ls, err := tailfile.TailFile(f, lines)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- tmp := bytes.NewBuffer([]byte{})
|
|
|
- for _, l := range ls {
|
|
|
- fmt.Fprintf(tmp, "%s\n", l)
|
|
|
- }
|
|
|
- cLog = tmp
|
|
|
+ n := maxFile
|
|
|
+ if config.Tail == "latest" && config.Since.IsZero() {
|
|
|
+ n = 1
|
|
|
}
|
|
|
-
|
|
|
- dec := json.NewDecoder(cLog)
|
|
|
- l := &jsonlog.JSONLog{}
|
|
|
- for {
|
|
|
- l.Reset()
|
|
|
- if err := dec.Decode(l); err == io.EOF {
|
|
|
- break
|
|
|
- } else if err != nil {
|
|
|
- logrus.Errorf("Error streaming logs: %s", err)
|
|
|
+ before := false
|
|
|
+ for i := n; i > 0; i-- {
|
|
|
+ if before {
|
|
|
break
|
|
|
}
|
|
|
- logLine := l.Log
|
|
|
- if !config.Since.IsZero() && l.Created.Before(config.Since) {
|
|
|
+ cLog, err := getReader(logDriver, i, n, lines)
|
|
|
+ if err != nil {
|
|
|
+ logrus.Debugf("Error reading %d log file: %v", i-1, err)
|
|
|
continue
|
|
|
}
|
|
|
- if config.Timestamps {
|
|
|
- // format can be "" or time format, so here can't be error
|
|
|
- logLine, _ = l.Format(format)
|
|
|
- }
|
|
|
- if l.Stream == "stdout" && config.UseStdout {
|
|
|
- io.WriteString(outStream, logLine)
|
|
|
+ //if lines are specified, then iterate only once
|
|
|
+ if lines > 0 {
|
|
|
+ i = 1
|
|
|
+ } else { // if lines are not specified, cLog is a file, It needs to be closed
|
|
|
+ defer cLog.(*os.File).Close()
|
|
|
}
|
|
|
- if l.Stream == "stderr" && config.UseStderr {
|
|
|
- io.WriteString(errStream, logLine)
|
|
|
+ dec := json.NewDecoder(cLog)
|
|
|
+ l := &jsonlog.JSONLog{}
|
|
|
+ for {
|
|
|
+ l.Reset()
|
|
|
+ if err := dec.Decode(l); err == io.EOF {
|
|
|
+ break
|
|
|
+ } else if err != nil {
|
|
|
+ logrus.Errorf("Error streaming logs: %s", err)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ logLine := l.Log
|
|
|
+ if !config.Since.IsZero() && l.Created.Before(config.Since) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if config.Timestamps {
|
|
|
+ // format can be "" or time format, so here can't be error
|
|
|
+ logLine, _ = l.Format(format)
|
|
|
+ }
|
|
|
+ if l.Stream == "stdout" && config.UseStdout {
|
|
|
+ io.WriteString(outStream, logLine)
|
|
|
+ }
|
|
|
+ if l.Stream == "stderr" && config.UseStderr {
|
|
|
+ io.WriteString(errStream, logLine)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -177,3 +202,36 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
+
|
|
|
+func getReader(logDriver logger.Logger, fileIndex, maxFiles, lines int) (io.Reader, error) {
|
|
|
+ if lines <= 0 {
|
|
|
+ index := strconv.Itoa(fileIndex - 1)
|
|
|
+ cLog, err := logDriver.(logger.Reader).ReadLog(index)
|
|
|
+ return cLog, err
|
|
|
+ }
|
|
|
+ buf := bytes.NewBuffer([]byte{})
|
|
|
+ remaining := lines
|
|
|
+ for i := 0; i < maxFiles; i++ {
|
|
|
+ index := strconv.Itoa(i)
|
|
|
+ cLog, err := logDriver.(logger.Reader).ReadLog(index)
|
|
|
+ if err != nil {
|
|
|
+ return buf, err
|
|
|
+ }
|
|
|
+ f := cLog.(*os.File)
|
|
|
+ ls, err := tailfile.TailFile(f, remaining)
|
|
|
+ if err != nil {
|
|
|
+ return buf, err
|
|
|
+ }
|
|
|
+ tmp := bytes.NewBuffer([]byte{})
|
|
|
+ for _, l := range ls {
|
|
|
+ fmt.Fprintf(tmp, "%s\n", l)
|
|
|
+ }
|
|
|
+ tmp.ReadFrom(buf)
|
|
|
+ buf = tmp
|
|
|
+ if len(ls) == remaining {
|
|
|
+ return buf, nil
|
|
|
+ }
|
|
|
+ remaining = remaining - len(ls)
|
|
|
+ }
|
|
|
+ return buf, nil
|
|
|
+}
|