Newer
Older
"syscall"
"github.com/asaskevich/govalidator"
type config struct {
DebugDest string `json:"debug-dest"`
Debug bool `json:"debug"`
Logs []logConfig `json:"logs"`
type logConfig struct {
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
logger *log.Logger
debugDest string
debug bool
)
func init() {
flag.StringVar(&configPath, "config", "./config.json", "Path to configuration file")
cfgPathValid := validatePath(&configPath)
if !cfgPathValid {
usage()
}
configFile, err := ioutil.ReadFile(configPath)
if err != nil {
usage()
}
cfg := config{}
if err = json.Unmarshal(configFile, &cfg); err != nil {
debugDest = cfg.DebugDest
debug = cfg.Debug
logConfigs = cfg.Logs
logger.Printf("Starting log monitoring with config %s", configPath)
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
for _, logCfg := range logConfigs {
go tailLog(logCfg)
}
caughtSig := <-sig
logger.Printf("Stopping, got signal %s", caughtSig)
}
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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
}
func parseLinesAndSend(t *tail.Tail, logCfg logConfig) {
if line.Err != nil {
continue
}
} else if matched, _ := regexp.MatchString(logCfg.SearchRegex, line.Text); matched {
func sendLine(line *tail.Line, logCfg logConfig) {
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,
Attachments: []slack.Attachment{att},
}
err := slack.Send(logCfg.WebhookURL, "", payload)
if len(err) > 0 {
fmt.Printf("error: %s\n", err)
}
}
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
}
func usage() {
flag.Usage()
os.Exit(3)
}