mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
podmanV2: implement logs
Implement the `podman {container} logs` for the v2 client. The remote client does not yet support it. There's some more work needed for the rest api; some options are missing (e.g., printing names) while others are broken (e.g., the until http parameter). The remote parts will be tackled in a future change. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
108
cmd/podmanV2/containers/logs.go
Normal file
108
cmd/podmanV2/containers/logs.go
Normal file
@ -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)
|
||||||
|
}
|
@ -261,6 +261,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
var until time.Time
|
var until time.Time
|
||||||
if _, found := r.URL.Query()["until"]; found {
|
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)
|
since, err = util.ParseInputTime(query.Until)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.BadRequest(w, "until", query.Until, err)
|
utils.BadRequest(w, "until", query.Until, err)
|
||||||
|
@ -172,6 +172,26 @@ type AttachOptions struct {
|
|||||||
Stderr *os.File
|
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
|
// ExecOptions describes the cli values to exec into
|
||||||
// a container
|
// a container
|
||||||
type ExecOptions struct {
|
type ExecOptions struct {
|
||||||
|
@ -20,6 +20,7 @@ type ContainerEngine interface {
|
|||||||
ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error)
|
ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error)
|
||||||
ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error)
|
ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error)
|
||||||
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, 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)
|
ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error)
|
||||||
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
|
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
|
||||||
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
|
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
|
||||||
|
@ -4,9 +4,11 @@ package abi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/containers/buildah"
|
"github.com/containers/buildah"
|
||||||
"github.com/containers/image/v5/manifest"
|
"github.com/containers/image/v5/manifest"
|
||||||
@ -14,6 +16,7 @@ import (
|
|||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/libpod/events"
|
"github.com/containers/libpod/libpod/events"
|
||||||
"github.com/containers/libpod/libpod/image"
|
"github.com/containers/libpod/libpod/image"
|
||||||
|
"github.com/containers/libpod/libpod/logs"
|
||||||
"github.com/containers/libpod/pkg/checkpoint"
|
"github.com/containers/libpod/pkg/checkpoint"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
|
"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
|
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
|
||||||
|
}
|
||||||
|
@ -305,6 +305,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
|
|||||||
return &entities.ContainerCreateReport{Id: response.ID}, nil
|
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 {
|
func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user