From 0f708efd8be843e06dd8a0ac68101a875a82c90e Mon Sep 17 00:00:00 2001 From: cdoern Date: Tue, 6 Jul 2021 14:53:56 -0400 Subject: Implemented --until flag for libpod's container logs compat containers/logs was missing actual usage of until query param. This led me to implement the until param for libpod's container logs as well. Added e2e tests. Signed-off-by: cdoern --- cmd/podman/containers/logs.go | 14 ++++++++++ docs/source/markdown/podman-logs.1.md | 29 +++++++++++++++++++++ libpod/container_log_linux.go | 19 +++----------- pkg/domain/entities/containers.go | 2 ++ pkg/domain/infra/abi/containers.go | 1 + pkg/domain/infra/tunnel/containers.go | 3 ++- test/e2e/logs_test.go | 29 +++++++++++++++++++++ test/system/035-logs.bats | 49 +++++++++++++++++++++++++++++++++++ 8 files changed, 129 insertions(+), 17 deletions(-) diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index 0d745291e..00a8d4b52 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -19,6 +19,8 @@ type logsOptionsWrapper struct { entities.ContainerLogsOptions SinceRaw string + + UntilRaw string } var ( @@ -101,6 +103,10 @@ func logsFlags(cmd *cobra.Command) { flags.StringVar(&logsOptions.SinceRaw, sinceFlagName, "", "Show logs since TIMESTAMP") _ = cmd.RegisterFlagCompletionFunc(sinceFlagName, completion.AutocompleteNone) + untilFlagName := "until" + flags.StringVar(&logsOptions.UntilRaw, untilFlagName, "", "Show logs until TIMESTAMP") + _ = cmd.RegisterFlagCompletionFunc(untilFlagName, completion.AutocompleteNone) + tailFlagName := "tail" flags.Int64Var(&logsOptions.Tail, tailFlagName, -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") _ = cmd.RegisterFlagCompletionFunc(tailFlagName, completion.AutocompleteNone) @@ -120,6 +126,14 @@ func logs(_ *cobra.Command, args []string) error { } logsOptions.Since = since } + if logsOptions.UntilRaw != "" { + // parse time, error out if something is wrong + until, err := util.ParseInputTime(logsOptions.UntilRaw) + if err != nil { + return errors.Wrapf(err, "error parsing --until %q", logsOptions.UntilRaw) + } + logsOptions.Until = until + } logsOptions.StdoutWriter = os.Stdout logsOptions.StderrWriter = os.Stderr return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions) diff --git a/docs/source/markdown/podman-logs.1.md b/docs/source/markdown/podman-logs.1.md index 8f1301102..6b3ef79d7 100644 --- a/docs/source/markdown/podman-logs.1.md +++ b/docs/source/markdown/podman-logs.1.md @@ -39,6 +39,14 @@ strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Suppor time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00, and 2006-01-02. +#### **--until**=*TIMESTAMP* + +Show logs until TIMESTAMP. The --until option can be Unix timestamps, date formatted timestamps, or Go duration +strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted +time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00, +and 2006-01-02. + + #### **--tail**=*LINES* Output the specified number of LINES at the end of the logs. LINES must be an integer. Defaults to -1, @@ -74,6 +82,17 @@ podman logs --tail 2 b3f2436bdb97 # Server initialized ``` +To view all containers logs: +``` +podman logs -t --since 0 myserver + +1:M 07 Aug 14:10:09.055 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted. +1:M 07 Aug 14:10:09.055 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'. +1:M 07 Aug 14:10:09.056 * Running mode=standalone, port=6379. +1:M 07 Aug 14:10:09.056 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. +1:M 07 Aug 14:10:09.056 # Server initialized +``` + To view a containers logs since a certain time: ``` podman logs -t --since 2017-08-07T10:10:09.055837383-04:00 myserver @@ -93,6 +112,16 @@ podman logs --since 10m myserver # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'. ``` +To view a container's logs until 30 minutes ago: +``` +podman logs --until 30m myserver + +AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.0.2.100. Set the 'ServerName' directive globally to suppress this message +AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.0.2.100. Set the 'ServerName' directive globally to suppress this message +[Tue Jul 20 13:18:14.223727 2021] [mpm_event:notice] [pid 1:tid 140021067187328] AH00489: Apache/2.4.48 (Unix) configured -- resuming normal operations +[Tue Jul 20 13:18:14.223819 2021] [core:notice] [pid 1:tid 140021067187328] AH00094: Command line: 'httpd -D FOREGROUND' +``` + ## SEE ALSO podman(1), podman-run(1), podman-container-rm(1) diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 9f9dd3b0d..d4afaa52a 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -97,8 +97,6 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption } }() - beforeTimeStamp := true - afterTimeStamp := false // needed for options.Since tailQueue := []*logs.LogLine{} // needed for options.Tail doTail := options.Tail > 0 for { @@ -150,21 +148,10 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption return } - if !afterTimeStamp { - entryTime := time.Unix(0, int64(entry.RealtimeTimestamp)*int64(time.Microsecond)) - if entryTime.Before(options.Since) { - continue - } - afterTimeStamp = true - } - if beforeTimeStamp { - entryTime := time.Unix(0, int64(entry.RealtimeTimestamp)*int64(time.Microsecond)) - if entryTime.Before(options.Until) || !options.Until.IsZero() { - continue - } - beforeTimeStamp = false + entryTime := time.Unix(0, int64(entry.RealtimeTimestamp)*int64(time.Microsecond)) + if (entryTime.Before(options.Since) && !options.Since.IsZero()) || (entryTime.After(options.Until) && !options.Until.IsZero()) { + continue } - // If we're reading an event and the container exited/died, // then we're done and can return. event, ok := entry.Fields["PODMAN_EVENT"] diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 302b35a47..4d85941cd 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -242,6 +242,8 @@ type ContainerLogsOptions struct { Names bool // Show logs since this timestamp. Since time.Time + // Show logs until this timestamp. + Until time.Time // Number of lines to display at the end of the output. Tail int64 // Show timestamps in the logs. diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 50751aa12..485ab944d 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -998,6 +998,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin Details: options.Details, Follow: options.Follow, Since: options.Since, + Until: options.Until, Tail: options.Tail, Timestamps: options.Timestamps, UseName: options.Names, diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index c17d7b54f..d7e8a4e46 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -369,10 +369,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, opts entities.ContainerLogsOptions) error { since := opts.Since.Format(time.RFC3339) + until := opts.Until.Format(time.RFC3339) tail := strconv.FormatInt(opts.Tail, 10) stdout := opts.StdoutWriter != nil stderr := opts.StderrWriter != nil - options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithStderr(stderr) + options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithUntil(until).WithStderr(stderr) options.WithStdout(stdout).WithTail(tail) var err error diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 507fab461..0a973b802 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "strings" + "time" . "github.com/containers/podman/v3/test/utils" . "github.com/onsi/ginkgo" @@ -135,6 +136,34 @@ var _ = Describe("Podman logs", func() { Expect(len(results.OutputToStringArray())).To(Equal(3)) }) + It("until duration 10m: "+log, func() { + logc := podmanTest.Podman([]string{"run", "--log-driver", log, "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc).To(Exit(0)) + cid := logc.OutputToString() + + results := podmanTest.Podman([]string{"logs", "--until", "10m", cid}) + results.WaitWithDefaultTimeout() + Expect(results).To(Exit(0)) + Expect(len(results.OutputToStringArray())).To(Equal(0)) + }) + + It("until time NOW: "+log, func() { + + logc := podmanTest.Podman([]string{"run", "--log-driver", log, "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + logc.WaitWithDefaultTimeout() + Expect(logc).To(Exit(0)) + cid := logc.OutputToString() + + now := time.Now() + now = now.Add(time.Minute * 1) + nowS := now.Format(time.RFC3339) + results := podmanTest.Podman([]string{"logs", "--until", nowS, cid}) + results.WaitWithDefaultTimeout() + Expect(results).To(Exit(0)) + Expect(len(results.OutputToStringArray())).To(Equal(3)) + }) + It("latest and container name should fail: "+log, func() { results := podmanTest.Podman([]string{"logs", "-l", "foobar"}) results.WaitWithDefaultTimeout() diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index ccf83df14..32282c8e1 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -24,6 +24,9 @@ load helpers # test --since with Unix timestamps run_podman logs --since 1000 $cid + # test --until with Unix timestamps + run_podman logs --until 1000 $cid + run_podman rm $cid } @@ -125,4 +128,50 @@ $s_after" _log_test_since journald } +function _log_test_until() { + local driver=$1 + + s_before="before_$(random_string)_${driver}" + s_after="after_$(random_string)_${driver}" + + before=$(date --iso-8601=seconds) + sleep 5 + run_podman run --log-driver=$driver -d --name test $IMAGE sh -c \ + "echo $s_before; trap 'echo $s_after; exit' SIGTERM; while :; do sleep 1; done" + + # sleep a second to make sure the date is after the first echo + sleep 1 + run_podman stop test + # sleep for 20 seconds to get the proper after time + sleep 20 + + run_podman logs test + is "$output" \ + "$s_before +$s_after" + + run_podman logs --until $before test + is "$output" \ + "" + + after=$(date --iso-8601=seconds) + + run_podman logs --until $after test + is "$output" \ + "$s_before +$s_after" + run_podman rm -f test +} + +@test "podman logs - until k8s-file" { + _log_test_until k8s-file +} + +@test "podman logs - until journald" { + # We can't use journald on RHEL as rootless: rhbz#1895105 + skip_if_journald_unavailable + + _log_test_until journald +} + # vim: filetype=sh -- cgit v1.2.3-54-g00ecf