Skip to content
Snippets Groups Projects
eth-log-alerting.go 2.8 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/asaskevich/govalidator"
	"github.com/hpcloud/tail"
)

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

	logger    *log.Logger
	debugDest string
	debug     bool

	tailer *tail.Tail
)

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)

	if !govalidator.IsURL(webhookURL) || len(channel) < 2 {
		usage()
	}
}

func main() {
	logger.Printf("Monitoring %s", logPath)
	logger.Printf("Forwarding to channel \"%s\" as user \"%s\" at %s", channel, username, webhookURL)
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

	go tailLog()

	caughtSig := <-sig
	tailer.Stop()
	tailer.Cleanup()
	logger.Printf("Stopping, got signal %s", caughtSig)
}

func tailLog() {
	t, err := tail.TailFile(logPath, tail.Config{Follow: true, MustExist: true, ReOpen: true})
	tailer = t

	if err != nil {
		logger.Println(err)
		usage()
	}

	for line := range t.Lines {
		if line.Err != nil {
			continue
		}

Erick Hitter's avatar
Erick Hitter committed
		if len(searchRegex) == 0 {
			go sendLine(line)
		} else {
			matched, _ := regexp.MatchString(searchRegex, line.Text)
			if matched {
				go sendLine(line)
			}
		}
Erick Hitter's avatar
Erick Hitter committed
	}
}

func sendLine(line *tail.Line) {
	logger.Println(line.Text)
}

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)
}