github.com/defang-io/defang/src@v0.0.0-20240505002154-bdf411911834/pkg/clouds/aws/ecs/tail.go (about)

     1  package ecs
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"path"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
    11  	"github.com/defang-io/defang/src/pkg"
    12  	"github.com/defang-io/defang/src/pkg/clouds/aws/region"
    13  )
    14  
    15  const AwsLogsStreamPrefix = ProjectName
    16  
    17  func (a *AwsEcs) Tail(ctx context.Context, taskArn TaskArn) error {
    18  	taskId := GetTaskID(taskArn)
    19  	a.Region = region.FromArn(*taskArn)
    20  	es, err := a.TailTaskID(ctx, taskId)
    21  	if err != nil {
    22  		return err
    23  	}
    24  
    25  	ctx, cancel := context.WithCancel(ctx)
    26  	defer cancel()
    27  	taskch := make(chan error)
    28  	defer close(taskch)
    29  	go func() {
    30  		taskch <- WaitForTask(ctx, taskArn, time.Second*3)
    31  	}()
    32  
    33  	for {
    34  		select {
    35  		case e := <-es.Events(): // blocking
    36  			events, err := GetLogEvents(e)
    37  			// Print before checking for errors, so we don't lose any logs in case of EOF
    38  			for _, event := range events {
    39  				fmt.Println(*event.Message)
    40  			}
    41  			if err != nil {
    42  				return err
    43  			}
    44  		case <-ctx.Done():
    45  			return ctx.Err()
    46  		case err := <-taskch:
    47  			return err
    48  		}
    49  	}
    50  }
    51  
    52  func (a *AwsEcs) GetTaskArn(taskID string) (TaskArn, error) {
    53  	if taskID == "" {
    54  		return nil, errors.New("taskID is empty")
    55  	}
    56  	taskArn := a.MakeARN("ecs", "task/"+a.ClusterName+"/"+taskID)
    57  	return &taskArn, nil
    58  }
    59  
    60  func (a *AwsEcs) TailTaskID(ctx context.Context, taskID string) (EventStream, error) {
    61  	if taskID == "" {
    62  		return nil, errors.New("taskID is empty")
    63  	}
    64  	lgi := LogGroupInput{LogGroupARN: a.LogGroupARN, LogStreamNames: []string{GetLogStreamForTaskID(taskID)}}
    65  	for {
    66  		stream, err := TailLogGroup(ctx, lgi)
    67  		if err != nil {
    68  			var resourceNotFound *types.ResourceNotFoundException
    69  			if !errors.As(err, &resourceNotFound) {
    70  				return nil, err
    71  			}
    72  			// The log stream doesn't exist yet, so wait for it to be created, but bail out if the task is stopped
    73  			err := getTaskStatus(ctx, a.Region, a.ClusterName, taskID)
    74  			if err != nil {
    75  				return nil, err
    76  			}
    77  			// continue loop, waiting for the log stream to be created; sleep to avoid throttling
    78  			pkg.SleepWithContext(ctx, time.Second)
    79  			continue
    80  		}
    81  		// TODO: should wrap this stream so we can return io.EOF on task stop
    82  		return stream, nil
    83  	}
    84  }
    85  
    86  func GetLogStreamForTaskID(taskID string) string {
    87  	return path.Join(AwsLogsStreamPrefix, ContainerName, taskID) // per "awslogs" driver
    88  }
    89  
    90  func GetTaskID(taskArn TaskArn) string {
    91  	return path.Base(*taskArn)
    92  }