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 }