summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podmanV2/containers/logs.go108
-rw-r--r--pkg/api/handlers/compat/containers.go1
-rw-r--r--pkg/domain/entities/containers.go20
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/infra/abi/containers.go48
-rw-r--r--pkg/domain/infra/tunnel/containers.go5
6 files changed, 183 insertions, 0 deletions
diff --git a/cmd/podmanV2/containers/logs.go b/cmd/podmanV2/containers/logs.go
new file mode 100644
index 000000000..d1a179495
--- /dev/null
+++ b/cmd/podmanV2/containers/logs.go
@@ -0,0 +1,108 @@
+package containers
+
+import (
+ "os"
+
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+)
+
+// logsOptionsWrapper wraps entities.LogsOptions and prevents leaking
+// CLI-only fields into the API types.
+type logsOptionsWrapper struct {
+ entities.ContainerLogsOptions
+
+ SinceRaw string
+}
+
+var (
+ logsOptions logsOptionsWrapper
+ logsDescription = `Retrieves logs for one or more containers.
+
+ This does not guarantee execution order when combined with podman run (i.e., your run may not have generated any logs at the time you execute podman logs).
+`
+ logsCommand = &cobra.Command{
+ Use: "logs [flags] CONTAINER [CONTAINER...]",
+ Short: "Fetch the logs of one or more container",
+ Long: logsDescription,
+ RunE: logs,
+ PreRunE: preRunE,
+ Example: `podman logs ctrID
+ podman logs --names ctrID1 ctrID2
+ podman logs --tail 2 mywebserver
+ podman logs --follow=true --since 10m ctrID
+ podman logs mywebserver mydbserver`,
+ }
+
+ containerLogsCommand = &cobra.Command{
+ Use: logsCommand.Use,
+ Short: logsCommand.Short,
+ Long: logsCommand.Long,
+ PreRunE: logsCommand.PreRunE,
+ RunE: logsCommand.RunE,
+ Example: `podman container logs ctrID
+ podman container logs --names ctrID1 ctrID2
+ podman container logs --tail 2 mywebserver
+ podman container logs --follow=true --since 10m ctrID
+ podman container logs mywebserver mydbserver`,
+ }
+)
+
+func init() {
+ // logs
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: logsCommand,
+ })
+
+ logsCommand.SetHelpTemplate(registry.HelpTemplate())
+ logsCommand.SetUsageTemplate(registry.UsageTemplate())
+
+ flags := logsCommand.Flags()
+ logsFlags(flags)
+
+ // container logs
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerLogsCommand,
+ Parent: containerCmd,
+ })
+
+ containerLogsFlags := containerLogsCommand.Flags()
+ logsFlags(containerLogsFlags)
+}
+
+func logsFlags(flags *pflag.FlagSet) {
+ flags.BoolVar(&logsOptions.Details, "details", false, "Show extra details provided to the logs")
+ flags.BoolVarP(&logsOptions.Follow, "follow", "f", false, "Follow log output. The default is false")
+ flags.BoolVarP(&logsOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+ flags.StringVar(&logsOptions.SinceRaw, "since", "", "Show logs since TIMESTAMP")
+ flags.Int64Var(&logsOptions.Tail, "tail", -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines")
+ flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log")
+ flags.BoolVarP(&logsOptions.Names, "names", "n", false, "Output the container name in the log")
+ flags.SetInterspersed(false)
+ _ = flags.MarkHidden("details")
+}
+
+func logs(cmd *cobra.Command, args []string) error {
+ if len(args) > 0 && logsOptions.Latest {
+ return errors.New("no containers can be specified when using 'latest'")
+ }
+ if !logsOptions.Latest && len(args) < 1 {
+ return errors.New("specify at least one container name or ID to log")
+ }
+ if logsOptions.SinceRaw != "" {
+ // parse time, error out if something is wrong
+ since, err := util.ParseInputTime(logsOptions.SinceRaw)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing --since %q", logsOptions.SinceRaw)
+ }
+ logsOptions.Since = since
+ }
+ logsOptions.Writer = os.Stdout
+ return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions)
+}
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index c53af0f26..3f6aca502 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -261,6 +261,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var until time.Time
if _, found := r.URL.Query()["until"]; found {
+ // FIXME: until != since but the logs backend does not yet support until.
since, err = util.ParseInputTime(query.Until)
if err != nil {
utils.BadRequest(w, "until", query.Until, err)
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 5d302058b..98f9f9471 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -172,6 +172,26 @@ type AttachOptions struct {
Stderr *os.File
}
+// ContainerLogsOptions describes the options to extract container logs.
+type ContainerLogsOptions struct {
+ // Show extra details provided to the logs.
+ Details bool
+ // Follow the log output.
+ Follow bool
+ // Display logs for the latest container only. Ignored on the remote client.
+ Latest bool
+ // Show container names in the output.
+ Names bool
+ // Show logs since this timestamp.
+ Since time.Time
+ // Number of lines to display at the end of the output.
+ Tail int64
+ // Show timestamps in the logs.
+ Timestamps bool
+ // Write the logs to Writer.
+ Writer io.Writer
+}
+
// ExecOptions describes the cli values to exec into
// a container
type ExecOptions struct {
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 24b7a9acc..fec288eb8 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -20,6 +20,7 @@ type ContainerEngine interface {
ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error)
ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error)
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
+ ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error
ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error)
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index ccbe6d4fd..1aabb2a6b 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -4,9 +4,11 @@ package abi
import (
"context"
+ "fmt"
"io/ioutil"
"strconv"
"strings"
+ "sync"
"github.com/containers/buildah"
"github.com/containers/image/v5/manifest"
@@ -14,6 +16,7 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/checkpoint"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
@@ -709,3 +712,48 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
}
return &report, nil
}
+
+func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
+ if options.Writer == nil {
+ return errors.New("no io.Writer set for container logs")
+ }
+
+ var wg sync.WaitGroup
+
+ ctrs, err := getContainersByContext(false, options.Latest, containers, ic.Libpod)
+ if err != nil {
+ return err
+ }
+
+ logOpts := &logs.LogOptions{
+ Multi: len(ctrs) > 1,
+ Details: options.Details,
+ Follow: options.Follow,
+ Since: options.Since,
+ Tail: options.Tail,
+ Timestamps: options.Timestamps,
+ UseName: options.Names,
+ WaitGroup: &wg,
+ }
+
+ chSize := len(ctrs) * int(options.Tail)
+ if chSize <= 0 {
+ chSize = 1
+ }
+ logChannel := make(chan *logs.LogLine, chSize)
+
+ if err := ic.Libpod.Log(ctrs, logOpts, logChannel); err != nil {
+ return err
+ }
+
+ go func() {
+ wg.Wait()
+ close(logChannel)
+ }()
+
+ for line := range logChannel {
+ fmt.Fprintln(options.Writer, line.String(logOpts))
+ }
+
+ return nil
+}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index b22c6e3ba..d40f0da36 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -305,6 +305,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
return &entities.ContainerCreateReport{Id: response.ID}, nil
}
+func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error {
+ // The endpoint is not ready yet and requires some more work.
+ return errors.New("not implemented yet")
+}
+
func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error {
return errors.New("not implemented")
}