浏览代码

Merge pull request #1178 from docker/tail_logs

introduce compose logs --tail and --follow options
Guillaume Tardif 4 年之前
父节点
当前提交
fd906c6c91
共有 11 个文件被更改,包括 39 次插入45 次删除
  1. 2 0
      api/compose/api.go
  2. 1 1
      cli/cmd/compose/compose.go
  3. 10 1
      cli/cmd/compose/logs.go
  4. 1 1
      ecs/aws.go
  5. 4 4
      ecs/aws_mock.go
  6. 1 1
      ecs/logs.go
  7. 4 1
      ecs/sdk.go
  8. 1 6
      local/compose/create.go
  9. 10 15
      local/compose/down.go
  10. 2 1
      local/compose/logs.go
  11. 3 14
      local/compose/stop.go

+ 2 - 0
api/compose/api.go

@@ -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 (

+ 1 - 1
cli/cmd/compose/compose.go

@@ -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),
 	)
 	)

+ 10 - 1
cli/cmd/compose/logs.go

@@ -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,
 	})
 	})
 }
 }

+ 1 - 1
ecs/aws.go

@@ -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)

+ 4 - 4
ecs/aws_mock.go

@@ -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

+ 1 - 1
ecs/logs.go

@@ -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
 }
 }
 
 

+ 4 - 1
ecs/sdk.go

@@ -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)
 	}
 	}
 }
 }

+ 1 - 6
local/compose/create.go

@@ -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 "+

+ 10 - 15
local/compose/down.go

@@ -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 {
-		err := s.removeContainers(ctx, w, eg, containers)
+	if options.RemoveOrphans && len(containers) > 0 {
+		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))

+ 2 - 1
local/compose/logs.go

@@ -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
 
 

+ 3 - 14
local/compose/stop.go

@@ -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 {
-		serviceContainers, others := containers.split(isService(service.Name))
-		err := s.stopContainers(ctx, w, serviceContainers)
-		containers = others
-		return err
+	return InReverseDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
+		return s.stopContainers(ctx, w, containers.filter(isService(service.Name)))
 	})
 	})
-	if err != nil {
-		return err
-	}
-
-	return eg.Wait()
 }
 }