Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit a4b003e

Browse files
committed
pull logs and events better than aggregate events from multiple channels
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
1 parent 17c26e8 commit a4b003e

File tree

10 files changed

+145
-111
lines changed

10 files changed

+145
-111
lines changed

api/compose/api.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,8 @@ type CreateOptions struct {
6565

6666
// StartOptions group options of the Start API
6767
type StartOptions struct {
68-
// Attach will attach to container and pipe stdout/stderr to LogConsumer
69-
Attach LogConsumer
70-
// Listener will get notified on container events
71-
Listener chan ContainerExited
68+
// Attach will attach to service containers and pipe stdout/stderr to channel
69+
Attach chan ContainerEvent
7270
}
7371

7472
// UpOptions group options of the Up API
@@ -185,11 +183,21 @@ type Stack struct {
185183
// LogConsumer is a callback to process log messages from services
186184
type LogConsumer interface {
187185
Log(service, container, message string)
188-
Status(service, container, message string)
186+
Status(service, container, msg string)
189187
}
190188

191-
// ContainerExited let us know a Container exited
192-
type ContainerExited struct {
193-
Service string
194-
Status int
189+
// ContainerEvent notify an event has been collected on Source container implementing Service
190+
type ContainerEvent struct {
191+
Type int
192+
Source string
193+
Service string
194+
Line string
195+
ExitCode int
195196
}
197+
198+
const (
199+
// ContainerEventLog is a ContainerEvent of type log. Line is set
200+
ContainerEventLog = iota
201+
// ContainerEventExit is a ContainerEvent of type exit. ExitCode is set
202+
ContainerEventExit
203+
)

cli/cmd/compose/start.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@ package compose
1818

1919
import (
2020
"context"
21-
"os"
22-
23-
"github.com/spf13/cobra"
2421

2522
"github.com/docker/compose-cli/api/client"
2623
"github.com/docker/compose-cli/api/compose"
2724
"github.com/docker/compose-cli/api/progress"
28-
"github.com/docker/compose-cli/cli/formatter"
25+
26+
"github.com/spf13/cobra"
2927
)
3028

3129
type startOptions struct {
@@ -67,7 +65,23 @@ func runStart(ctx context.Context, opts startOptions, services []string) error {
6765
return err
6866
}
6967

70-
return c.ComposeService().Start(ctx, project, compose.StartOptions{
71-
Attach: formatter.NewLogConsumer(ctx, os.Stdout),
68+
queue := make(chan compose.ContainerEvent)
69+
printer := printer{
70+
queue: queue,
71+
}
72+
err = c.ComposeService().Start(ctx, project, compose.StartOptions{
73+
Attach: queue,
74+
})
75+
if err != nil {
76+
return err
77+
}
78+
79+
_, err = printer.run(ctx, false, func() error {
80+
ctx := context.Background()
81+
_, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
82+
return "", c.ComposeService().Stop(ctx, project)
83+
})
84+
return err
7285
})
86+
return err
7387
}

cli/cmd/compose/up.go

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ package compose
1818

1919
import (
2020
"context"
21-
"errors"
2221
"fmt"
23-
"github.com/sirupsen/logrus"
2422
"os"
23+
"os/signal"
2524
"path/filepath"
25+
"syscall"
2626

2727
"github.com/docker/compose-cli/api/client"
2828
"github.com/docker/compose-cli/api/compose"
@@ -31,6 +31,7 @@ import (
3131
"github.com/docker/compose-cli/cli/formatter"
3232

3333
"github.com/compose-spec/compose-go/types"
34+
"github.com/sirupsen/logrus"
3435
"github.com/spf13/cobra"
3536
)
3637

@@ -151,47 +152,35 @@ func runCreateStart(ctx context.Context, opts upOptions, services []string) erro
151152
return nil
152153
}
153154

154-
ctx, cancel := context.WithCancel(ctx)
155-
listener := make(chan compose.ContainerExited)
156-
exitCode := make(chan int)
155+
queue := make(chan compose.ContainerEvent)
156+
printer := printer{
157+
queue: queue,
158+
}
159+
160+
stopFunc := func() error {
161+
ctx := context.Background()
162+
_, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
163+
return "", c.ComposeService().Stop(ctx, project)
164+
})
165+
return err
166+
}
167+
signalChan := make(chan os.Signal, 1)
168+
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
157169
go func() {
158-
var aborting bool
159-
for {
160-
exit := <-listener
161-
if opts.cascadeStop && !aborting {
162-
aborting = true
163-
cancel()
164-
exitCode <- exit.Status
165-
}
166-
}
170+
<-signalChan
171+
fmt.Println("Gracefully stopping...")
172+
stopFunc() // nolint:errcheck
167173
}()
168174

169175
err = c.ComposeService().Start(ctx, project, compose.StartOptions{
170-
Attach: formatter.NewLogConsumer(ctx, os.Stdout),
171-
Listener: listener,
176+
Attach: queue,
172177
})
173-
174-
if errors.Is(ctx.Err(), context.Canceled) {
175-
select {
176-
case exit := <-exitCode:
177-
fmt.Println("Aborting on container exit...")
178-
err = stop(c, project)
179-
logrus.Error(exit)
180-
// os.Exit(exit)
181-
default:
182-
// cancelled by user
183-
fmt.Println("Gracefully stopping...")
184-
err = stop(c, project)
185-
}
178+
if err != nil {
179+
return err
186180
}
187-
return err
188-
}
189181

190-
func stop(c *client.Client, project *types.Project) error {
191-
ctx := context.Background()
192-
_, err := progress.Run(ctx, func(ctx context.Context) (string, error) {
193-
return "", c.ComposeService().Stop(ctx, project)
194-
})
182+
_, err = printer.run(ctx, opts.cascadeStop, stopFunc)
183+
// FIXME os.Exit
195184
return err
196185
}
197186

@@ -235,3 +224,26 @@ func setup(ctx context.Context, opts composeOptions, services []string) (*client
235224

236225
return c, project, nil
237226
}
227+
228+
type printer struct {
229+
queue chan compose.ContainerEvent
230+
}
231+
232+
func (p printer) run(ctx context.Context, cascadeStop bool, stopFn func() error) (int, error) { //nolint:unparam
233+
consumer := formatter.NewLogConsumer(ctx, os.Stdout)
234+
for {
235+
event := <-p.queue
236+
switch event.Type {
237+
case compose.ContainerEventExit:
238+
consumer.Status(event.Service, event.Source, fmt.Sprintf("exited with code %d", event.ExitCode))
239+
if cascadeStop {
240+
fmt.Println("Aborting on container exit...")
241+
err := stopFn()
242+
logrus.Error(event.ExitCode)
243+
return event.ExitCode, err
244+
}
245+
case compose.ContainerEventLog:
246+
consumer.Log(event.Service, event.Source, event.Line)
247+
}
248+
}
249+
}

ecs/logs.go

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,43 +20,13 @@ import (
2020
"context"
2121

2222
"github.com/docker/compose-cli/api/compose"
23+
"github.com/docker/compose-cli/utils"
2324
)
2425

2526
func (b *ecsAPIService) Logs(ctx context.Context, projectName string, consumer compose.LogConsumer, options compose.LogOptions) error {
2627
if len(options.Services) > 0 {
27-
consumer = filteredLogConsumer(consumer, options.Services)
28+
consumer = utils.FilteredLogConsumer(consumer, options.Services)
2829
}
2930
err := b.aws.GetLogs(ctx, projectName, consumer.Log, options.Follow)
3031
return err
3132
}
32-
33-
func filteredLogConsumer(consumer compose.LogConsumer, services []string) compose.LogConsumer {
34-
if len(services) == 0 {
35-
return consumer
36-
}
37-
allowed := map[string]bool{}
38-
for _, s := range services {
39-
allowed[s] = true
40-
}
41-
return &allowListLogConsumer{
42-
allowList: allowed,
43-
delegate: consumer,
44-
}
45-
}
46-
47-
type allowListLogConsumer struct {
48-
allowList map[string]bool
49-
delegate compose.LogConsumer
50-
}
51-
52-
func (a *allowListLogConsumer) Log(service, container, message string) {
53-
if a.allowList[service] {
54-
a.delegate.Log(service, container, message)
55-
}
56-
}
57-
58-
func (a *allowListLogConsumer) Status(service, container, message string) {
59-
if a.allowList[service] {
60-
a.delegate.Status(service, container, message)
61-
}
62-
}

local/compose/attach.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,13 @@ import (
2424

2525
"github.com/docker/compose-cli/api/compose"
2626
convert "github.com/docker/compose-cli/local/moby"
27-
"github.com/docker/compose-cli/utils"
2827

2928
"github.com/compose-spec/compose-go/types"
3029
moby "github.com/docker/docker/api/types"
3130
"github.com/docker/docker/pkg/stdcopy"
3231
)
3332

34-
func (s *composeService) attach(ctx context.Context, project *types.Project, consumer compose.LogConsumer) (Containers, error) {
33+
func (s *composeService) attach(ctx context.Context, project *types.Project, consumer chan compose.ContainerEvent) (Containers, error) {
3534
containers, err := s.getContainers(ctx, project)
3635
if err != nil {
3736
return nil, err
@@ -52,7 +51,7 @@ func (s *composeService) attach(ctx context.Context, project *types.Project, con
5251
return containers, nil
5352
}
5453

55-
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, consumer compose.LogConsumer, project *types.Project) error {
54+
func (s *composeService) attachContainer(ctx context.Context, container moby.Container, consumer chan compose.ContainerEvent, project *types.Project) error {
5655
serviceName := container.Labels[serviceLabel]
5756
w := getWriter(serviceName, getContainerNameWithoutProject(container), consumer)
5857

local/compose/logs.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package compose
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"io"
2223

@@ -52,6 +53,7 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
5253
}
5354
eg, ctx := errgroup.WithContext(ctx)
5455
for _, c := range list {
56+
c := c
5557
service := c.Labels[serviceLabel]
5658
if ignore(service) {
5759
continue
@@ -73,7 +75,7 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
7375
if err != nil {
7476
return err
7577
}
76-
w := utils.GetWriter(service, container.Name[1:], consumer)
78+
w := utils.GetWriter(service, getContainerNameWithoutProject(c), consumer)
7779
if container.Config.Tty {
7880
_, err = io.Copy(w, r)
7981
} else {
@@ -84,3 +86,33 @@ func (s *composeService) Logs(ctx context.Context, projectName string, consumer
8486
}
8587
return eg.Wait()
8688
}
89+
90+
type splitBuffer struct {
91+
service string
92+
container string
93+
consumer chan compose.ContainerEvent
94+
}
95+
96+
// getWriter creates a io.Writer that will actually split by line and format by LogConsumer
97+
func getWriter(service, container string, events chan compose.ContainerEvent) io.Writer {
98+
return splitBuffer{
99+
service: service,
100+
container: container,
101+
consumer: events,
102+
}
103+
}
104+
105+
func (s splitBuffer) Write(b []byte) (n int, err error) {
106+
split := bytes.Split(b, []byte{'\n'})
107+
for _, line := range split {
108+
if len(line) != 0 {
109+
s.consumer <- compose.ContainerEvent{
110+
Type: compose.ContainerEventLog,
111+
Service: s.service,
112+
Source: s.container,
113+
Line: string(line),
114+
}
115+
}
116+
}
117+
return len(b), nil
118+
}

local/compose/start.go

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ package compose
1818

1919
import (
2020
"context"
21-
"fmt"
2221

2322
"github.com/docker/compose-cli/api/compose"
2423

2524
"github.com/compose-spec/compose-go/types"
2625
"github.com/docker/docker/api/types/container"
27-
"golang.org/x/sync/errgroup"
26+
"github.com/sirupsen/logrus"
2827
)
2928

3029
func (s *composeService) Start(ctx context.Context, project *types.Project, options compose.StartOptions) error {
@@ -35,12 +34,6 @@ func (s *composeService) Start(ctx context.Context, project *types.Project, opti
3534
return err
3635
}
3736
containers = c
38-
} else {
39-
c, err := s.getContainers(ctx, project)
40-
if err != nil {
41-
return err
42-
}
43-
containers = c
4437
}
4538

4639
err := InDependencyOrder(ctx, project, func(c context.Context, service types.ServiceConfig) error {
@@ -54,26 +47,21 @@ func (s *composeService) Start(ctx context.Context, project *types.Project, opti
5447
return nil
5548
}
5649

57-
eg, ctx := errgroup.WithContext(ctx)
5850
for _, c := range containers {
5951
c := c
60-
eg.Go(func() error {
61-
statusC, errC := s.apiClient.ContainerWait(ctx, c.ID, container.WaitConditionNotRunning)
52+
go func() {
53+
statusC, errC := s.apiClient.ContainerWait(context.Background(), c.ID, container.WaitConditionNotRunning)
6254
select {
6355
case status := <-statusC:
64-
service := c.Labels[serviceLabel]
65-
options.Attach.Status(service, getCanonicalContainerName(c), fmt.Sprintf("exited with code %d", status.StatusCode))
66-
if options.Listener != nil {
67-
options.Listener <- compose.ContainerExited{
68-
Service: service,
69-
Status: int(status.StatusCode),
70-
}
56+
options.Attach <- compose.ContainerEvent{
57+
Type: compose.ContainerEventExit,
58+
Source: getCanonicalContainerName(c),
59+
ExitCode: int(status.StatusCode),
7160
}
72-
return nil
7361
case err := <-errC:
74-
return err
62+
logrus.Warnf("Unexpected API error for %s : %s\n", getCanonicalContainerName(c), err.Error())
7563
}
76-
})
64+
}()
7765
}
78-
return eg.Wait()
66+
return nil
7967
}

0 commit comments

Comments
 (0)