mirror of
https://github.com/containers/podman.git
synced 2025-08-06 03:19:52 +08:00
Merge pull request #5760 from vrothberg/v2-logs
podmanV2: implement logs
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
|
||||
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)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
Reference in New Issue
Block a user