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

import (
Erick Hitter's avatar
Erick Hitter committed
	"flag"
	"fmt"
	"io/ioutil"
Erick Hitter's avatar
Erick Hitter committed
	"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/ashwanthkumar/slack-go-webhook"
Erick Hitter's avatar
Erick Hitter committed
	"github.com/hpcloud/tail"
)

	DebugDest string      `json:"debug-dest"`
	Debug     bool        `json:"debug"`
	Logs      []logConfig `json:"logs"`
	LogPath     string `json:"log_path"`
	WebhookURL  string `json:"webhook_url"`
	Username    string `json:"username"`
	Channel     string `json:"channel"`
	Color       string `json:"color"`
	IconURL     string `json:"icon_url"`
	SearchRegex string `json:"search"`
	configPath string
	logConfigs []logConfig
Erick Hitter's avatar
Erick Hitter committed

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

func init() {
	flag.StringVar(&configPath, "config", "./config.json", "Path to configuration file")
Erick Hitter's avatar
Erick Hitter committed
	flag.Parse()

	cfgPathValid := validatePath(&configPath)
	if !cfgPathValid {
		usage()
	}
	configFile, err := ioutil.ReadFile(configPath)
	cfg := config{}
	if err = json.Unmarshal(configFile, &cfg); err != nil {
Erick Hitter's avatar
Erick Hitter committed
		usage()
	}
Erick Hitter's avatar
Erick Hitter committed

	debugDest = cfg.DebugDest
	debug = cfg.Debug
	logConfigs = cfg.Logs

	setUpLogger()
Erick Hitter's avatar
Erick Hitter committed
}

func main() {
	logger.Printf("Starting log monitoring with config %s", configPath)
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)

	for _, logCfg := range logConfigs {
		go tailLog(logCfg)
	}
Erick Hitter's avatar
Erick Hitter committed

	caughtSig := <-sig

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

func tailLog(logCfg logConfig) {
	if logPathValid := validatePath(&logCfg.LogPath); !logPathValid {
		if debug {
			logger.Println("Invalid path: ", fmt.Sprintf("%+v\n", logCfg))
		}

		return
	}

	if !govalidator.IsURL(logCfg.WebhookURL) || len(logCfg.Username) == 0 || len(logCfg.Channel) < 2 {
		if debug {
			logger.Println("Invalid webhook, username, channel: ", fmt.Sprintf("%+v\n", logCfg))
		}

		return
	}

	t, err := tail.TailFile(logCfg.LogPath, tail.Config{
		Follow:    true,
		Location:  &tail.SeekInfo{Offset: 0, Whence: 2},
		MustExist: true,
		ReOpen:    true,
		Logger:    logger,
	})
	if err != nil {
		logger.Println(err)
		t.Cleanup()
		return
	}

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

		if len(logCfg.SearchRegex) == 0 {
			go sendLine(line, logCfg)
		} else if matched, _ := regexp.MatchString(logCfg.SearchRegex, line.Text); matched {
			go sendLine(line, logCfg)
Erick Hitter's avatar
Erick Hitter committed
		}
func sendLine(line *tail.Line, logCfg logConfig) {
Erick Hitter's avatar
Erick Hitter committed
	fallback := fmt.Sprintf("New entry in %s", logCfg.LogPath)
	pretext := fmt.Sprintf("In `%s` at `%s`:", logCfg.LogPath, line.Time)
	text := fmt.Sprintf("```\n%s\n```", line.Text)
	att := slack.Attachment{
		Color:    &logCfg.Color,
		Fallback: &fallback,
		PreText:  &pretext,
		Text:     &text,
	}
	payload := slack.Payload{
		Username:    logCfg.Username,
		Channel:     logCfg.Channel,
Erick Hitter's avatar
Erick Hitter committed
		IconUrl:     logCfg.IconURL,
		Attachments: []slack.Attachment{att},
	}

	err := slack.Send(logCfg.WebhookURL, "", payload)
	if len(err) > 0 {
		fmt.Printf("error: %s\n", err)
	}

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) bool {
	if len(*path) <= 1 {
		return false
	}
	var err error
	*path, err = filepath.Abs(*path)
	if err != nil {
		logger.Printf("Error: %s", err.Error())
		return false

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

	return true
Erick Hitter's avatar
Erick Hitter committed
}

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