Skip to content
This repository has been archived by the owner on Mar 5, 2023. It is now read-only.

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
winebarrel committed Apr 14, 2019
0 parents commit 098aca9
Show file tree
Hide file tree
Showing 15 changed files with 805 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pkg/*
32 changes: 32 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
SHELL := /bin/bash
PROGRAM := cwli
VERSION := v0.1.0
GOOS := $(shell go env GOOS)
GOARCH := $(shell go env GOARCH)
ENTRYPOINT := cmd/cwli/main.go
SRC := $(wildcard *.go) $(wildcard */*.go)

.PHONY: all
all: $(PROGRAM)

$(PROGRAM): $(SRC) lint vet
go build \
-ldflags "-X github.com/winebarrel/cwli/cli.version=$(VERSION)" \
-o pkg/$(PROGRAM) $(ENTRYPOINT)

.PHONY: package
package: clean $(PROGRAM)
gzip -c pkg/$(PROGRAM) > pkg/$(PROGRAM)-$(VERSION)-$(GOOS)-$(GOARCH).gz
rm pkg/$(PROGRAM)

.PHONY: lint
lint:
golint -set_exit_status

.PHONY: vet
vet:
go vet

.PHONY: clean
clean:
rm -f pkg/*
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# cwli

## Usage

```
$ cwli -h
Usage of cwli:
-query string
Query string
-showptr
Show @ptr in query result
-version
Print version and exit
```

```
$ cwli
ap-northeast-1> show groups;
/aws/kinesisfirehose/my-kinesis
/aws/lambda/my-lambda
/aws/rds/cluster/my-rds/slow-query
ap-northeast-1> head /aws/lambda/my-lambda limit 1;
*************************** 1. row ***************************
Timestamp: 1516438129835
Message: START RequestId: bbd0dbdd-fdbe-11e7-a41f-398ced7d9303 Version: $LATEST
ap-northeast-1> source /aws/lambda/my-lambda start=2018/11/19 end=2019/11/21 | field @timestamp, @message | limit 2;
{"@timestamp":"2019-04-09 09:38:19.455","@message":"END RequestId: ab83228c-6af1-4c0b-a304-e043aecbe84a\n"}
{"@timestamp":"2019-04-09 09:38:19.455","@message":"2019-04-09T09:38:19.455Z\tab83228c-6af1-4c0b-a304-e043aecbe84a\tLogEC2InstanceStateChange\n"}
// Status: Complete
// Statistics: BytesScanned=859801 RecordsMatched=2065 RecordsScanned=2065
```

### Passing to external command

```
ap-northeast-1> source /aws/lambda/my-lambda start=2018/11/19 end=2019/11/21 | field @timestamp, @message ! head -n 1 | tee output.jsonl;
{"@timestamp":"2019-04-09 09:38:19.455","@message":"2019-04-09T09:38:19.455Z\tab83228c-6af1-4c0b-a304-e043aecbe84a\tLogEC2InstanceStateChange\n"}
```

```
$ cat output.jsonl
{"@timestamp":"2019-04-09 09:38:19.455","@message":"2019-04-09T09:38:19.455Z\tab83228c-6af1-4c0b-a304-e043aecbe84a\tLogEC2InstanceStateChange\n"}
```
31 changes: 31 additions & 0 deletions cli/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cli

import (
"flag"
"fmt"
"os"
)

var version string

type Flags struct {
Query string
Showptr bool
}

func ParseFlags() (flags *Flags) {
flags = &Flags{}
var printVersion bool

flag.StringVar(&flags.Query, "query", "", "Query string")
flag.BoolVar(&flags.Showptr, "showptr", false, "Show @ptr in query result")
flag.BoolVar(&printVersion, "version", false, "Print version and exit")
flag.Parse()

if printVersion {
fmt.Println(version)
os.Exit(0)
}

return
}
18 changes: 18 additions & 0 deletions cmd/cwli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"log"

"github.com/winebarrel/cwli"
"github.com/winebarrel/cwli/cli"
)

func init() {
log.SetFlags(log.LstdFlags)
}

func main() {
flags := cli.ParseFlags()
runner := cwli.NewRunner(flags)
runner.Run()
}
29 changes: 29 additions & 0 deletions command_executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cwli

import (
"io"
"log"
"os"
"os/exec"
)

func executeCommand(str string, input string) (err error) {
cmd := exec.Command("sh", "-c", str)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

stdin, err := cmd.StdinPipe()

if err != nil {
log.Fatal(err)
}

go func() {
defer stdin.Close()
io.WriteString(stdin, input)
}()

err = cmd.Run()

return
}
19 changes: 19 additions & 0 deletions exec/executable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package exec

import (
"io"

"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/winebarrel/cwli/cli"
)

type Executable interface {
Start(svc *cloudwatchlogs.CloudWatchLogs, flags *cli.Flags, out io.Writer) error
}

var Commands = []func(string) (Executable, error){
parseHelp,
parseShowGroups,
parseHead,
parseQuery,
}
152 changes: 152 additions & 0 deletions exec/head.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package exec

import (
"fmt"
"io"
"regexp"
"strconv"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/winebarrel/cwli/cli"
)

var regexpHead = regexp.MustCompile(`(?i)^head\s+(\S+)(?:\s+limit\s+([0-9]+))?\s*$`)

type headCommand struct {
group string
limit int64
}

func parseHead(str string) (cmd Executable, err error) {
submatch := regexpHead.FindStringSubmatch(str)

if len(submatch) == 0 {
return
}

group := submatch[1]
limit := int64(3)

if submatch[2] != "" {
limit, err = strconv.ParseInt(submatch[2], 10, 64)

if err != nil {
return
}
}

if limit <= 0 {
err = fmt.Errorf("Invalid limit: %d", limit)
return
}

cmd = &headCommand{
group: group,
limit: limit,
}

return
}

func describeLogGroup(svc *cloudwatchlogs.CloudWatchLogs, group string) (logGroup *cloudwatchlogs.LogGroup, err error) {
params := &cloudwatchlogs.DescribeLogGroupsInput{
LogGroupNamePrefix: aws.String(group),
Limit: aws.Int64(1),
}

resp, err := svc.DescribeLogGroups(params)

if err != nil {
return
}

if len(resp.LogGroups) == 0 || *resp.LogGroups[0].LogGroupName != group {
err = fmt.Errorf("LogGroup was not found: %s", group)
return
}

logGroup = resp.LogGroups[0]

return
}

func describeLogStream(svc *cloudwatchlogs.CloudWatchLogs, logGroup *cloudwatchlogs.LogGroup) (logStream *cloudwatchlogs.LogStream, err error) {
params := &cloudwatchlogs.DescribeLogStreamsInput{
LogGroupName: logGroup.LogGroupName,
Descending: aws.Bool(true),
OrderBy: aws.String("LastEventTime"),
Limit: aws.Int64(1),
}

resp, err := svc.DescribeLogStreams(params)

if err != nil {
return
}

if len(resp.LogStreams) == 0 {
err = fmt.Errorf("LogStream was not found in %s", *logGroup.LogGroupName)
return
}

logStream = resp.LogStreams[0]

return
}

func getLogEvent(svc *cloudwatchlogs.CloudWatchLogs, logGroup *cloudwatchlogs.LogGroup, logStream *cloudwatchlogs.LogStream, limit int64) (events []*cloudwatchlogs.OutputLogEvent, err error) {
params := &cloudwatchlogs.GetLogEventsInput{
LogGroupName: logGroup.LogGroupName,
LogStreamName: logStream.LogStreamName,
StartTime: logStream.LastEventTimestamp,
Limit: aws.Int64(limit),
}

resp, err := svc.GetLogEvents(params)

if err != nil {
return
}

if len(resp.Events) == 0 {
err = fmt.Errorf("Events was not found in %s", *logGroup.LogGroupName)
return
}

events = resp.Events

return
}

func (cmd *headCommand) Start(svc *cloudwatchlogs.CloudWatchLogs, flags *cli.Flags, out io.Writer) (err error) {
logGroup, err := describeLogGroup(svc, cmd.group)

if err != nil {
return
}

logStream, err := describeLogStream(svc, logGroup)

if err != nil {
return
}

events, err := getLogEvent(svc, logGroup, logStream, cmd.limit)

if err != nil {
return
}

for i, event := range events {
fmt.Fprintf(out, "*************************** %d. row ***************************\n", i+1)
fmt.Fprintf(out, "Timestamp: %d\n", *event.Timestamp)
fmt.Fprintf(out, "Message: %s\n", *event.Message)
}

return
}

func usageHead() string {
return "head LOG-GROUP [limit N]\n\tPrint first lines of a Log Group"
}
45 changes: 45 additions & 0 deletions exec/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package exec

import (
"fmt"
"io"
"regexp"

"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/winebarrel/cwli/cli"
)

var usages = []func() string{
usageHelp,
usageShowGroups,
usageHead,
usageQuery,
}

var regexpHelp = regexp.MustCompile(`(?i)^help\s*$`)

type helpCommand struct {
}

func parseHelp(str string) (cmd Executable, err error) {
if !regexpHelp.MatchString(str) {
return
}

cmd = &helpCommand{}

return
}

func (cmd *helpCommand) Start(svc *cloudwatchlogs.CloudWatchLogs, flags *cli.Flags, out io.Writer) (err error) {

for _, usage := range usages {
fmt.Fprintln(out, usage())
}

return
}

func usageHelp() string {
return "help\n\tPrint a help message"
}
Loading

0 comments on commit 098aca9

Please sign in to comment.