зеркало из https://github.com/microsoft/docker.git
Add --since argument to docker logs cmd
Added --since argument to `docker logs` command. Accept unix timestamps and shows logs only created after the specified date. Default value is 0 and passing default value or not specifying the value in the request causes parameter to be ignored (behavior prior to this change). Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
This commit is contained in:
Родитель
340fd140e6
Коммит
cb9a6b9aed
|
@ -2,8 +2,6 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/docker/opts"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
@ -26,7 +24,6 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
v = url.Values{}
|
v = url.Values{}
|
||||||
loc = time.FixedZone(time.Now().Zone())
|
|
||||||
eventFilterArgs = filters.Args{}
|
eventFilterArgs = filters.Args{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,22 +36,11 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var setTime = func(key, value string) {
|
|
||||||
format := timeutils.RFC3339NanoFixed
|
|
||||||
if len(value) < len(format) {
|
|
||||||
format = format[:len(value)]
|
|
||||||
}
|
|
||||||
if t, err := time.ParseInLocation(format, value, loc); err == nil {
|
|
||||||
v.Set(key, strconv.FormatInt(t.Unix(), 10))
|
|
||||||
} else {
|
|
||||||
v.Set(key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if *since != "" {
|
if *since != "" {
|
||||||
setTime("since", *since)
|
v.Set("since", timeutils.GetTimestamp(*since))
|
||||||
}
|
}
|
||||||
if *until != "" {
|
if *until != "" {
|
||||||
setTime("until", *until)
|
v.Set("until", timeutils.GetTimestamp(*until))
|
||||||
}
|
}
|
||||||
if len(eventFilterArgs) > 0 {
|
if len(eventFilterArgs) > 0 {
|
||||||
filterJSON, err := filters.ToParam(eventFilterArgs)
|
filterJSON, err := filters.ToParam(eventFilterArgs)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
"github.com/docker/docker/pkg/timeutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdLogs fetches the logs of a given container.
|
// CmdLogs fetches the logs of a given container.
|
||||||
|
@ -16,6 +17,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
var (
|
var (
|
||||||
cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
|
cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true)
|
||||||
follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
||||||
|
since = cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
||||||
times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
||||||
tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs")
|
||||||
)
|
)
|
||||||
|
@ -43,6 +45,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
v.Set("stdout", "1")
|
v.Set("stdout", "1")
|
||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
|
|
||||||
|
if *since != "" {
|
||||||
|
v.Set("since", timeutils.GetTimestamp(*since))
|
||||||
|
}
|
||||||
|
|
||||||
if *times {
|
if *times {
|
||||||
v.Set("timestamps", "1")
|
v.Set("timestamps", "1")
|
||||||
}
|
}
|
||||||
|
|
|
@ -594,9 +594,19 @@ func (s *Server) getContainersLogs(version version.Version, w http.ResponseWrite
|
||||||
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var since time.Time
|
||||||
|
if r.Form.Get("since") != "" {
|
||||||
|
s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
since = time.Unix(s, 0)
|
||||||
|
}
|
||||||
|
|
||||||
logsConfig := &daemon.ContainerLogsConfig{
|
logsConfig := &daemon.ContainerLogsConfig{
|
||||||
Follow: boolValue(r, "follow"),
|
Follow: boolValue(r, "follow"),
|
||||||
Timestamps: boolValue(r, "timestamps"),
|
Timestamps: boolValue(r, "timestamps"),
|
||||||
|
Since: since,
|
||||||
Tail: r.Form.Get("tail"),
|
Tail: r.Form.Get("tail"),
|
||||||
UseStdout: stdout,
|
UseStdout: stdout,
|
||||||
UseStderr: stderr,
|
UseStderr: stderr,
|
||||||
|
|
|
@ -593,7 +593,7 @@ _docker_logs() {
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--follow -f --help --tail --timestamps -t" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "--follow -f --help --since --tail --timestamps -t" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
local counter=$(__docker_pos_first_nonflag '--tail')
|
local counter=$(__docker_pos_first_nonflag '--tail')
|
||||||
|
|
|
@ -233,6 +233,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a logs -d 'Fetch the log
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s f -l follow -d 'Follow log output'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s f -l follow -d 'Follow log output'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l help -d 'Print usage'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l help -d 'Print usage'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s t -l timestamps -d 'Show timestamps'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -s t -l timestamps -d 'Show timestamps'
|
||||||
|
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l since -d 'Show logs since timestamp'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l tail -d 'Output the specified number of lines at the end of logs (defaults to all logs)'
|
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -l tail -d 'Output the specified number of lines at the end of logs (defaults to all logs)'
|
||||||
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -a '(__fish_print_docker_containers running)' -d "Container"
|
complete -c docker -A -f -n '__fish_seen_subcommand_from logs' -a '(__fish_print_docker_containers running)' -d "Container"
|
||||||
|
|
||||||
|
|
|
@ -305,6 +305,7 @@ __docker_subcommand () {
|
||||||
(logs)
|
(logs)
|
||||||
_arguments \
|
_arguments \
|
||||||
{-f,--follow}'[Follow log output]' \
|
{-f,--follow}'[Follow log output]' \
|
||||||
|
'-s,--since[Show logs since timestamp]' \
|
||||||
{-t,--timestamps}'[Show timestamps]' \
|
{-t,--timestamps}'[Show timestamps]' \
|
||||||
'--tail=-[Output the last K lines]:lines:(1 10 20 50 all)' \
|
'--tail=-[Output the last K lines]:lines:(1 10 20 50 all)' \
|
||||||
'*:containers:__docker_containers'
|
'*:containers:__docker_containers'
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/pkg/jsonlog"
|
"github.com/docker/docker/pkg/jsonlog"
|
||||||
|
@ -19,6 +20,7 @@ import (
|
||||||
type ContainerLogsConfig struct {
|
type ContainerLogsConfig struct {
|
||||||
Follow, Timestamps bool
|
Follow, Timestamps bool
|
||||||
Tail string
|
Tail string
|
||||||
|
Since time.Time
|
||||||
UseStdout, UseStderr bool
|
UseStdout, UseStderr bool
|
||||||
OutStream io.Writer
|
OutStream io.Writer
|
||||||
}
|
}
|
||||||
|
@ -88,6 +90,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
lines = -1
|
lines = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if lines != 0 {
|
if lines != 0 {
|
||||||
if lines > 0 {
|
if lines > 0 {
|
||||||
f := cLog.(*os.File)
|
f := cLog.(*os.File)
|
||||||
|
@ -101,9 +104,11 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
}
|
}
|
||||||
cLog = tmp
|
cLog = tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
dec := json.NewDecoder(cLog)
|
dec := json.NewDecoder(cLog)
|
||||||
l := &jsonlog.JSONLog{}
|
l := &jsonlog.JSONLog{}
|
||||||
for {
|
for {
|
||||||
|
l.Reset()
|
||||||
if err := dec.Decode(l); err == io.EOF {
|
if err := dec.Decode(l); err == io.EOF {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -111,6 +116,9 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
logLine := l.Log
|
logLine := l.Log
|
||||||
|
if !config.Since.IsZero() && l.Created.Before(config.Since) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if config.Timestamps {
|
if config.Timestamps {
|
||||||
// format can be "" or time format, so here can't be error
|
// format can be "" or time format, so here can't be error
|
||||||
logLine, _ = l.Format(format)
|
logLine, _ = l.Format(format)
|
||||||
|
@ -121,7 +129,6 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
if l.Stream == "stderr" && config.UseStderr {
|
if l.Stream == "stderr" && config.UseStderr {
|
||||||
io.WriteString(errStream, logLine)
|
io.WriteString(errStream, logLine)
|
||||||
}
|
}
|
||||||
l.Reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +146,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
stdoutPipe := container.StdoutLogPipe()
|
stdoutPipe := container.StdoutLogPipe()
|
||||||
defer stdoutPipe.Close()
|
defer stdoutPipe.Close()
|
||||||
go func() {
|
go func() {
|
||||||
errors <- jsonlog.WriteLog(stdoutPipe, outStream, format)
|
errors <- jsonlog.WriteLog(stdoutPipe, outStream, format, config.Since)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -148,7 +155,7 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er
|
||||||
stderrPipe := container.StderrLogPipe()
|
stderrPipe := container.StderrLogPipe()
|
||||||
defer stderrPipe.Close()
|
defer stderrPipe.Close()
|
||||||
go func() {
|
go func() {
|
||||||
errors <- jsonlog.WriteLog(stderrPipe, errStream, format)
|
errors <- jsonlog.WriteLog(stderrPipe, errStream, format, config.Since)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ docker-logs - Fetch the logs of a container
|
||||||
**docker logs**
|
**docker logs**
|
||||||
[**-f**|**--follow**[=*false*]]
|
[**-f**|**--follow**[=*false*]]
|
||||||
[**--help**]
|
[**--help**]
|
||||||
|
[**--since**[=*SINCE*]]
|
||||||
[**-t**|**--timestamps**[=*false*]]
|
[**-t**|**--timestamps**[=*false*]]
|
||||||
[**--tail**[=*"all"*]]
|
[**--tail**[=*"all"*]]
|
||||||
CONTAINER
|
CONTAINER
|
||||||
|
@ -31,6 +32,9 @@ then continue streaming new output from the container’s stdout and stderr.
|
||||||
**-f**, **--follow**=*true*|*false*
|
**-f**, **--follow**=*true*|*false*
|
||||||
Follow log output. The default is *false*.
|
Follow log output. The default is *false*.
|
||||||
|
|
||||||
|
**--since**=""
|
||||||
|
Show logs since timestamp
|
||||||
|
|
||||||
**-t**, **--timestamps**=*true*|*false*
|
**-t**, **--timestamps**=*true*|*false*
|
||||||
Show timestamps. The default is *false*.
|
Show timestamps. The default is *false*.
|
||||||
|
|
||||||
|
@ -42,3 +46,4 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||||
based on docker.com source material and internal work.
|
based on docker.com source material and internal work.
|
||||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||||
July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||||
|
April 2015, updated by Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
|
||||||
|
|
|
@ -52,6 +52,12 @@ You can still call an old version of the API using
|
||||||
You can now supply a `stream` bool to get only one set of stats and
|
You can now supply a `stream` bool to get only one set of stats and
|
||||||
disconnect
|
disconnect
|
||||||
|
|
||||||
|
`GET /containers(id)/logs`
|
||||||
|
|
||||||
|
**New!**
|
||||||
|
|
||||||
|
This endpoint now accepts a `since` timestamp parameter.
|
||||||
|
|
||||||
## v1.18
|
## v1.18
|
||||||
|
|
||||||
### Full documentation
|
### Full documentation
|
||||||
|
|
|
@ -477,7 +477,7 @@ Get stdout and stderr logs from the container ``id``
|
||||||
|
|
||||||
**Example request**:
|
**Example request**:
|
||||||
|
|
||||||
GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1×tamps=1&follow=1&tail=10 HTTP/1.1
|
GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1×tamps=1&follow=1&tail=10&since=1428990821 HTTP/1.1
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
|
|
||||||
|
@ -493,6 +493,8 @@ Query Parameters:
|
||||||
- **follow** – 1/True/true or 0/False/false, return stream. Default false
|
- **follow** – 1/True/true or 0/False/false, return stream. Default false
|
||||||
- **stdout** – 1/True/true or 0/False/false, show stdout log. Default false
|
- **stdout** – 1/True/true or 0/False/false, show stdout log. Default false
|
||||||
- **stderr** – 1/True/true or 0/False/false, show stderr log. Default false
|
- **stderr** – 1/True/true or 0/False/false, show stderr log. Default false
|
||||||
|
- **since** – UNIX timestamp (integer) to filter logs. Specifying a timestamp
|
||||||
|
will only output log-entries since that timestamp. Default: 0 (unfiltered)
|
||||||
- **timestamps** – 1/True/true or 0/False/false, print timestamps for
|
- **timestamps** – 1/True/true or 0/False/false, print timestamps for
|
||||||
every log line. Default false
|
every log line. Default false
|
||||||
- **tail** – Output specified number of lines at the end of logs: `all` or `<number>`. Default all
|
- **tail** – Output specified number of lines at the end of logs: `all` or `<number>`. Default all
|
||||||
|
|
|
@ -1616,6 +1616,7 @@ For example:
|
||||||
Fetch the logs of a container
|
Fetch the logs of a container
|
||||||
|
|
||||||
-f, --follow=false Follow log output
|
-f, --follow=false Follow log output
|
||||||
|
--since="" Show logs since timestamp
|
||||||
-t, --timestamps=false Show timestamps
|
-t, --timestamps=false Show timestamps
|
||||||
--tail="all" Number of lines to show from the end of the logs
|
--tail="all" Number of lines to show from the end of the logs
|
||||||
|
|
||||||
|
@ -1635,6 +1636,10 @@ timestamp, for example `2014-09-16T06:17:46.000000000Z`, to each
|
||||||
log entry. To ensure that the timestamps for are aligned the
|
log entry. To ensure that the timestamps for are aligned the
|
||||||
nano-second part of the timestamp will be padded with zero when necessary.
|
nano-second part of the timestamp will be padded with zero when necessary.
|
||||||
|
|
||||||
|
The `--since` option shows logs of a container generated only after
|
||||||
|
the given date, specified as RFC 3339 or UNIX timestamp. The `--since` option
|
||||||
|
can be combined with the `--follow` and `--tail` options.
|
||||||
|
|
||||||
## pause
|
## pause
|
||||||
|
|
||||||
Usage: docker pause CONTAINER [CONTAINER...]
|
Usage: docker pause CONTAINER [CONTAINER...]
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -276,6 +277,80 @@ func (s *DockerSuite) TestLogsFollowStopped(c *check.C) {
|
||||||
deleteContainer(cleanedContainerID)
|
deleteContainer(cleanedContainerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestLogsSince(c *check.C) {
|
||||||
|
name := "testlogssince"
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "--name="+name, "busybox", "/bin/sh", "-c", `date +%s; for i in $(seq 1 5); do sleep 1; echo log$i; done`)
|
||||||
|
out, _, err := runCommandWithOutput(runCmd)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("run failed with errors: %s, %v", out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outLines := strings.Split(out, "\n")
|
||||||
|
startUnix, _ := strconv.ParseInt(outLines[0], 10, 64)
|
||||||
|
since := startUnix + 3
|
||||||
|
logsCmd := exec.Command(dockerBinary, "logs", "-t", fmt.Sprintf("--since=%v", since), name)
|
||||||
|
|
||||||
|
out, _, err = runCommandWithOutput(logsCmd)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("failed to log container: %s, %v", out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip 2 seconds
|
||||||
|
unexpected := []string{"log1", "log2"}
|
||||||
|
for _, v := range unexpected {
|
||||||
|
if strings.Contains(out, v) {
|
||||||
|
c.Fatalf("unexpected log message returned=%v, since=%v\nout=%v", v, since, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with default value specified and parameter omitted
|
||||||
|
expected := []string{"log1", "log2", "log3", "log4", "log5"}
|
||||||
|
for _, cmd := range []*exec.Cmd{
|
||||||
|
exec.Command(dockerBinary, "logs", "-t", name),
|
||||||
|
exec.Command(dockerBinary, "logs", "-t", "--since=0", name),
|
||||||
|
} {
|
||||||
|
out, _, err = runCommandWithOutput(cmd)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("failed to log container: %s, %v", out, err)
|
||||||
|
}
|
||||||
|
for _, v := range expected {
|
||||||
|
if !strings.Contains(out, v) {
|
||||||
|
c.Fatalf("'%v' does not contain=%v\nout=%s", cmd.Args, v, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSuite) TestLogsSinceFutureFollow(c *check.C) {
|
||||||
|
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `for i in $(seq 1 5); do date +%s; sleep 1; done`)
|
||||||
|
out, _, err := runCommandWithOutput(runCmd)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("run failed with errors: %s, %v", out, err)
|
||||||
|
}
|
||||||
|
cleanedContainerID := strings.TrimSpace(out)
|
||||||
|
|
||||||
|
now := daemonTime(c).Unix()
|
||||||
|
since := now + 2
|
||||||
|
logCmd := exec.Command(dockerBinary, "logs", "-f", fmt.Sprintf("--since=%v", since), cleanedContainerID)
|
||||||
|
out, _, err = runCommandWithOutput(logCmd)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("failed to log container: %s, %v", out, err)
|
||||||
|
}
|
||||||
|
lines := strings.Split(strings.TrimSpace(out), "\n")
|
||||||
|
if len(lines) == 0 {
|
||||||
|
c.Fatal("got no log lines")
|
||||||
|
}
|
||||||
|
for _, v := range lines {
|
||||||
|
ts, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("cannot parse timestamp output from log: '%v'\nout=%s", v, out)
|
||||||
|
}
|
||||||
|
if ts < since {
|
||||||
|
c.Fatalf("earlier log found. since=%v logdate=%v", since, ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Regression test for #8832
|
// Regression test for #8832
|
||||||
func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) {
|
func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) {
|
||||||
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `usleep 200000;yes X | head -c 200000`)
|
runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `usleep 200000;yes X | head -c 200000`)
|
||||||
|
|
|
@ -32,16 +32,20 @@ func (jl *JSONLog) Reset() {
|
||||||
jl.Created = time.Time{}
|
jl.Created = time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteLog(src io.Reader, dst io.Writer, format string) error {
|
func WriteLog(src io.Reader, dst io.Writer, format string, since time.Time) error {
|
||||||
dec := json.NewDecoder(src)
|
dec := json.NewDecoder(src)
|
||||||
l := &JSONLog{}
|
l := &JSONLog{}
|
||||||
for {
|
for {
|
||||||
|
l.Reset()
|
||||||
if err := dec.Decode(l); err == io.EOF {
|
if err := dec.Decode(l); err == io.EOF {
|
||||||
return nil
|
return nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
logrus.Printf("Error streaming logs: %s", err)
|
logrus.Printf("Error streaming logs: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !since.IsZero() && l.Created.Before(since) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
line, err := l.Format(format)
|
line, err := l.Format(format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -49,6 +53,5 @@ func WriteLog(src io.Reader, dst io.Writer, format string) error {
|
||||||
if _, err := io.WriteString(dst, line); err != nil {
|
if _, err := io.WriteString(dst, line); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
l.Reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ func TestWriteLog(t *testing.T) {
|
||||||
}
|
}
|
||||||
w := bytes.NewBuffer(nil)
|
w := bytes.NewBuffer(nil)
|
||||||
format := timeutils.RFC3339NanoFixed
|
format := timeutils.RFC3339NanoFixed
|
||||||
if err := WriteLog(&buf, w, format); err != nil {
|
if err := WriteLog(&buf, w, format, time.Time{}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
res := w.String()
|
res := w.String()
|
||||||
|
@ -52,7 +52,7 @@ func BenchmarkWriteLog(b *testing.B) {
|
||||||
b.SetBytes(int64(r.Len()))
|
b.SetBytes(int64(r.Len()))
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
if err := WriteLog(r, w, format); err != nil {
|
if err := WriteLog(r, w, format, time.Time{}); err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package timeutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTimestamp tries to parse given string as RFC3339 time
|
||||||
|
// or Unix timestamp, if successful returns a Unix timestamp
|
||||||
|
// as string otherwise returns value back.
|
||||||
|
func GetTimestamp(value string) string {
|
||||||
|
format := RFC3339NanoFixed
|
||||||
|
loc := time.FixedZone(time.Now().Zone())
|
||||||
|
if len(value) < len(format) {
|
||||||
|
format = format[:len(value)]
|
||||||
|
}
|
||||||
|
t, err := time.ParseInLocation(format, value, loc)
|
||||||
|
if err != nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(t.Unix(), 10)
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче