Merge pull request #1178 from docker/tail_logs

introduce compose logs --tail and --follow options
This commit is contained in:
Guillaume Tardif 2021-01-29 15:54:38 +01:00 committed by GitHub
commit fd906c6c91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 39 additions and 45 deletions

View File

@ -122,6 +122,8 @@ type ServiceStatus struct {
// LogOptions defines optional parameters for the `Log` API // LogOptions defines optional parameters for the `Log` API
type LogOptions struct { type LogOptions struct {
Services []string Services []string
Tail string
Follow bool
} }
const ( const (

View File

@ -97,7 +97,7 @@ func Command(contextType string) *cobra.Command {
stopCommand(&opts), stopCommand(&opts),
psCommand(&opts), psCommand(&opts),
listCommand(), listCommand(),
logsCommand(&opts), logsCommand(&opts, contextType),
convertCommand(&opts), convertCommand(&opts),
runCommand(&opts), runCommand(&opts),
) )

View File

@ -24,15 +24,18 @@ import (
"github.com/docker/compose-cli/api/client" "github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/cli/formatter" "github.com/docker/compose-cli/cli/formatter"
) )
type logsOptions struct { type logsOptions struct {
*projectOptions *projectOptions
composeOptions composeOptions
follow bool
tail string
} }
func logsCommand(p *projectOptions) *cobra.Command { func logsCommand(p *projectOptions, contextType string) *cobra.Command {
opts := logsOptions{ opts := logsOptions{
projectOptions: p, projectOptions: p,
} }
@ -43,6 +46,10 @@ func logsCommand(p *projectOptions) *cobra.Command {
return runLogs(cmd.Context(), opts, args) return runLogs(cmd.Context(), opts, args)
}, },
} }
logsCmd.Flags().BoolVar(&opts.follow, "follow", false, "Follow log output.")
if contextType == store.DefaultContextType {
logsCmd.Flags().StringVar(&opts.tail, "tail", "all", "Number of lines to show from the end of the logs for each container.")
}
return logsCmd return logsCmd
} }
@ -59,5 +66,7 @@ func runLogs(ctx context.Context, opts logsOptions, services []string) error {
consumer := formatter.NewLogConsumer(ctx, os.Stdout) consumer := formatter.NewLogConsumer(ctx, os.Stdout)
return c.ComposeService().Logs(ctx, projectName, consumer, compose.LogOptions{ return c.ComposeService().Logs(ctx, projectName, consumer, compose.LogOptions{
Services: services, Services: services,
Follow: opts.follow,
Tail: opts.tail,
}) })
} }

View File

@ -63,7 +63,7 @@ type API interface {
InspectSecret(ctx context.Context, id string) (secrets.Secret, error) InspectSecret(ctx context.Context, id string) (secrets.Secret, error)
ListSecrets(ctx context.Context) ([]secrets.Secret, error) ListSecrets(ctx context.Context) ([]secrets.Secret, error)
DeleteSecret(ctx context.Context, id string, recover bool) error DeleteSecret(ctx context.Context, id string, recover bool) error
GetLogs(ctx context.Context, name string, consumer func(service, container, message string)) error GetLogs(ctx context.Context, name string, consumer func(service string, container string, message string), follow bool) error
DescribeService(ctx context.Context, cluster string, arn string) (compose.ServiceStatus, error) DescribeService(ctx context.Context, cluster string, arn string) (compose.ServiceStatus, error)
DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]compose.ContainerSummary, error) DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]compose.ContainerSummary, error)
getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]compose.PortPublisher, error) getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]compose.PortPublisher, error)

View File

@ -285,17 +285,17 @@ func (mr *MockAPIMockRecorder) GetLoadBalancerURL(arg0, arg1 interface{}) *gomoc
} }
// GetLogs mocks base method // GetLogs mocks base method
func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string, arg2 func(string, string, string)) error { func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string, arg2 func(string, string, string), arg3 bool) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLogs", arg0, arg1, arg2) ret := m.ctrl.Call(m, "GetLogs", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error) ret0, _ := ret[0].(error)
return ret0 return ret0
} }
// GetLogs indicates an expected call of GetLogs // GetLogs indicates an expected call of GetLogs
func (mr *MockAPIMockRecorder) GetLogs(arg0, arg1, arg2 interface{}) *gomock.Call { func (mr *MockAPIMockRecorder) GetLogs(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockAPI)(nil).GetLogs), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockAPI)(nil).GetLogs), arg0, arg1, arg2, arg3)
} }
// GetParameter mocks base method // GetParameter mocks base method

View File

@ -26,7 +26,7 @@ func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer c
if len(options.Services) > 0 { if len(options.Services) > 0 {
consumer = filteredLogConsumer(consumer, options.Services) consumer = filteredLogConsumer(consumer, options.Services)
} }
err := b.aws.GetLogs(ctx, projectName, consumer.Log) err := b.aws.GetLogs(ctx, projectName, consumer.Log, options.Follow)
return err return err
} }

View File

@ -805,7 +805,7 @@ func (s sdk) DeleteSecret(ctx context.Context, id string, recover bool) error {
return err return err
} }
func (s sdk) GetLogs(ctx context.Context, name string, consumer func(service, container, message string)) error { func (s sdk) GetLogs(ctx context.Context, name string, consumer func(service string, container string, message string), follow bool) error {
logGroup := fmt.Sprintf("/docker-compose/%s", name) logGroup := fmt.Sprintf("/docker-compose/%s", name)
var startTime int64 var startTime int64
for { for {
@ -837,6 +837,9 @@ func (s sdk) GetLogs(ctx context.Context, name string, consumer func(service, co
} }
} }
} }
if !follow {
return nil
}
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
} }

View File

@ -35,7 +35,6 @@ import (
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/progress" "github.com/docker/compose-cli/api/progress"
@ -77,15 +76,11 @@ func (s *composeService) Create(ctx context.Context, project *types.Project, opt
orphans := observedState.filter(isNotService(project.ServiceNames()...)) orphans := observedState.filter(isNotService(project.ServiceNames()...))
if len(orphans) > 0 { if len(orphans) > 0 {
if opts.RemoveOrphans { if opts.RemoveOrphans {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
err := s.removeContainers(ctx, w, eg, orphans) err := s.removeContainers(ctx, w, orphans)
if err != nil { if err != nil {
return err return err
} }
if eg.Wait() != nil {
return err
}
} else { } else {
logrus.Warnf("Found orphan containers (%s) for this project. If "+ logrus.Warnf("Found orphan containers (%s) for this project. If "+
"you removed or renamed this service in your compose "+ "you removed or renamed this service in your compose "+

View File

@ -33,7 +33,6 @@ import (
) )
func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error { func (s *composeService) Down(ctx context.Context, projectName string, options compose.DownOptions) error {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
if options.Project == nil { if options.Project == nil {
@ -55,25 +54,21 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service types.ServiceConfig) error { err = InReverseDependencyOrder(ctx, options.Project, func(c context.Context, service types.ServiceConfig) error {
serviceContainers, others := containers.split(isService(service.Name)) serviceContainers, others := containers.split(isService(service.Name))
err := s.removeContainers(ctx, w, eg, serviceContainers) err := s.removeContainers(ctx, w, serviceContainers)
containers = others containers = others
return err return err
}) })
if err != nil {
return err
}
if options.RemoveOrphans { if options.RemoveOrphans && len(containers) > 0 {
err := s.removeContainers(ctx, w, eg, containers) err := s.removeContainers(ctx, w, containers)
if err != nil { if err != nil {
return err return err
} }
} }
if err != nil {
return err
}
err = eg.Wait()
if err != nil {
return err
}
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{ networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{
Filters: filters.NewArgs( Filters: filters.NewArgs(
projectFilter(projectName), projectFilter(projectName),
@ -82,6 +77,8 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
if err != nil { if err != nil {
return err return err
} }
eg, _ := errgroup.WithContext(ctx)
for _, n := range networks { for _, n := range networks {
networkID := n.ID networkID := n.ID
networkName := n.Name networkName := n.Name
@ -89,7 +86,6 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
return s.ensureNetworkDown(ctx, networkID, networkName) return s.ensureNetworkDown(ctx, networkID, networkName)
}) })
} }
return eg.Wait() return eg.Wait()
} }
@ -108,15 +104,14 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
return nil return nil
} }
func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, eg *errgroup.Group, containers []moby.Container) error { func (s *composeService) removeContainers(ctx context.Context, w progress.Writer, containers []moby.Container) error {
eg, _ := errgroup.WithContext(ctx)
for _, container := range containers { for _, container := range containers {
toDelete := container toDelete := container
eg.Go(func() error { eg.Go(func() error {
eventName := "Container " + getCanonicalContainerName(toDelete) eventName := "Container " + getCanonicalContainerName(toDelete)
w.Event(progress.StoppingEvent(eventName))
err := s.stopContainers(ctx, w, []moby.Container{container}) err := s.stopContainers(ctx, w, []moby.Container{container})
if err != nil { if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
return err return err
} }
w.Event(progress.RemovingEvent(eventName)) w.Event(progress.RemovingEvent(eventName))

View File

@ -64,7 +64,8 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
r, err := s.apiClient.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{ r, err := s.apiClient.ContainerLogs(ctx, container.ID, types.ContainerLogsOptions{
ShowStdout: true, ShowStdout: true,
ShowStderr: true, ShowStderr: true,
Follow: true, Follow: options.Follow,
Tail: options.Tail,
}) })
defer r.Close() // nolint errcheck defer r.Close() // nolint errcheck

View File

@ -19,17 +19,14 @@ package compose
import ( import (
"context" "context"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types" moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/compose-cli/api/progress" "github.com/docker/compose-cli/api/progress"
"github.com/compose-spec/compose-go/types"
"golang.org/x/sync/errgroup"
) )
func (s *composeService) Stop(ctx context.Context, project *types.Project) error { func (s *composeService) Stop(ctx context.Context, project *types.Project) error {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx) w := progress.ContextWriter(ctx)
var containers Containers var containers Containers
@ -41,15 +38,7 @@ func (s *composeService) Stop(ctx context.Context, project *types.Project) error
return err return err
} }
err = InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error { return InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
serviceContainers, others := containers.split(isService(service.Name)) return s.stopContainers(ctx, w, containers.filter(isService(service.Name)))
err := s.stopContainers(ctx, w, serviceContainers)
containers = others
return err
}) })
if err != nil {
return err
}
return eg.Wait()
} }