Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show logs in service and task #99

Merged
merged 2 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ brew install keidarcy/tap/e1s
- [x] CPUUtilization
- [x] MemoryUtilization
- [x] Show autoscaling target and policy
- [x] Show log events(only support log driver: awslogs)
- [x] Open selected resource in browser(support new UI(v2))
- [x] SSH into container
- [x] Edit service
Expand Down Expand Up @@ -123,6 +124,7 @@ $ e1s -version
| `a` | Show service auto scaling |
| `m` | Show service metrics(CPUUtilization/MemoryUtilization) |
| `r` | Reload resources |
| `o` | Show log events |
| `v` | List task definition revisions |
| `f` | Toggle full screen |
| `e` | Edit resource |
Expand Down
27 changes: 0 additions & 27 deletions api/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,12 @@ package api

import (
"context"
"os"
"sort"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/applicationautoscaling"
"github.com/aws/aws-sdk-go-v2/service/cloudwatch"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
"github.com/keidarcy/e1s/util"
)

var logger = util.Logger

type Store struct {
*aws.Config
ecs *ecs.Client
cloudWatch *cloudwatch.Client
autoScaling *applicationautoscaling.Client
}

func NewStore() *Store {
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(os.Getenv("AWS_REGION")))
if err != nil {
logger.Printf("e1s - aws unable to load SDK config, error: %v\n", err)
}
ecsClient := ecs.NewFromConfig(cfg)
return &Store{
Config: &cfg,
ecs: ecsClient,
}
}

// Equivalent to
// aws ecs list-clusters
// aws ecs describe-clusters --clusters ${clusters}
Expand Down
73 changes: 73 additions & 0 deletions api/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package api

import (
"context"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
cloudwatchlogsTypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
"github.com/aws/aws-sdk-go-v2/service/ecs/types"
)

// Equivalent to
// latest_log_stream=$(aws logs describe-log-streams \
// --log-group-name "$log_group" \
// --limit 1 \
// --order-by "LastEventTime" \
// --descending \
// --query "logStreams[0].logStreamName" \
// --output text)

// # Get the latest 10 log events from the log stream
//
// aws logs get-log-events \
// --log-group-name "$log_group" \
// --log-stream-name "$latest_log_stream" \
// --limit 10 \
// --query "events[*].[timestamp,message]" \
// --output table
func (store *Store) GetLogs(tdArn *string) ([]cloudwatchlogsTypes.OutputLogEvent, error) {
store.getCloudwatchlogsClient()
td, err := store.DescribeTaskDefinition(tdArn)

if err != nil {
logger.Printf("e1s - aws failed to describe task definition, err: %v\n", err)
return nil, err
}

logs := []cloudwatchlogsTypes.OutputLogEvent{}
for _, c := range td.ContainerDefinitions {
if c.LogConfiguration.LogDriver != types.LogDriverAwslogs {
continue
}
groupName := c.LogConfiguration.Options["awslogs-group"]
if groupName == "" {
continue
}
describeLogStreamsInput := &cloudwatchlogs.DescribeLogStreamsInput{
LogGroupName: &groupName,
Limit: aws.Int32(1),
OrderBy: cloudwatchlogsTypes.OrderByLastEventTime,
Descending: aws.Bool(true),
}
describeLogStreamsOutput, err := store.cloudwatchlogs.DescribeLogStreams(context.Background(), describeLogStreamsInput)
if err != nil {
logger.Printf("e1s - aws failed to describe log stream, err: %v\n", err)
continue
}
streamName := describeLogStreamsOutput.LogStreams[0].LogStreamName

getLogEventsInput := &cloudwatchlogs.GetLogEventsInput{
LogGroupName: &groupName,
LogStreamName: streamName,
Limit: aws.Int32(100),
}
getLogEventsOutput, err := store.cloudwatchlogs.GetLogEvents(context.Background(), getLogEventsInput)
if err != nil {
logger.Printf("e1s - aws failed to get log events, err: %v\n", err)
continue
}
logs = append(logs, getLogEventsOutput.Events...)
}
return logs, nil
}
12 changes: 3 additions & 9 deletions api/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (store *Store) GetMetrics(cluster, service *string) (*MetricsData, error) {
func (store *Store) getCPU(cluster, service *string) ([]types.Datapoint, error) {
statisticsInput := store.getStatisticsInput(cluster, service)
statisticsInput.MetricName = aws.String(CPU)
metricOutput, err := store.cloudWatch.GetMetricStatistics(context.TODO(), statisticsInput)
metricOutput, err := store.cloudwatch.GetMetricStatistics(context.TODO(), statisticsInput)

if err != nil {
logger.Printf("e1s - aws failed to %s, cluster: \"%s\", service: \"%s\", err: %v\n", CPU, *cluster, *service, err)
Expand All @@ -79,7 +79,7 @@ func (store *Store) getCPU(cluster, service *string) ([]types.Datapoint, error)
func (store *Store) getMemory(cluster, service *string) ([]types.Datapoint, error) {
statisticsInput := store.getStatisticsInput(cluster, service)
statisticsInput.MetricName = aws.String(Memory)
metricOutput, err := store.cloudWatch.GetMetricStatistics(context.TODO(), statisticsInput)
metricOutput, err := store.cloudwatch.GetMetricStatistics(context.TODO(), statisticsInput)

if err != nil {
logger.Printf("e1s - aws failed to %s, cluster: \"%s\", service: \"%s\", err: %v\n", Memory, *cluster, *service, err)
Expand All @@ -90,7 +90,7 @@ func (store *Store) getMemory(cluster, service *string) ([]types.Datapoint, erro
}

func (store *Store) getStatisticsInput(cluster, service *string) *cloudwatch.GetMetricStatisticsInput {
store.getCloudWatchClient()
store.getCloudwatchClient()

// period := 30
// granularity := 1800
Expand Down Expand Up @@ -118,9 +118,3 @@ func (store *Store) getStatisticsInput(cluster, service *string) *cloudwatch.Get
Dimensions: dimensions,
}
}

func (store *Store) getCloudWatchClient() {
if store.cloudWatch == nil {
store.cloudWatch = cloudwatch.NewFromConfig(*store.Config)
}
}
48 changes: 48 additions & 0 deletions api/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package api

import (
"context"
"os"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/applicationautoscaling"
"github.com/aws/aws-sdk-go-v2/service/cloudwatch"
"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go-v2/service/ecs"
"github.com/keidarcy/e1s/util"
)

var logger = util.Logger

type Store struct {
*aws.Config
ecs *ecs.Client
cloudwatch *cloudwatch.Client
cloudwatchlogs *cloudwatchlogs.Client
autoScaling *applicationautoscaling.Client
}

func NewStore() *Store {
cfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(os.Getenv("AWS_REGION")))
if err != nil {
logger.Printf("e1s - aws unable to load SDK config, error: %v\n", err)
}
ecsClient := ecs.NewFromConfig(cfg)
return &Store{
Config: &cfg,
ecs: ecsClient,
}
}

func (store *Store) getCloudwatchClient() {
if store.cloudwatch == nil {
store.cloudwatch = cloudwatch.NewFromConfig(*store.Config)
}
}

func (store *Store) getCloudwatchlogsClient() {
if store.cloudwatchlogs == nil {
store.cloudwatchlogs = cloudwatchlogs.NewFromConfig(*store.Config)
}
}
4 changes: 2 additions & 2 deletions api/task_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ const (

// Equivalent to
// aws ecs describe-task-definition --task-definition ${taskDefinition}
func (store *Store) DescribeTaskDefinition(name *string) (types.TaskDefinition, error) {
func (store *Store) DescribeTaskDefinition(tdArn *string) (types.TaskDefinition, error) {

include := []types.TaskDefinitionField{
types.TaskDefinitionFieldTags,
}
taskDefinition, err := store.ecs.DescribeTaskDefinition(context.Background(), &ecs.DescribeTaskDefinitionInput{
TaskDefinition: name,
TaskDefinition: tdArn,
Include: include,
})
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ require (
)

require (
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.9 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.2 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/aws/aws-sdk-go-v2 v1.24.0 h1:890+mqQ+hTpNuw0gGP6/4akolQkSToDJgHfQE7AwGuk=
github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
github.com/aws/aws-sdk-go-v2/config v1.25.11 h1:RWzp7jhPRliIcACefGkKp03L0Yofmd2p8M25kbiyvno=
github.com/aws/aws-sdk-go-v2/config v1.25.11/go.mod h1:BVUs0chMdygHsQtvaMyEOpW2GIW+ubrxJLgIz/JU29s=
github.com/aws/aws-sdk-go-v2/credentials v1.16.9 h1:LQo3MUIOzod9JdUK+wxmSdgzLVYUbII3jXn3S/HJZU0=
Expand All @@ -16,6 +18,8 @@ github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.25.5 h1:Td0N1+0Gz
github.com/aws/aws-sdk-go-v2/service/applicationautoscaling v1.25.5/go.mod h1:GeIiZrYejOpIuMAV4acj3l4arHHaA64VO3aUmkrjH+w=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.0 h1:f426fLs4hcrLuczLBqWf1Ob6FKJhISaR4e9Iw3Scr5A=
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.0/go.mod h1:G63GKqSBLpBmO3tN1/PwM2NC65XvSd00zJWTZk202bc=
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1 h1:ZMgx58Tqyr8kTSR9zLzX+W933ujDYleOtFedvn0xHg8=
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.30.1/go.mod h1:4Oeb7n2r/ApBIHphQkprve380p/RpPWBotumd44EDGg=
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.5 h1:3SUOmmbFRHvZGm/B0nZh4a7ryB9hqyXrZLRqZjQ5juA=
github.com/aws/aws-sdk-go-v2/service/ecs v1.35.5/go.mod h1:LzHcyOEvaLjbc5e+fP/KmPWBr+h/Ef+EHvnf1Pzo368=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 h1:e3PCNeEaev/ZF01cQyNZgmYE9oYYePIMJs2mWSKG514=
Expand Down
15 changes: 15 additions & 0 deletions ui/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ func (v *View) switchToTaskDefinitionJson() {

// Switch to selected task definition revision list JSON page
func (v *View) switchToTaskDefinitionRevisionsJson() {
if v.kind == ClusterPage {
return
}
family, _ := v.getTaskDefinitionDetail()

revisions, err := v.app.Store.ListTaskDefinition(&family)
Expand Down Expand Up @@ -84,6 +87,18 @@ func (v *View) switchToServiceEventsList() {
v.showListPages(selected, "events")
}

// Switch to selected service events JSON page
func (v *View) switchToLogsList() {
selected, err := v.getCurrentSelection()
if err != nil {
return
}
if v.kind == ClusterPage {
return
}
v.showListPages(selected, "logs")
}

// Deprecated
// Switch to Metrics get by cloudwatch
func (v *View) switchToMetrics() {
Expand Down
36 changes: 31 additions & 5 deletions ui/list.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package ui

import (
"fmt"
"os"
"time"

"github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
)

const (
logFmt = "[aqua::]%s[-:-:-]:%s\n"
)

// Show new page from LIST(like logs, events) content in table area and handle done event to go back
Expand All @@ -17,14 +24,33 @@ func (v *View) getListString(entity Entity, which string) string {
tz := os.Getenv("TZ")
currentTz, _ := time.LoadLocation(tz)

switch {
case which == "events":
switch which {
case "events":
if entity.service == nil {
contentString += "[red::]No valid contents[-:-:-]"
}
for _, e := range entity.events {
createdAt := e.CreatedAt.In(currentTz)
contentString += createdAt.Format(time.RFC3339) + ": " + *e.Message + "\n"
contentString += fmt.Sprintf(logFmt, createdAt.Format(time.RFC3339), *e.Message)
}
case "logs":
var logs []types.OutputLogEvent
var err error

if entity.service != nil {
logs, err = v.app.Store.GetLogs(entity.service.TaskDefinition)
} else if entity.task != nil {
logs, err = v.app.Store.GetLogs(entity.task.TaskDefinitionArn)
}

if err != nil {
contentString += "[red::]No valid contents[-:-:-]"
}

for _, log := range logs {
m := log.Message
contentString += fmt.Sprintf(logFmt, time.Unix(0, *log.Timestamp*int64(time.Millisecond)).Format(time.RFC3339), *m)
}
// add logs
// case which == "logs"
}

return contentString
Expand Down
3 changes: 2 additions & 1 deletion ui/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ func newServiceView(services []types.Service, app *App) *ServiceView {
keys := append(basicKeyInputs, []KeyInput{
{key: string(wKey), description: describeServiceEvents},
{key: string(tKey), description: describeTaskDefinition},
{key: string(rKey), description: describeTaskDefinitionRevisions},
{key: string(vKey), description: describeTaskDefinitionRevisions},
{key: string(mKey), description: showMetrics},
{key: string(aKey), description: showAutoScaling},
{key: string(eKey), description: editService},
{key: string(oKey), description: showLogs},
}...)
return &ServiceView{
View: *newView(app, ServicePage, keys),
Expand Down
Loading