Skip to content

Commit

Permalink
Merge pull request #1 from kamatama41/init
Browse files Browse the repository at this point in the history
Initial commit
  • Loading branch information
kamatama41 authored Jul 14, 2019
2 parents 6d8ce53 + 341f546 commit f0468f2
Show file tree
Hide file tree
Showing 7 changed files with 385 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out


taildog*
25 changes: 25 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
language: go
go:
- "1.12.x"
env:
global:
- GO111MODULE=on
cache:
directories:
- $HOME/.cache/go-build
- $HOME/gopath/pkg/mod

branches:
only:
- master
- release

script:
- make test
- make vet

deploy:
provider: script
script: ./scripts/release.sh
on:
branch: release
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
NAME := taildog

default: test

test:
go test ./...

build:
go build -o $(NAME)

release:
./scripts/release.sh

vet:
@echo "go vet ."
@go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \
echo ""; \
echo "Vet found suspicious constructs. Please check the reported constructs"; \
echo "and fix them if necessary before submitting the code for review."; \
exit 1; \
fi

fmt:
gofmt -w .

.PHONY: test build release vet fmt
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/kamatama41/taildog

go 1.12
251 changes: 251 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package main

import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"text/template"
"time"
)

var (
query = flag.String("q", "",
"Search query. See https://docs.datadoghq.com/logs/explorer/search/ for more details of query.")
msgFormat = flag.String("f", "{{.Timestamp}} {{.Host}} {{.Service}} {{.Message}}",
"Message format of entries in Golang's template style.\n"+
"You can use any field in the \"content\" of the response of the Log Query API.\n"+
"https://docs.datadoghq.com/api/?lang=bash#get-a-list-of-logs\n")
interval = flag.Int64("i", 15000, "Interval time in milliseconds until the next attempt")
limit = flag.Int("l", 1000, "Number of logs fetched once")
fromStr = flag.String("from", "", "Minimum timestamp for requested logs, should be an ISO-8601 string.")
toStr = flag.String("to", "", "Maximum timestamp for requested logs, should be an ISO-8601 string.")
version = flag.Bool("V", false, "Show version of taildog")

apiKey = getEnv("DD_API_KEY")
appKey = getEnv("DD_APP_KEY")
)

type config struct {
query string
from myTime
to myTime
limit int
tmpl *template.Template
nextLogId string
follow bool
}

type logsInfo struct {
Logs []logInfo `json:"logs"`
NextLogId string `json:"nextLogId"`
Status string `json:"status"`
}

type logInfo struct {
Id string `json:"id"`
Content logContent `json:"content"`
}

type logContent struct {
Timestamp string `json:"timestamp"`
Tags []string `json:"tags"`
Attributes map[string]interface{} `json:"attributes"`
Host string `json:"host"`
Service string `json:"service"`
Message string `json:"message"`
}

type myTime struct {
time.Time
}

func main() {
flag.Parse()
if *version {
println(VERSION)
return
}

cfg, err := newConfig()
if err != nil {
log.Fatal(err)
}

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

for cfg.follow {
time.Sleep(time.Duration(*interval) * time.Millisecond)

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

func showLogs(cfg *config) (*config, error) {
log.Println(cfg.String())

reqBody := map[string]interface{}{
"query": cfg.query,
"limit": cfg.limit,
"time": map[string]int64{
"from": cfg.from.UnixMillis(),
"to": cfg.to.UnixMillis(),
},
"sort": "asc",
}
if cfg.nextLogId != "" {
reqBody["startAt"] = cfg.nextLogId
}

reqBodyJson, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}

url := fmt.Sprintf(
"https://api.datadoghq.com/api/v1/logs-queries/list?api_key=%s&application_key=%s", apiKey, appKey)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBodyJson))
if err != nil {
return nil, err
}
req.Header.Add("content-type", "application/json")

client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()

resBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}

if res.StatusCode/100 != 2 {
err = fmt.Errorf("unexpected status %d: %s", res.StatusCode, string(resBody))
return nil, err
}

logsInfo := &logsInfo{}
err = json.Unmarshal(resBody, logsInfo)
if err != nil {
return nil, err
}

for _, l := range logsInfo.Logs {
err := cfg.tmpl.Execute(os.Stdout, l.Content)
if err != nil {
return nil, err
}
}

err = cfg.update(logsInfo)
return cfg, err
}

func getEnv(key string) string {
if v, ok := os.LookupEnv(key); ok {
return v
}
log.Fatalf("Env %s must be sed but not found.", key)
return ""
}

func now() myTime {
return newTime(time.Now())
}

func newTime(t time.Time) myTime {
return myTime{t}
}

func parseTime(str string) (myTime, error) {
t, err := time.Parse(time.RFC3339, str)
return newTime(t), err
}

func (t myTime) UnixMillis() int64 {
return t.UnixNano() / 1000000
}

func (t myTime) Add(d time.Duration) myTime {
return myTime{t.Time.Add(d)}
}

func newConfig() (*config, error) {
tmpl, err := template.New("logLine").Parse(*msgFormat + "\n")
if err != nil {
return nil, err
}

var from, to myTime
if *fromStr != "" {
from, err = parseTime(*fromStr)
if err != nil {
return nil, err
}
}
if *toStr != "" {
to, err = parseTime(*toStr)
if err != nil {
return nil, err
}
}

if (from.IsZero() && !to.IsZero()) || (!from.IsZero() && to.IsZero()) {
return nil, fmt.Errorf("both 'from' and 'to' must be set")
}

follow := false
if from.IsZero() && to.IsZero() {
follow = true
// First attempt for the follow mode, retrieve logs from 30 seconds ago
from = now().Add(time.Duration(-30 * time.Second))
to = now()
}

return &config{
query: *query,
from: from,
to: to,
limit: *limit,
follow: follow,
tmpl: tmpl,
}, nil
}

func (cfg *config) update(info *logsInfo) error {
cfg.nextLogId = info.NextLogId
if cfg.nextLogId != "" {
// There are remaining logs (keep last condition)
return nil
}

if len(info.Logs) != 0 {
ts := info.Logs[len(info.Logs)-1].Content.Timestamp
t, err := parseTime(ts)
if err != nil {
return err
}
// (Timestamp of the last log) + 1ms, to avoid to show duplicate logs
cfg.from = t.Add(time.Duration(1 * time.Millisecond))
}
cfg.to = now()

return nil
}

func (cfg *config) String() string {
return fmt.Sprintf("%s %s %s", cfg.from.Format(time.RFC3339Nano), cfg.to.Format(time.RFC3339Nano), cfg.nextLogId)
}
74 changes: 74 additions & 0 deletions scripts/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash

set -eu

if [[ "${TRAVIS}" != "true" ]]; then
echo "This script is allowed to run on Travis CI"
exit 1
fi

REPO=taildog

git config --global user.email "shiketaudonko41@gmail.com"
git config --global user.name "kamatama41"
git remote -v
git remote add kamatama41 https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/kamatama41/${REPO}.git
git fetch kamatama41
git checkout -b master kamatama41/master

PROJECT_ROOT=$(cd $(dirname $0)/..; pwd)
VERSION_FILE=${PROJECT_ROOT}/version.go
VERSION=$(cat ${VERSION_FILE} | grep -o -E "[0-9]+\.[0-9]+\.[0-9]+")

echo "## Create the new release"
go get github.com/aktau/github-release
github-release release \
--user kamatama41 \
--repo ${REPO} \
--tag v${VERSION}


echo "## Build and upload release binaries"
PLATFORMS="darwin/amd64"
PLATFORMS="${PLATFORMS} windows/amd64"
PLATFORMS="${PLATFORMS} linux/amd64"
for PLATFORM in ${PLATFORMS}; do
GOOS=${PLATFORM%/*}
GOARCH=${PLATFORM#*/}
BIN_FILENAME="${REPO}"
CMD="GOOS=${GOOS} GOARCH=${GOARCH} go build -o ${BIN_FILENAME}"
rm -f ${BIN_FILENAME}
echo "${CMD}"
eval ${CMD}

ZIP_FILENAME="${REPO}_${VERSION}_${GOOS}_${GOARCH}.zip"
CMD="zip ${ZIP_FILENAME} ${BIN_FILENAME}"
echo "${CMD}"
eval ${CMD}

github-release upload \
--user kamatama41 \
--repo ${REPO} \
--tag v${VERSION} \
--name ${ZIP_FILENAME} \
--file ${ZIP_FILENAME}
done

gem install semantic
script=$(cat << EOS
require 'semantic'
puts Semantic::Version.new(gets).increment!(:patch)
EOS
)
NEXT_VERSION=$(echo ${VERSION} | ruby -e "${script}")
cat << EOS > ${VERSION_FILE}
package main
var VERSION = "${NEXT_VERSION}"
EOS


echo "## Bump up the version to ${NEXT_VERSION}"
git add ${VERSION_FILE}
git commit -m "Bump up to the next version"
git push kamatama41 master
3 changes: 3 additions & 0 deletions version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

var VERSION = "0.1.0"

0 comments on commit f0468f2

Please sign in to comment.