From b91936506c4e6209835691851134a6015a6bc774 Mon Sep 17 00:00:00 2001
From: Erick Hitter <git-contrib@ethitter.com>
Date: Sun, 16 Jul 2017 13:13:48 -0700
Subject: [PATCH] Implement goroutine per log file

---
 eth-log-alerting.go | 201 +++++++++++++++++++++++---------------------
 1 file changed, 104 insertions(+), 97 deletions(-)

diff --git a/eth-log-alerting.go b/eth-log-alerting.go
index 35698ff..af6d958 100644
--- a/eth-log-alerting.go
+++ b/eth-log-alerting.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"log"
 	"os"
 	"os/signal"
@@ -17,21 +18,19 @@ import (
 )
 
 type config struct {
-	DebugDest string
-	Debug     bool
-	Logs      logConfigs
+	DebugDest string      `json:"debug-dest"`
+	Debug     bool        `json:"debug"`
+	Logs      []logConfig `json:"logs"`
 }
 
-type logConfigs []logConfig
-
 type logConfig struct {
-	logPath     string
-	webhookURL  string
-	username    string
-	channel     string
-	color       string
-	iconURL     string
-	searchRegex string
+	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"`
 }
 
 type attachment struct {
@@ -44,15 +43,8 @@ type attachment struct {
 type attachments []attachment
 
 var (
-	logPath     string // Deprecate
-	webhookURL  string // Deprecate
-	username    string // Deprecate
-	channel     string // Deprecate
-	color       string // Deprecate
-	iconURL     string // Deprecate
-	searchRegex string // Deprecate
-
-	mh *matterhook.Client // Deprecate
+	configPath string
+	logConfigs []logConfig
 
 	logger    *log.Logger
 	debugDest string
@@ -60,102 +52,115 @@ var (
 )
 
 func init() {
-	var configPath string
-	// 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")
-	// flag.StringVar(&searchRegex, "search", "", "Search term or regex to match")
-	// 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.StringVar(&configPath, "config", "./config.json", "Path to configuration file")
 	flag.Parse()
 
-	validatePath(&configPath)
+	cfgPathValid := validatePath(&configPath)
+	if !cfgPathValid {
+		usage()
+	}
 
-	configFile, err := os.Open(configPath)
-	jsonDecoder := json.NewDecoder(configFile)
-	config := config{}
-	err = jsonDecoder.Decode(&config)
+	configFile, err := ioutil.ReadFile(configPath)
 	if err != nil {
 		usage()
 	}
 
-	fmt.Println(fmt.Sprintf("%+v\n", config))
-	os.Exit(3)
-	setUpLogger()
-
-	validatePath(&logPath)
-
-	if !govalidator.IsURL(webhookURL) || len(channel) < 2 {
+	config := config{}
+	if err = json.Unmarshal(configFile, &config); err != nil {
 		usage()
 	}
 
-	// mh = matterhook.New(webhookURL, matterhook.Config{DisableServer: true})
+	debugDest = config.DebugDest
+	debug = config.Debug
+
+	setUpLogger()
+
+	logConfigs = config.Logs
 }
 
 func main() {
-	// logger.Printf("Monitoring %s", logPath)
-	// logger.Printf("Forwarding entries to channel \"%s\" as user \"%s\" at %s", channel, username, webhookURL)
+	logger.Printf("Starting log monitoring with config %s", configPath)
 
 	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,
-	// })
-	// if err != nil {
-	// 	logger.Println(err)
-	// 	t.Cleanup()
-	// 	close(sig)
-	// 	os.Exit(3)
-	// }
-
-	// go parseLinesAndSend(t)
+	for _, logCfg := range logConfigs {
+		go tailLog(logCfg)
+	}
 
 	caughtSig := <-sig
-	// t.Stop()
-	// t.Cleanup()
 
 	logger.Printf("Stopping, got signal %s", caughtSig)
 }
 
-func parseLinesAndSend(t *tail.Tail) {
+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
+	}
+
+	mh := matterhook.New(logCfg.WebhookURL, matterhook.Config{DisableServer: true})
+
+	parseLinesAndSend(t, mh, logCfg)
+
+	t.Stop()
+	t.Cleanup()
+}
+
+func parseLinesAndSend(t *tail.Tail, mh *matterhook.Client, logCfg logConfig) {
 	for line := range t.Lines {
 		if line.Err != nil {
 			continue
 		}
 
-		if len(searchRegex) == 0 {
-			go sendLine(line)
-		} else if matched, _ := regexp.MatchString(searchRegex, line.Text); matched {
-			go sendLine(line)
+		if len(logCfg.SearchRegex) == 0 {
+			go sendLine(line, mh, logCfg)
+		} else if matched, _ := regexp.MatchString(logCfg.SearchRegex, line.Text); matched {
+			go sendLine(line, mh, logCfg)
 		}
 	}
 }
 
-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,
-	// })
+func sendLine(line *tail.Line, mh *matterhook.Client, logCfg logConfig) {
+	atts := attachments{
+		attachment{
+			Fallback: fmt.Sprintf("New entry in %s", logCfg.LogPath),
+			Pretext:  fmt.Sprintf("In `%s` at `%s`:", logCfg.LogPath, line.Time),
+			Text:     fmt.Sprintf("    %s", line.Text),
+			Color:    logCfg.Color,
+		},
+	}
+
+	mh.Send(matterhook.OMessage{
+		Channel:     logCfg.Channel,
+		UserName:    logCfg.Username,
+		Attachments: atts,
+		IconURL:     logCfg.IconURL,
+	})
 }
 
 func setUpLogger() {
@@ -178,22 +183,24 @@ func setUpLogger() {
 	}
 }
 
-func validatePath(path *string) {
-	if len(*path) > 1 {
-		var err error
-		*path, err = filepath.Abs(*path)
+func validatePath(path *string) bool {
+	if len(*path) <= 1 {
+		return false
+	}
 
-		if err != nil {
-			fmt.Printf("Error: %s", err.Error())
-			os.Exit(3)
-		}
+	var err error
+	*path, err = filepath.Abs(*path)
 
-		if _, err = os.Stat(*path); os.IsNotExist(err) {
-			usage()
-		}
-	} else {
-		usage()
+	if err != nil {
+		logger.Printf("Error: %s", err.Error())
+		return false
 	}
+
+	if _, err = os.Stat(*path); os.IsNotExist(err) {
+		return false
+	}
+
+	return true
 }
 
 func usage() {
-- 
GitLab