2014-08-01 01:03:21 +04:00
|
|
|
package daemon
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
2014-10-30 22:33:26 +03:00
|
|
|
"sync"
|
2014-08-01 01:03:21 +04:00
|
|
|
|
2014-10-25 02:11:48 +04:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2014-08-01 01:03:21 +04:00
|
|
|
"github.com/docker/docker/engine"
|
2014-08-07 03:45:04 +04:00
|
|
|
"github.com/docker/docker/pkg/jsonlog"
|
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
|
|
|
)
|
|
|
|
|
|
|
|
func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status {
|
|
|
|
if len(job.Args) != 1 {
|
|
|
|
return job.Errorf("Usage: %s CONTAINER\n", job.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
name = job.Args[0]
|
|
|
|
stdout = job.GetenvBool("stdout")
|
|
|
|
stderr = job.GetenvBool("stderr")
|
|
|
|
tail = job.Getenv("tail")
|
|
|
|
follow = job.GetenvBool("follow")
|
|
|
|
times = job.GetenvBool("timestamps")
|
|
|
|
lines = -1
|
|
|
|
format string
|
|
|
|
)
|
|
|
|
if !(stdout || stderr) {
|
|
|
|
return job.Errorf("You must choose at least one stream")
|
|
|
|
}
|
|
|
|
if times {
|
2014-09-15 22:44:58 +04:00
|
|
|
format = timeutils.RFC3339NanoFixed
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
if tail == "" {
|
|
|
|
tail = "all"
|
|
|
|
}
|
2014-12-17 02:06:35 +03:00
|
|
|
container, err := daemon.Get(name)
|
|
|
|
if err != nil {
|
|
|
|
return job.Error(err)
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2015-02-06 03:24:47 +03:00
|
|
|
if container.LogDriverType() != "json-file" {
|
|
|
|
return job.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver")
|
|
|
|
}
|
2014-08-01 01:03:21 +04:00
|
|
|
cLog, err := container.ReadLog("json")
|
|
|
|
if err != nil && os.IsNotExist(err) {
|
|
|
|
// Legacy logs
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Debugf("Old logs format")
|
2014-08-01 01:03:21 +04:00
|
|
|
if stdout {
|
|
|
|
cLog, err := container.ReadLog("stdout")
|
|
|
|
if err != nil {
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Errorf("Error reading logs (stdout): %s", err)
|
2014-08-01 01:03:21 +04:00
|
|
|
} else if _, err := io.Copy(job.Stdout, cLog); err != nil {
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Errorf("Error streaming logs (stdout): %s", err)
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if stderr {
|
|
|
|
cLog, err := container.ReadLog("stderr")
|
|
|
|
if err != nil {
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Errorf("Error reading logs (stderr): %s", err)
|
2014-08-01 01:03:21 +04:00
|
|
|
} else if _, err := io.Copy(job.Stderr, cLog); err != nil {
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Errorf("Error streaming logs (stderr): %s", err)
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if err != nil {
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Errorf("Error reading logs (json): %s", err)
|
2014-08-01 01:03:21 +04:00
|
|
|
} else {
|
|
|
|
if tail != "all" {
|
|
|
|
var err error
|
|
|
|
lines, err = strconv.Atoi(tail)
|
|
|
|
if err != nil {
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err)
|
2014-08-01 01:03:21 +04:00
|
|
|
lines = -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if lines != 0 {
|
|
|
|
if lines > 0 {
|
|
|
|
f := cLog.(*os.File)
|
|
|
|
ls, err := tailfile.TailFile(f, lines)
|
|
|
|
if err != nil {
|
|
|
|
return job.Error(err)
|
|
|
|
}
|
|
|
|
tmp := bytes.NewBuffer([]byte{})
|
|
|
|
for _, l := range ls {
|
|
|
|
fmt.Fprintf(tmp, "%s\n", l)
|
|
|
|
}
|
|
|
|
cLog = tmp
|
|
|
|
}
|
|
|
|
dec := json.NewDecoder(cLog)
|
2014-09-18 17:29:19 +04:00
|
|
|
l := &jsonlog.JSONLog{}
|
2014-08-01 01:03:21 +04:00
|
|
|
for {
|
|
|
|
if err := dec.Decode(l); err == io.EOF {
|
|
|
|
break
|
|
|
|
} else if err != nil {
|
2014-07-25 00:37:44 +04:00
|
|
|
log.Errorf("Error streaming logs: %s", err)
|
2014-08-01 01:03:21 +04:00
|
|
|
break
|
|
|
|
}
|
|
|
|
logLine := l.Log
|
|
|
|
if times {
|
2015-01-29 01:33:15 +03:00
|
|
|
// format can be "" or time format, so here can't be error
|
|
|
|
logLine, _ = l.Format(format)
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
if l.Stream == "stdout" && stdout {
|
2014-09-18 17:29:19 +04:00
|
|
|
io.WriteString(job.Stdout, logLine)
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
if l.Stream == "stderr" && stderr {
|
2014-09-18 17:29:19 +04:00
|
|
|
io.WriteString(job.Stderr, logLine)
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2014-09-18 17:29:19 +04:00
|
|
|
l.Reset()
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-08-31 19:20:35 +04:00
|
|
|
if follow && container.IsRunning() {
|
2014-08-01 01:03:21 +04:00
|
|
|
errors := make(chan error, 2)
|
2014-10-30 22:33:26 +03:00
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
|
2014-08-01 01:03:21 +04:00
|
|
|
if stdout {
|
2014-10-30 22:33:26 +03:00
|
|
|
wg.Add(1)
|
2014-08-01 01:03:21 +04:00
|
|
|
stdoutPipe := container.StdoutLogPipe()
|
2014-09-22 10:55:46 +04:00
|
|
|
defer stdoutPipe.Close()
|
2014-08-01 01:03:21 +04:00
|
|
|
go func() {
|
2014-08-07 03:45:04 +04:00
|
|
|
errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format)
|
2014-10-30 22:33:26 +03:00
|
|
|
wg.Done()
|
2014-08-01 01:03:21 +04:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
if stderr {
|
2014-10-30 22:33:26 +03:00
|
|
|
wg.Add(1)
|
2014-08-01 01:03:21 +04:00
|
|
|
stderrPipe := container.StderrLogPipe()
|
2014-09-22 10:55:46 +04:00
|
|
|
defer stderrPipe.Close()
|
2014-08-01 01:03:21 +04:00
|
|
|
go func() {
|
2014-08-07 03:45:04 +04:00
|
|
|
errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format)
|
2014-10-30 22:33:26 +03:00
|
|
|
wg.Done()
|
2014-08-01 01:03:21 +04:00
|
|
|
}()
|
|
|
|
}
|
2014-10-30 22:33:26 +03:00
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
close(errors)
|
|
|
|
|
|
|
|
for err := range errors {
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("%s", err)
|
|
|
|
}
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
2014-10-30 22:33:26 +03:00
|
|
|
|
2014-08-01 01:03:21 +04:00
|
|
|
}
|
|
|
|
return engine.StatusOK
|
|
|
|
}
|