Skip to content
Snippets Groups Projects
eth-log-alerting.go 3.58 KiB
Newer Older
Erick Hitter's avatar
Erick Hitter committed
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"path/filepath"
Erick Hitter's avatar
Erick Hitter committed
	"regexp"
Erick Hitter's avatar
Erick Hitter committed
	"syscall"

	"github.com/42wim/matterbridge/matterhook"
Erick Hitter's avatar
Erick Hitter committed
	"github.com/asaskevich/govalidator"
	"github.com/hpcloud/tail"
)

type attachment struct {
	Fallback string `json:"fallback,omitempty"`
	Pretext  string `json:"pretext,omitempty"`
	Text     string `json:"text"`
	Color    string `json:"color,omitempty"`
}

type attachments []attachment

Erick Hitter's avatar
Erick Hitter committed
var (
Erick Hitter's avatar
Erick Hitter committed
	logPath     string
	webhookURL  string
	username    string
	channel     string
	color       string
	iconURL     string
	searchRegex string
Erick Hitter's avatar
Erick Hitter committed
	mh *matterhook.Client

Erick Hitter's avatar
Erick Hitter committed
	logger    *log.Logger
	debugDest string
	debug     bool
)

func init() {
	flag.StringVar(&logPath, "log-path", "", "Log to monitor")
	flag.StringVar(&webhookURL, "webhook", "", "Webhook to forward log entries to")
	flag.StringVar(&username, "username", "logbot", "Username to post as")
	flag.StringVar(&channel, "channel", "", "Channel to post log entries to")
	flag.StringVar(&color, "color", "default", "Color for entry, either named or hex with `#`")
	flag.StringVar(&iconURL, "icon-url", "", "URL of icon to use for bot")
Erick Hitter's avatar
Erick Hitter committed
	flag.StringVar(&searchRegex, "search", "", "Search term or regex to match")
Erick Hitter's avatar
Erick Hitter committed
	flag.StringVar(&debugDest, "debug-dest", "os.Stdout", "Destination for debug and other messages, omit to log to Stdout")
	flag.BoolVar(&debug, "debug", false, "Include additional log data for debugging")
	flag.Parse()

	setUpLogger()

	validatePath(&logPath)

Erick Hitter's avatar
Erick Hitter committed
	if !govalidator.IsURL(webhookURL) || len(channel) < 2 {
Erick Hitter's avatar
Erick Hitter committed
		usage()
	}
Erick Hitter's avatar
Erick Hitter committed

	mh = matterhook.New(webhookURL, matterhook.Config{DisableServer: true})
Erick Hitter's avatar
Erick Hitter committed
}

func main() {
	logger.Printf("Monitoring %s", logPath)
Erick Hitter's avatar
Erick Hitter committed
	logger.Printf("Forwarding entries to channel \"%s\" as user \"%s\" at %s", channel, username, webhookURL)
Erick Hitter's avatar
Erick Hitter committed

Erick Hitter's avatar
Erick Hitter committed
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

	t, err := tail.TailFile(logPath, tail.Config{
		Follow:    true,
		Location:  &tail.SeekInfo{Offset: 0, Whence: 2},
		MustExist: true,
		ReOpen:    true,
		Logger:    logger,
	})
Erick Hitter's avatar
Erick Hitter committed
	if err != nil {
		logger.Println(err)
Erick Hitter's avatar
Erick Hitter committed
		t.Cleanup()
		close(sig)
		os.Exit(3)
Erick Hitter's avatar
Erick Hitter committed
	go parseLinesAndSend(t)

	caughtSig := <-sig
	t.Stop()
	t.Cleanup()

	logger.Printf("Stopping, got signal %s", caughtSig)
}

func parseLinesAndSend(t *tail.Tail) {
Erick Hitter's avatar
Erick Hitter committed
	for line := range t.Lines {
		if line.Err != nil {
			continue
		}

Erick Hitter's avatar
Erick Hitter committed
		if len(searchRegex) == 0 {
Erick Hitter's avatar
Erick Hitter committed
			go sendLine(line)
		} else if matched, _ := regexp.MatchString(searchRegex, line.Text); matched {
			go sendLine(line)
Erick Hitter's avatar
Erick Hitter committed
		}
Erick Hitter's avatar
Erick Hitter committed
func sendLine(line *tail.Line) {
	atts := attachments{
		attachment{
			Fallback: fmt.Sprintf("New entry in %s", logPath),
			Pretext:  fmt.Sprintf("In `%s` at `%s`:", logPath, line.Time),
			Text:     fmt.Sprintf("    %s", line.Text),
			Color:    color,
		},
	}

	mh.Send(matterhook.OMessage{
		Channel:     channel,
		UserName:    username,
		Attachments: atts,
		IconURL:     iconURL,
	})
Erick Hitter's avatar
Erick Hitter committed
}

func setUpLogger() {
	logOpts := log.Ldate | log.Ltime | log.LUTC | log.Lshortfile

	if debugDest == "os.Stdout" {
		logger = log.New(os.Stdout, "DEBUG: ", logOpts)
	} else {
		path, err := filepath.Abs(debugDest)
		if err != nil {
			logger.Fatal(err)
		}

		logFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal(err)
		}

		logger = log.New(logFile, "", logOpts)
	}
}

func validatePath(path *string) {
	if len(*path) > 1 {
		var err error
		*path, err = filepath.Abs(*path)

		if err != nil {
			fmt.Printf("Error: %s", err.Error())
			os.Exit(3)
		}

		if _, err = os.Stat(*path); os.IsNotExist(err) {
			usage()
		}
	} else {
		usage()
	}
}

func usage() {
	flag.Usage()
	os.Exit(3)
}