github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/internal/commands/logs.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
    13  	"github.com/aws/aws-sdk-go/service/ecs"
    14  	"github.com/hazelops/ize/internal/config"
    15  	"github.com/sirupsen/logrus"
    16  	"github.com/spf13/cobra"
    17  )
    18  
    19  type LogsOptions struct {
    20  	Config     *config.Project
    21  	AppName    string
    22  	EcsCluster string
    23  	Task       string
    24  }
    25  
    26  func NewLogsFlags(project *config.Project) *LogsOptions {
    27  	return &LogsOptions{
    28  		Config: project,
    29  	}
    30  }
    31  
    32  func NewCmdLogs(project *config.Project) *cobra.Command {
    33  	o := NewLogsFlags(project)
    34  
    35  	cmd := &cobra.Command{
    36  		Use:               "logs [app-name]",
    37  		Short:             "Stream logs of container in the ECS",
    38  		Args:              cobra.MinimumNArgs(1),
    39  		ValidArgsFunction: config.GetApps,
    40  		RunE: func(cmd *cobra.Command, args []string) error {
    41  			cmd.SilenceUsage = true
    42  			err := o.Complete(cmd)
    43  			if err != nil {
    44  				return err
    45  			}
    46  
    47  			err = o.Validate()
    48  			if err != nil {
    49  				return err
    50  			}
    51  
    52  			err = o.Run()
    53  			if err != nil {
    54  				return err
    55  			}
    56  
    57  			return nil
    58  		},
    59  	}
    60  
    61  	cmd.Flags().StringVar(&o.EcsCluster, "ecs-cluster", "", "set ECS cluster name")
    62  	cmd.Flags().StringVar(&o.Task, "task", "", "set ECS task id")
    63  
    64  	return cmd
    65  }
    66  
    67  func (o *LogsOptions) Complete(cmd *cobra.Command) error {
    68  	if o.EcsCluster == "" {
    69  		o.EcsCluster = fmt.Sprintf("%s-%s", o.Config.Env, o.Config.Namespace)
    70  	}
    71  
    72  	o.AppName = cmd.Flags().Args()[0]
    73  
    74  	return nil
    75  }
    76  
    77  func (o *LogsOptions) Validate() error {
    78  	if len(o.AppName) == 0 {
    79  		return fmt.Errorf("can't validate: app name must be specified\n")
    80  	}
    81  	return nil
    82  }
    83  
    84  func (o *LogsOptions) Run() error {
    85  	logGroup := fmt.Sprintf("%s-%s", o.Config.Env, o.AppName)
    86  
    87  	taskID := o.Task
    88  	if len(taskID) == 0 {
    89  		lto, err := o.Config.AWSClient.ECSClient.ListTasks(&ecs.ListTasksInput{
    90  			Cluster:       &o.EcsCluster,
    91  			DesiredStatus: aws.String("RUNNING"),
    92  			ServiceName:   &logGroup,
    93  			MaxResults:    aws.Int64(1),
    94  		})
    95  
    96  		logrus.Infof("log group: %s, cluster name: %s", logGroup, o.EcsCluster)
    97  
    98  		if err != nil {
    99  			return fmt.Errorf("can't run logs: %w", err)
   100  		}
   101  
   102  		taskID = *lto.TaskArns[0]
   103  		taskID = taskID[strings.LastIndex(taskID, "/")+1:]
   104  	}
   105  
   106  	var token *string
   107  	logStreamName := fmt.Sprintf("main/%s/%s", o.AppName, taskID)
   108  
   109  	GetLogs(o.Config.AWSClient.CloudWatchLogsClient, logGroup, logStreamName, token)
   110  
   111  	return nil
   112  }
   113  
   114  func GetLogs(clw cloudwatchlogsiface.CloudWatchLogsAPI, logGroup string, logStreamName string, token *string) {
   115  	for {
   116  		logEvents, err := clw.GetLogEvents(&cloudwatchlogs.GetLogEventsInput{
   117  			LogGroupName:  &logGroup,
   118  			LogStreamName: &logStreamName,
   119  			NextToken:     token,
   120  		})
   121  		if err != nil {
   122  			_, _ = fmt.Fprintf(os.Stderr, "%s: %v\n", logStreamName, err)
   123  			continue
   124  		}
   125  
   126  		for _, e := range logEvents.Events {
   127  			_, m := formatMessage(e)
   128  			fmt.Println(m)
   129  		}
   130  
   131  		token = logEvents.NextForwardToken
   132  
   133  		time.Sleep(time.Second * 5)
   134  	}
   135  }
   136  
   137  func formatMessage(e *cloudwatchlogs.OutputLogEvent) (t time.Time, m string) {
   138  	m = *e.Message
   139  
   140  	if len(m) > 16 {
   141  		if _, err := time.Parse("Jan  2 15:04:05 ", m[:16]); err == nil {
   142  			m = m[16:]
   143  		}
   144  	}
   145  
   146  	t = time.Unix(0, *e.Timestamp*1000000)
   147  	m = t.Format("2006-01-02 15:04:05 ") + m
   148  	return
   149  }