2014-08-01 01:03:21 +04:00
|
|
|
package daemon
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2015-05-07 04:09:27 +03:00
|
|
|
"net"
|
2014-08-01 01:03:21 +04:00
|
|
|
"os"
|
|
|
|
"strconv"
|
2015-05-07 04:09:27 +03:00
|
|
|
"syscall"
|
2015-04-14 07:36:12 +03:00
|
|
|
"time"
|
2014-08-01 01:03:21 +04:00
|
|
|
|
2015-03-27 01:22:04 +03:00
|
|
|
"github.com/Sirupsen/logrus"
|
2015-07-01 03:40:13 +03:00
|
|
|
"github.com/docker/docker/daemon/logger"
|
2015-04-09 07:23:30 +03:00
|
|
|
"github.com/docker/docker/daemon/logger/jsonfilelog"
|
2014-08-07 03:45:04 +04:00
|
|
|
"github.com/docker/docker/pkg/jsonlog"
|
2015-04-12 00:49:14 +03:00
|
|
|
"github.com/docker/docker/pkg/stdcopy"
|
2014-09-15 22:44:58 +04:00
|
|
|
"github.com/docker/docker/pkg/tailfile"
|
|
|
|
"github.com/docker/docker/pkg/timeutils"
|
2014-08-01 01:03:21 +04:00
|
|
|
)
|
|
|
|
|
2015-04-12 00:49:14 +03:00
|
|
|
type ContainerLogsConfig struct {
|
|
|
|
Follow, Timestamps bool
|
|
|
|
Tail string
|
2015-04-14 07:36:12 +03:00
|
|
|
Since time.Time
|
2015-04-12 00:49:14 +03:00
|
|
|
UseStdout, UseStderr bool
|
|
|
|
OutStream io.Writer
|
2015-06-04 22:15:33 +03:00
|
|
|
Stop <-chan bool
|
2015-04-12 00:49:14 +03:00
|
|
|
}
|
2014-08-01 01:03:21 +04:00
|
|
|
|
2015-04-12 00:49:14 +03:00
|
|
|
func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error {
|
2014-08-01 01:03:21 +04:00
|
|
|
var (
|
|
|
|
lines = -1
|
|
|
|
format string
|
|
|
|
)
|
2015-04-12 00:49:14 +03:00
|
|
|
if !(config.UseStdout || config.UseStderr) {
|
2015-03-25 10:44:12 +03:00
|
|
|
return fmt.Errorf("You must choose at least one stream")
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-04-12 00:49:14 +03:00
|
|
|
if config.Timestamps {
|
2014-09-15 22:44:58 +04:00
|
|
|
format = timeutils.RFC3339NanoFixed
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-04-12 00:49:14 +03:00
|
|
|
if config.Tail == "" {
|
2015-07-01 03:40:13 +03:00
|
|
|
config.Tail = "latest"
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-04-12 00:49:14 +03:00
|
|
|
|
2014-12-17 02:06:35 +03:00
|
|
|
container, err := daemon.Get(name)
|
|
|
|
if err != nil {
|
2015-03-25 10:44:12 +03:00
|
|
|
return err
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-04-12 00:49:14 +03:00
|
|
|
|
|
|
|
var (
|
|
|
|
outStream = config.OutStream
|
|
|
|
errStream io.Writer
|
|
|
|
)
|
|
|
|
if !container.Config.Tty {
|
|
|
|
errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
|
|
|
outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
|
|
|
} else {
|
|
|
|
errStream = outStream
|
|
|
|
}
|
|
|
|
|
2015-04-09 07:23:30 +03:00
|
|
|
if container.LogDriverType() != jsonfilelog.Name {
|
2015-03-25 10:44:12 +03:00
|
|
|
return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
|
2015-02-06 03:24:47 +03:00
|
|
|
}
|
2015-07-01 03:40:13 +03:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-09 07:23:30 +03:00
|
|
|
logDriver, err := container.getLogger()
|
|
|
|
if err != nil {
|
2015-07-01 03:40:13 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, ok := logDriver.(logger.Reader)
|
|
|
|
if !ok {
|
|
|
|
logrus.Errorf("Cannot read logs of the [%s] driver", logDriver.Name())
|
2014-08-01 01:03:21 +04:00
|
|
|
} else {
|
2015-04-09 07:23:30 +03:00
|
|
|
// json-file driver
|
2015-07-01 03:40:13 +03:00
|
|
|
if config.Tail != "all" && config.Tail != "latest" {
|
2014-08-01 01:03:21 +04:00
|
|
|
var err error
|
2015-04-12 00:49:14 +03:00
|
|
|
lines, err = strconv.Atoi(config.Tail)
|
2014-08-01 01:03:21 +04:00
|
|
|
if err != nil {
|
2015-04-12 00:49:14 +03:00
|
|
|
logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err)
|
2014-08-01 01:03:21 +04:00
|
|
|
lines = -1
|
|
|
|
}
|
|
|
|
}
|
2015-04-14 07:36:12 +03:00
|
|
|
|
2014-08-01 01:03:21 +04:00
|
|
|
if lines != 0 {
|
2015-07-01 03:40:13 +03:00
|
|
|
n := maxFile
|
|
|
|
if config.Tail == "latest" && config.Since.IsZero() {
|
|
|
|
n = 1
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-07-01 03:40:13 +03:00
|
|
|
before := false
|
|
|
|
for i := n; i > 0; i-- {
|
|
|
|
if before {
|
2014-08-01 01:03:21 +04:00
|
|
|
break
|
|
|
|
}
|
2015-07-01 03:40:13 +03:00
|
|
|
cLog, err := getReader(logDriver, i, n, lines)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Debugf("Error reading %d log file: %v", i-1, err)
|
2015-04-14 07:36:12 +03:00
|
|
|
continue
|
|
|
|
}
|
2015-07-01 03:40:13 +03:00
|
|
|
//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()
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-07-01 03:40:13 +03:00
|
|
|
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)
|
|
|
|
}
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-05-07 04:09:27 +03:00
|
|
|
|
2015-04-12 00:49:14 +03:00
|
|
|
if config.Follow && container.IsRunning() {
|
2015-06-04 22:15:33 +03:00
|
|
|
chErrStderr := make(chan error)
|
|
|
|
chErrStdout := make(chan error)
|
2015-05-07 04:09:27 +03:00
|
|
|
var stdoutPipe, stderrPipe io.ReadCloser
|
2014-10-30 22:33:26 +03:00
|
|
|
|
2015-04-24 01:08:41 +03:00
|
|
|
// write an empty chunk of data (this is to ensure that the
|
|
|
|
// HTTP Response is sent immediatly, even if the container has
|
|
|
|
// not yet produced any data)
|
|
|
|
outStream.Write(nil)
|
|
|
|
|
2015-04-12 00:49:14 +03:00
|
|
|
if config.UseStdout {
|
2015-05-07 04:09:27 +03:00
|
|
|
stdoutPipe = container.StdoutLogPipe()
|
2014-08-01 01:03:21 +04:00
|
|
|
go func() {
|
2015-05-07 04:09:27 +03:00
|
|
|
logrus.Debug("logs: stdout stream begin")
|
2015-06-04 22:15:33 +03:00
|
|
|
chErrStdout <- jsonlog.WriteLog(stdoutPipe, outStream, format, config.Since)
|
2015-05-07 04:09:27 +03:00
|
|
|
logrus.Debug("logs: stdout stream end")
|
2014-08-01 01:03:21 +04:00
|
|
|
}()
|
|
|
|
}
|
2015-04-12 00:49:14 +03:00
|
|
|
if config.UseStderr {
|
2015-05-07 04:09:27 +03:00
|
|
|
stderrPipe = container.StderrLogPipe()
|
2014-08-01 01:03:21 +04:00
|
|
|
go func() {
|
2015-05-07 04:09:27 +03:00
|
|
|
logrus.Debug("logs: stderr stream begin")
|
2015-06-04 22:15:33 +03:00
|
|
|
chErrStderr <- jsonlog.WriteLog(stderrPipe, errStream, format, config.Since)
|
2015-05-07 04:09:27 +03:00
|
|
|
logrus.Debug("logs: stderr stream end")
|
2014-08-01 01:03:21 +04:00
|
|
|
}()
|
|
|
|
}
|
2014-10-30 22:33:26 +03:00
|
|
|
|
2015-06-04 22:15:33 +03:00
|
|
|
select {
|
|
|
|
case err = <-chErrStderr:
|
|
|
|
if stdoutPipe != nil {
|
|
|
|
stdoutPipe.Close()
|
|
|
|
<-chErrStdout
|
|
|
|
}
|
|
|
|
case err = <-chErrStdout:
|
|
|
|
if stderrPipe != nil {
|
|
|
|
stderrPipe.Close()
|
|
|
|
<-chErrStderr
|
|
|
|
}
|
|
|
|
case <-config.Stop:
|
|
|
|
if stdoutPipe != nil {
|
|
|
|
stdoutPipe.Close()
|
|
|
|
<-chErrStdout
|
|
|
|
}
|
|
|
|
if stderrPipe != nil {
|
|
|
|
stderrPipe.Close()
|
|
|
|
<-chErrStderr
|
|
|
|
}
|
|
|
|
return nil
|
2015-05-07 04:09:27 +03:00
|
|
|
}
|
2014-10-30 22:33:26 +03:00
|
|
|
|
2015-05-07 04:09:27 +03:00
|
|
|
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
|
|
|
|
if e, ok := err.(*net.OpError); ok && e.Err != syscall.EPIPE {
|
|
|
|
logrus.Errorf("error streaming logs: %v", err)
|
2014-10-30 22:33:26 +03:00
|
|
|
}
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
}
|
2015-03-25 10:44:12 +03:00
|
|
|
return nil
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-07-01 03:40:13 +03:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|