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

Add query subcommand. #70

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
64 changes: 64 additions & 0 deletions blade/blade.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/fatih/color"
"github.com/olekukonko/tablewriter"
)

// A Blade is a Saw execution instance
Expand Down Expand Up @@ -164,6 +165,69 @@ func (b *Blade) StreamEvents() {
}
}

// RunQuery runs a CloudWatch Logs Insights query and prints the results to the console
func (b *Blade) RunQuery() {
input := b.config.StartQueryInput()
startQueryOutput, err := b.cwl.StartQuery(input)
if err != nil {
fmt.Println("Error", err)
os.Exit(2)
}

queryId := startQueryOutput.QueryId
queryResultsOutput := &cloudwatchlogs.GetQueryResultsOutput{}
for {
queryResultsOutput, err = b.cwl.GetQueryResults(&cloudwatchlogs.GetQueryResultsInput{
QueryId: queryId,
})
if err != nil {
fmt.Println("Error", err)
os.Exit(2)
}

if *queryResultsOutput.Status == "Complete" {
break
}

time.Sleep(2 * time.Second)
}

// We're assuming the field names are always in the same order
// and that the order is the same as they appear in the query
table := tablewriter.NewWriter(os.Stdout)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetAutoFormatHeaders(false)
table.SetAutoWrapText(false)
table.SetBorder(false)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetHeaderLine(false)
table.SetNoWhiteSpace(true)
table.SetRowSeparator("")
table.SetTablePadding("\t")

firstRow := queryResultsOutput.Results[0]
headers := make([]string, len(firstRow)-1)
for i := 0; i < len(firstRow)-1; i++ {
headers[i] = *firstRow[i].Field
}
if !b.output.NoHeaders {
table.SetHeader(headers)
}

logEntries := make([][]string, len(queryResultsOutput.Results))
for index, logRecord := range queryResultsOutput.Results {
logEntry := make([]string, len(logRecord)-1)
for i := 0; i < len(logRecord)-1; i++ {
logEntry[i] = *logRecord[i].Value
}
logEntries[index] = logEntry
}
table.AppendBulk(logEntries)
table.Render()
}

// formatEvent returns a CloudWatch log event as a formatted string using the provided formatter
func formatEvent(formatter *colorjson.Formatter, event *cloudwatchlogs.FilteredLogEvent) string {
red := color.New(color.FgRed).SprintFunc()
Expand Down
54 changes: 54 additions & 0 deletions cmd/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cmd

import (
"fmt"
"os"

"github.com/TylerBrock/saw/blade"
"github.com/TylerBrock/saw/config"
"github.com/spf13/cobra"
)

var queryConfig config.Configuration
var queryOutputConfig config.OutputConfiguration

var queryCommand = &cobra.Command{
Use: "query",
Short: "Query CloudWatch Logs Insights",
Long: "",
Run: func(cmd *cobra.Command, args []string) {
b := blade.NewBlade(&queryConfig, &awsConfig, &queryOutputConfig)
if queryConfig.Query == "" {
fmt.Println("--query must be set")
os.Exit(2)
}
if len(queryConfig.Groups) == 0 {
fmt.Println("--groups must be defined at least once")
os.Exit(2)
}

b.RunQuery()
},
}

func init() {
queryCommand.Flags().StringSliceVar(&queryConfig.Groups, "groups", []string{}, "CloudWatch Log Groups to query against")
queryCommand.Flags().StringVar(&queryConfig.Query, "query", "", "CloudWatch Logs Insights query to run")
queryCommand.Flags().StringVar(
&queryConfig.Start,
"start",
"",
`start getting the logs from this point
Takes an absolute timestamp in RFC3339 format, or a relative time (eg. -2h).
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`,
)
queryCommand.Flags().StringVar(
&queryConfig.End,
"stop",
"now",
`stop getting the logs at this point
Takes an absolute timestamp in RFC3339 format, or a relative time (eg. -2h).
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`,
)
queryCommand.Flags().BoolVar(&queryOutputConfig.NoHeaders, "no-headers", false, "Whether or not to print field headers")
}
1 change: 1 addition & 0 deletions cmd/saw.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func init() {
SawCommand.AddCommand(versionCommand)
SawCommand.AddCommand(watchCommand)
SawCommand.AddCommand(getCommand)
SawCommand.AddCommand(queryCommand)
SawCommand.PersistentFlags().StringVar(&awsConfig.Region, "region", "", "override profile AWS region")
SawCommand.PersistentFlags().StringVar(&awsConfig.Profile, "profile", "", "override default AWS profile")
}
52 changes: 52 additions & 0 deletions config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package config

import (
"errors"
"fmt"
"os"
"sort"
"time"

Expand All @@ -11,13 +13,15 @@ import (

type Configuration struct {
Group string
Groups []string
Prefix string
Start string
End string
Filter string
Streams []*cloudwatchlogs.LogStream
Descending bool
OrderBy string
Query string
}

// Define the order of time formats to attempt to use to parse our input absolute time
Expand All @@ -31,6 +35,10 @@ var absoluteTimeFormats = []string{
// Parse the input string into a time.Time object.
// Provide the currentTime as a parameter to support relative time.
func getTime(timeStr string, currentTime time.Time) (time.Time, error) {
if timeStr == "now" {
return currentTime, nil
}

relative, err := time.ParseDuration(timeStr)
if err == nil {
return currentTime.Add(relative), nil
Expand Down Expand Up @@ -88,6 +96,10 @@ func (c *Configuration) FilterLogEventsInput() *cloudwatchlogs.FilterLogEventsIn
if err == nil {
absoluteStartTime = st
}
if err != nil {
fmt.Println(err)
os.Exit(2)
}
}
input.SetStartTime(aws.TimeUnixMilli(absoluteStartTime))

Expand All @@ -96,6 +108,10 @@ func (c *Configuration) FilterLogEventsInput() *cloudwatchlogs.FilterLogEventsIn
if err == nil {
input.SetEndTime(aws.TimeUnixMilli(et))
}
if err != nil {
fmt.Println(err)
os.Exit(2)
}
}

if c.Filter != "" {
Expand Down Expand Up @@ -126,3 +142,39 @@ func (c *Configuration) TopStreamNames() []*string {

return streamNames
}

func (c *Configuration) StartQueryInput() *cloudwatchlogs.StartQueryInput {
input := cloudwatchlogs.StartQueryInput{}
input.SetLogGroupNames(aws.StringSlice(c.Groups))

// TODO: allow multiple groups

currentTime := time.Now()
absoluteStartTime := currentTime
if c.Start != "" {
st, err := getTime(c.Start, currentTime)
if err == nil {
absoluteStartTime = st
}
if err != nil {
fmt.Println(err)
os.Exit(2)
}
}
input.SetStartTime(aws.TimeUnixMilli(absoluteStartTime))

if c.End != "" {
et, err := getTime(c.End, currentTime)
if err == nil {
input.SetEndTime(aws.TimeUnixMilli(et))
}
if err != nil {
fmt.Println(err)
os.Exit(2)
}
}

input.SetQueryString(c.Query)

return &input
}
1 change: 1 addition & 0 deletions config/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type OutputConfiguration struct {
Invert bool
RawString bool
NoColor bool
NoHeaders bool
}

func (c *OutputConfiguration) Formatter() *colorjson.Formatter {
Expand Down
19 changes: 10 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
module github.com/TylerBrock/saw

go 1.12
go 1.17

require (
github.com/TylerBrock/colorjson v0.0.0-20180527164720-95ec53f28296
github.com/aws/aws-sdk-go v1.13.56
github.com/aws/aws-sdk-go v1.43.12
github.com/fatih/color v1.7.0
github.com/go-ini/ini v1.37.0 // indirect
github.com/spf13/cobra v0.0.3
)

require (
github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/mattn/go-colorable v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.3 // indirect
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
github.com/spf13/cobra v0.0.3
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/spf13/pflag v1.0.1 // indirect
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect
gopkg.in/ini.v1 v1.42.0 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
)
42 changes: 42 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
github.com/TylerBrock/colorjson v0.0.0-20180527164720-95ec53f28296 h1:JYWTroLXcNzSCgu66NMgdjwoMHQRbv2SoOVNFb4kRkE=
github.com/TylerBrock/colorjson v0.0.0-20180527164720-95ec53f28296/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w=
github.com/aws/aws-sdk-go v1.43.12 h1:wOdx6+reSDpUBFEuJDA6edCrojzy8rOtMzhS2rD9+7M=
github.com/aws/aws-sdk-go v1.43.12/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe h1:MCgzztuoH5LZNr9AkIaicIDvCfACu11KUCCZQnRHDC0=
github.com/hokaccha/go-prettyjson v0.0.0-20180920040306-f579f869bbfe/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=