|
@@ -22,6 +22,7 @@
|
|
package server
|
|
package server
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
+ "bytes"
|
|
"encoding/json"
|
|
"encoding/json"
|
|
"fmt"
|
|
"fmt"
|
|
"io"
|
|
"io"
|
|
@@ -52,6 +53,7 @@ import (
|
|
"github.com/dotcloud/docker/image"
|
|
"github.com/dotcloud/docker/image"
|
|
"github.com/dotcloud/docker/pkg/graphdb"
|
|
"github.com/dotcloud/docker/pkg/graphdb"
|
|
"github.com/dotcloud/docker/pkg/signal"
|
|
"github.com/dotcloud/docker/pkg/signal"
|
|
|
|
+ "github.com/dotcloud/docker/pkg/tailfile"
|
|
"github.com/dotcloud/docker/registry"
|
|
"github.com/dotcloud/docker/registry"
|
|
"github.com/dotcloud/docker/runconfig"
|
|
"github.com/dotcloud/docker/runconfig"
|
|
"github.com/dotcloud/docker/utils"
|
|
"github.com/dotcloud/docker/utils"
|
|
@@ -2153,8 +2155,10 @@ func (srv *Server) ContainerLogs(job *engine.Job) engine.Status {
|
|
name = job.Args[0]
|
|
name = job.Args[0]
|
|
stdout = job.GetenvBool("stdout")
|
|
stdout = job.GetenvBool("stdout")
|
|
stderr = job.GetenvBool("stderr")
|
|
stderr = job.GetenvBool("stderr")
|
|
|
|
+ tail = job.Getenv("tail")
|
|
follow = job.GetenvBool("follow")
|
|
follow = job.GetenvBool("follow")
|
|
times = job.GetenvBool("timestamps")
|
|
times = job.GetenvBool("timestamps")
|
|
|
|
+ lines = -1
|
|
format string
|
|
format string
|
|
)
|
|
)
|
|
if !(stdout || stderr) {
|
|
if !(stdout || stderr) {
|
|
@@ -2163,6 +2167,9 @@ func (srv *Server) ContainerLogs(job *engine.Job) engine.Status {
|
|
if times {
|
|
if times {
|
|
format = time.StampMilli
|
|
format = time.StampMilli
|
|
}
|
|
}
|
|
|
|
+ if tail == "" {
|
|
|
|
+ tail = "all"
|
|
|
|
+ }
|
|
container := srv.daemon.Get(name)
|
|
container := srv.daemon.Get(name)
|
|
if container == nil {
|
|
if container == nil {
|
|
return job.Errorf("No such container: %s", name)
|
|
return job.Errorf("No such container: %s", name)
|
|
@@ -2190,25 +2197,47 @@ func (srv *Server) ContainerLogs(job *engine.Job) engine.Status {
|
|
} else if err != nil {
|
|
} else if err != nil {
|
|
utils.Errorf("Error reading logs (json): %s", err)
|
|
utils.Errorf("Error reading logs (json): %s", err)
|
|
} else {
|
|
} else {
|
|
- dec := json.NewDecoder(cLog)
|
|
|
|
- for {
|
|
|
|
- l := &utils.JSONLog{}
|
|
|
|
-
|
|
|
|
- if err := dec.Decode(l); err == io.EOF {
|
|
|
|
- break
|
|
|
|
- } else if err != nil {
|
|
|
|
- utils.Errorf("Error streaming logs: %s", err)
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
- logLine := l.Log
|
|
|
|
- if times {
|
|
|
|
- logLine = fmt.Sprintf("[%s] %s", l.Created.Format(format), logLine)
|
|
|
|
|
|
+ if tail != "all" {
|
|
|
|
+ var err error
|
|
|
|
+ lines, err = strconv.Atoi(tail)
|
|
|
|
+ if err != nil {
|
|
|
|
+ utils.Errorf("Failed to parse tail %s, error: %v, show all logs", err)
|
|
|
|
+ lines = -1
|
|
}
|
|
}
|
|
- if l.Stream == "stdout" && stdout {
|
|
|
|
- fmt.Fprintf(job.Stdout, "%s", logLine)
|
|
|
|
|
|
+ }
|
|
|
|
+ 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
|
|
}
|
|
}
|
|
- if l.Stream == "stderr" && stderr {
|
|
|
|
- fmt.Fprintf(job.Stderr, "%s", logLine)
|
|
|
|
|
|
+ dec := json.NewDecoder(cLog)
|
|
|
|
+ for {
|
|
|
|
+ l := &utils.JSONLog{}
|
|
|
|
+
|
|
|
|
+ if err := dec.Decode(l); err == io.EOF {
|
|
|
|
+ break
|
|
|
|
+ } else if err != nil {
|
|
|
|
+ utils.Errorf("Error streaming logs: %s", err)
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ logLine := l.Log
|
|
|
|
+ if times {
|
|
|
|
+ logLine = fmt.Sprintf("[%s] %s", l.Created.Format(format), logLine)
|
|
|
|
+ }
|
|
|
|
+ if l.Stream == "stdout" && stdout {
|
|
|
|
+ fmt.Fprintf(job.Stdout, "%s", logLine)
|
|
|
|
+ }
|
|
|
|
+ if l.Stream == "stderr" && stderr {
|
|
|
|
+ fmt.Fprintf(job.Stderr, "%s", logLine)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|