From e25a322943f7bd741091725464a6c9fdeac5a4e5 Mon Sep 17 00:00:00 2001 From: Martin Geno Date: Fri, 11 Aug 2017 17:45:42 +0200 Subject: [PATCH] add bot + add detect of timestemp in syslog --- bot/command.go | 72 +++++++++++++++++++++++++++++++++ bot/main.go | 41 +++++++++++++++++++ cmd/logmania/main.go | 25 +++++++----- lib/syslog/main.go | 76 +++++++++++++++++++++++++++++++++++ log/level.go | 17 ++++++++ notify/all/internal.go | 6 ++- notify/{ => config}/config.go | 4 +- notify/console/main.go | 7 +++- notify/main.go | 4 +- notify/xmpp/main.go | 24 +++++++++-- receive/syslog/internal.go | 22 ++-------- 11 files changed, 261 insertions(+), 37 deletions(-) create mode 100644 bot/command.go create mode 100644 bot/main.go rename notify/{ => config}/config.go (96%) diff --git a/bot/command.go b/bot/command.go new file mode 100644 index 0000000..3d4b466 --- /dev/null +++ b/bot/command.go @@ -0,0 +1,72 @@ +package bot + +import ( + "fmt" + + "github.com/genofire/logmania/log" +) + +type commandFunc func(func(string), string, []string) + +func (b *Bot) help(answer func(string), from string, params []string) { + msg := fmt.Sprintf("Hi %s there are the following commands:\n", from) + for cmd := range b.commands { + msg = fmt.Sprintf("%s - !%s\n", msg, cmd) + } + answer(msg) +} + +func (b *Bot) sendTo(answer func(string), from string, params []string) { + host := params[0] + to := from + if len(params) > 1 { + to = params[1] + } + + if list, ok := b.state.HostTo[host]; ok { + b.state.HostTo[host] = append(list, to) + } else { + b.state.HostTo[host] = []string{to} + } + + answer(fmt.Sprintf("added %s in list of %s", to, from)) +} + +func (b *Bot) setHostname(answer func(string), from string, params []string) { + host := params[0] + name := params[1] + + b.state.Hostname[host] = name + + answer(fmt.Sprintf("set for %s the hostname %s", host, name)) +} + +func (b *Bot) listHostname(answer func(string), from string, params []string) { + msg := "hostnames:\n" + for ip, hostname := range b.state.Hostname { + msg = fmt.Sprintf("%s%s - %s", msg, ip, hostname) + } + answer(msg) +} + +func (b *Bot) listMaxfilter(answer func(string), from string, params []string) { + msg := "filters:\n" + for to, filter := range b.state.MaxPrioIn { + msg = fmt.Sprintf("%s%s - %s", msg, to, filter.String()) + } + answer(msg) +} + +func (b *Bot) setMaxfilter(answer func(string), from string, params []string) { + to := from + max := log.NewLoglevel(params[0]) + + if len(params) > 1 { + to = params[0] + max = log.NewLoglevel(params[1]) + } + + b.state.MaxPrioIn[to] = max + + answer(fmt.Sprintf("set filter for %s to %s", to, max.String())) +} diff --git a/bot/main.go b/bot/main.go new file mode 100644 index 0000000..550da23 --- /dev/null +++ b/bot/main.go @@ -0,0 +1,41 @@ +package bot + +import ( + "fmt" + "strings" + + configNotify "github.com/genofire/logmania/notify/config" +) + +type Bot struct { + state *configNotify.NotifyState + commands map[string]commandFunc +} + +func NewBot(state *configNotify.NotifyState) *Bot { + b := &Bot{ + state: state, + } + b.commands = map[string]commandFunc{ + "help": b.help, + "send-to": b.sendTo, + "hostname-set": b.setHostname, + "hostname-list": b.listHostname, + "filter-set": b.setMaxfilter, + "filter-list": b.listMaxfilter, + } + return b +} + +func (b *Bot) Handle(answer func(string), from, msg string) { + msgParts := strings.Split(msg, " ") + if msgParts[0][0] != '!' { + return + } + cmdName := msgParts[0][1:] + if cmd, ok := b.commands[cmdName]; ok { + cmd(answer, from, msgParts[1:]) + } else { + answer(fmt.Sprintf("not found command: !%s", cmdName)) + } +} diff --git a/cmd/logmania/main.go b/cmd/logmania/main.go index 4a3d5fe..20fbd80 100644 --- a/cmd/logmania/main.go +++ b/cmd/logmania/main.go @@ -15,21 +15,24 @@ import ( "os/signal" "syscall" + "github.com/genofire/logmania/bot" "github.com/genofire/logmania/lib" log "github.com/genofire/logmania/log" "github.com/genofire/logmania/notify" allNotify "github.com/genofire/logmania/notify/all" + configNotify "github.com/genofire/logmania/notify/config" "github.com/genofire/logmania/receive" allReceiver "github.com/genofire/logmania/receive/all" ) var ( - configPath string - config *lib.Config - notifyConfig *notify.NotifyState - notifier notify.Notifier - receiver receive.Receiver - logChannel chan *log.Entry + configPath string + config *lib.Config + notifyState *configNotify.NotifyState + notifier notify.Notifier + receiver receive.Receiver + logChannel chan *log.Entry + logmaniaBot *bot.Bot ) func main() { @@ -41,10 +44,12 @@ func main() { log.Panicf("Could not load '%s' for configuration.", configPath) } - notifyConfig := notify.ReadStateFile(config.Notify.StateFile) - go notifyConfig.Saver(config.Notify.StateFile) + notifyState := configNotify.ReadStateFile(config.Notify.StateFile) + go notifyState.Saver(config.Notify.StateFile) - notifier = allNotify.Init(&config.Notify, notifyConfig) + logmaniaBot = bot.NewBot(notifyState) + + notifier = allNotify.Init(&config.Notify, notifyState, logmaniaBot) log.Save = notifier.Send logChannel = make(chan *log.Entry) @@ -97,5 +102,5 @@ func reload() { go receiver.Listen() notifier.Close() - notifier = allNotify.Init(&config.Notify, notifyConfig) + notifier = allNotify.Init(&config.Notify, notifyState, logmaniaBot) } diff --git a/lib/syslog/main.go b/lib/syslog/main.go index 7f88add..a8b23d6 100644 --- a/lib/syslog/main.go +++ b/lib/syslog/main.go @@ -1 +1,77 @@ package syslog + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "time" +) + +type SyslogMessage struct { + Timestemp time.Time + Hostname string + Tag string + Service int + Content string + Facility int + Severity int +} + +func Parse(binaryMsg []byte) *SyslogMessage { + var err error + + msg := &SyslogMessage{} + + re := regexp.MustCompile("<([0-9]*)>(.*)") + match := re.FindStringSubmatch(string(binaryMsg)) + + prio, _ := strconv.Atoi(match[1]) + msg.Facility = prio / 8 + msg.Severity = prio % 8 + + timeLength := len(time.RFC3339) + if len(match[2]) > timeLength { + msg.Timestemp, err = time.Parse(time.RFC3339, match[2][:timeLength]) + if err != nil { + timeLength = 0 + } + } + timeLength = len(msg.Timestemp.Format(time.Stamp)) + if len(match[2]) > timeLength { + msg.Timestemp, err = time.Parse(time.Stamp, match[2][:timeLength]) + if err != nil { + timeLength = 0 + } + } + + msg.Content = match[2][timeLength:] + + /* + TODO: detect other parts in content + - Hostname (if exists) + - Tag + - Service + */ + + return msg +} + +func (msg *SyslogMessage) Priority() int { + return msg.Facility*8 + msg.Severity +} + +func (msg *SyslogMessage) Dump() []byte { + result := fmt.Sprintf("<%d>%s %s %s[%d]: %s", + msg.Priority(), + msg.Timestemp.Format(time.RFC3339), + msg.Hostname, + msg.Tag, + msg.Service, + msg.Content, + ) + if !strings.HasSuffix(msg.Content, "\n") { + result = fmt.Sprintf("%s\n", result) + } + return []byte(result) +} diff --git a/log/level.go b/log/level.go index 3a036ce..a1bab92 100644 --- a/log/level.go +++ b/log/level.go @@ -30,6 +30,23 @@ func (l LogLevel) String() string { return "NOT VALID" } +func NewLoglevel(by string) LogLevel { + switch by { + case "debug": + return DebugLevel + case "info": + return InfoLevel + case "warn": + return WarnLevel + case "error": + return ErrorLevel + case "panic": + return PanicLevel + + } + return DebugLevel +} + /** * log command */ diff --git a/notify/all/internal.go b/notify/all/internal.go index b00f69c..b433fb2 100644 --- a/notify/all/internal.go +++ b/notify/all/internal.go @@ -1,9 +1,11 @@ package all import ( + "github.com/genofire/logmania/bot" "github.com/genofire/logmania/lib" "github.com/genofire/logmania/log" "github.com/genofire/logmania/notify" + configNotify "github.com/genofire/logmania/notify/config" ) type Notifier struct { @@ -12,10 +14,10 @@ type Notifier struct { channelNotify chan *log.Entry } -func Init(config *lib.NotifyConfig, state *notify.NotifyState) notify.Notifier { +func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bot) notify.Notifier { var list []notify.Notifier for _, init := range notify.NotifyRegister { - notify := init(config, state) + notify := init(config, state, bot) if notify == nil { continue diff --git a/notify/config.go b/notify/config/config.go similarity index 96% rename from notify/config.go rename to notify/config/config.go index 65134d9..f7a0abb 100644 --- a/notify/config.go +++ b/notify/config/config.go @@ -1,4 +1,4 @@ -package notify +package config import ( "encoding/json" @@ -21,7 +21,7 @@ func (state *NotifyState) SendTo(e *log.Entry) []string { if to, ok := state.HostTo[e.Hostname]; ok { var toList []string for _, toEntry := range to { - if lvl := state.MaxPrioIn[toEntry]; e.Level > lvl { + if lvl := state.MaxPrioIn[toEntry]; e.Level < lvl { continue } toList = append(toList, toEntry) diff --git a/notify/console/main.go b/notify/console/main.go index 818a749..f61e02d 100644 --- a/notify/console/main.go +++ b/notify/console/main.go @@ -8,9 +8,11 @@ import ( "github.com/bclicn/color" + "github.com/genofire/logmania/bot" "github.com/genofire/logmania/lib" "github.com/genofire/logmania/log" "github.com/genofire/logmania/notify" + configNotify "github.com/genofire/logmania/notify/config" ) var ( @@ -25,7 +27,7 @@ type Notifier struct { ShowTime bool } -func Init(config *lib.NotifyConfig, state *notify.NotifyState) notify.Notifier { +func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bot) notify.Notifier { return &Notifier{ TimeFormat: "2006-01-02 15:04:05", ShowTime: true, @@ -34,6 +36,9 @@ func Init(config *lib.NotifyConfig, state *notify.NotifyState) notify.Notifier { // handle a log entry (print it on the terminal with color) func (n *Notifier) Send(e *log.Entry) { + if e.Hostname != "" { + return + } v := []interface{}{} format := "[%s]" diff --git a/notify/main.go b/notify/main.go index e85cbda..dc123a2 100644 --- a/notify/main.go +++ b/notify/main.go @@ -1,8 +1,10 @@ package notify import ( + "github.com/genofire/logmania/bot" "github.com/genofire/logmania/lib" "github.com/genofire/logmania/log" + configNotify "github.com/genofire/logmania/notify/config" ) var NotifyRegister []NotifyInit @@ -12,7 +14,7 @@ type Notifier interface { Close() } -type NotifyInit func(*lib.NotifyConfig, *NotifyState) Notifier +type NotifyInit func(*lib.NotifyConfig, *configNotify.NotifyState, *bot.Bot) Notifier func AddNotifier(n NotifyInit) { NotifyRegister = append(NotifyRegister, n) diff --git a/notify/xmpp/main.go b/notify/xmpp/main.go index a2a60ce..658dc97 100644 --- a/notify/xmpp/main.go +++ b/notify/xmpp/main.go @@ -1,19 +1,22 @@ package xmpp import ( + xmpp "github.com/mattn/go-xmpp" + + "github.com/genofire/logmania/bot" "github.com/genofire/logmania/lib" "github.com/genofire/logmania/log" "github.com/genofire/logmania/notify" - xmpp "github.com/mattn/go-xmpp" + configNotify "github.com/genofire/logmania/notify/config" ) type Notifier struct { notify.Notifier client *xmpp.Client - state *notify.NotifyState + state *configNotify.NotifyState } -func Init(config *lib.NotifyConfig, state *notify.NotifyState) notify.Notifier { +func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bot) notify.Notifier { options := xmpp.Options{ Host: config.XMPP.Host, User: config.XMPP.Username, @@ -28,6 +31,21 @@ func Init(config *lib.NotifyConfig, state *notify.NotifyState) notify.Notifier { if err != nil { return nil } + go func() { + for { + chat, err := client.Recv() + if err != nil { + log.Warn(err) + } + switch v := chat.(type) { + case xmpp.Chat: + bot.Handle(func(answer string) { + client.SendHtml(xmpp.Chat{Remote: v.Remote, Type: "chat", Text: answer}) + }, v.Remote, v.Text) + } + } + }() + log.Info("xmpp startup") return &Notifier{client: client, state: state} } diff --git a/receive/syslog/internal.go b/receive/syslog/internal.go index 68c4be2..e4b1e48 100644 --- a/receive/syslog/internal.go +++ b/receive/syslog/internal.go @@ -1,9 +1,7 @@ package syslog import ( - "regexp" - "strconv" - + libSyslog "github.com/genofire/logmania/lib/syslog" "github.com/genofire/logmania/log" ) @@ -19,23 +17,11 @@ var SyslogPriorityMap = map[int]log.LogLevel{ } func toLogEntry(msg []byte, from string) *log.Entry { - re := regexp.MustCompile("<([0-9]*)>(.*)") - match := re.FindStringSubmatch(string(msg)) - - if len(match) <= 1 { - return &log.Entry{ - Level: log.DebugLevel, - Text: string(msg), - Hostname: from, - } - } - v, _ := strconv.Atoi(match[1]) - prio := v % 8 - text := match[2] + syslogMsg := libSyslog.Parse(msg) return &log.Entry{ - Level: SyslogPriorityMap[prio], - Text: text, + Level: SyslogPriorityMap[syslogMsg.Severity], + Text: syslogMsg.Content, Hostname: from, } }