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
type LogOptions struct {
Services []string
Tail string
Follow bool
}
const (

View File

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

View File

@ -24,15 +24,18 @@ import (
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/context/store"
"github.com/docker/compose-cli/cli/formatter"
)
type logsOptions struct {
*projectOptions
composeOptions
follow bool
tail string
}
func logsCommand(p *projectOptions) *cobra.Command {
func logsCommand(p *projectOptions, contextType string) *cobra.Command {
opts := logsOptions{
projectOptions: p,
}
@ -43,6 +46,10 @@ func logsCommand(p *projectOptions) *cobra.Command {
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
}
@ -59,5 +66,7 @@ func runLogs(ctx context.Context, opts logsOptions, services []string) error {
consumer := formatter.NewLogConsumer(ctx, os.Stdout)
return c.ComposeService().Logs(ctx, projectName, consumer, compose.LogOptions{
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)
ListSecrets(ctx context.Context) ([]secrets.Secret, 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)
DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]compose.ContainerSummary, 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
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()
ret := m.ctrl.Call(m, "GetLogs", arg0, arg1, arg2)
ret := m.ctrl.Call(m, "GetLogs", arg0, arg1, arg2, arg3)
ret0, _ := ret[0].(error)
return ret0
}
// 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()
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

View File

@ -26,7 +26,7 @@ func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer c
if len(options.Services) > 0 {
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
}

View File

@ -805,7 +805,7 @@ func (s sdk) DeleteSecret(ctx context.Context, id string, recover bool) error {
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)
var startTime int64
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)
}
}

View File

@ -35,7 +35,6 @@ import (
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"github.com/docker/compose-cli/api/compose"
"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()...))
if len(orphans) > 0 {
if opts.RemoveOrphans {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx)
err := s.removeContainers(ctx, w, eg, orphans)
err := s.removeContainers(ctx, w, orphans)
if err != nil {
return err
}
if eg.Wait() != nil {
return err
}
} else {
logrus.Warnf("Found orphan containers (%s) for this project. If "+
"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 {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx)
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 {
serviceContainers, others := containers.split(isService(service.Name))
err := s.removeContainers(ctx, w, eg, serviceContainers)
err := s.removeContainers(ctx, w, serviceContainers)
containers = others
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 {
return err
}
}
if err != nil {
return err
}
err = eg.Wait()
if err != nil {
return err
}
networks, err := s.apiClient.NetworkList(ctx, moby.NetworkListOptions{
Filters: filters.NewArgs(
projectFilter(projectName),
@ -82,6 +77,8 @@ func (s *composeService) Down(ctx context.Context, projectName string, options c
if err != nil {
return err
}
eg, _ := errgroup.WithContext(ctx)
for _, n := range networks {
networkID := n.ID
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 eg.Wait()
}
@ -108,15 +104,14 @@ func (s *composeService) stopContainers(ctx context.Context, w progress.Writer,
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 {
toDelete := container
eg.Go(func() error {
eventName := "Container " + getCanonicalContainerName(toDelete)
w.Event(progress.StoppingEvent(eventName))
err := s.stopContainers(ctx, w, []moby.Container{container})
if err != nil {
w.Event(progress.ErrorMessageEvent(eventName, "Error while Stopping"))
return err
}
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{
ShowStdout: true,
ShowStderr: true,
Follow: true,
Follow: options.Follow,
Tail: options.Tail,
})
defer r.Close() // nolint errcheck

View File

@ -19,17 +19,14 @@ package compose
import (
"context"
"github.com/compose-spec/compose-go/types"
moby "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"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 {
eg, _ := errgroup.WithContext(ctx)
w := progress.ContextWriter(ctx)
var containers Containers
@ -41,15 +38,7 @@ func (s *composeService) Stop(ctx context.Context, project *types.Project) error
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()
}