diff --git a/bot/command.go b/bot/command.go index b468df4..47ef583 100644 --- a/bot/command.go +++ b/bot/command.go @@ -6,8 +6,7 @@ import ( "time" timeago "github.com/ararog/timeago" - - "github.com/genofire/logmania/log" + log "github.com/sirupsen/logrus" ) type commandFunc func(func(string), string, []string) @@ -156,11 +155,18 @@ func (b *Bot) setMaxfilter(answer func(string), from string, params []string) { return } to := from - max := log.NewLoglevel(params[0]) + var max log.Level + var err error if len(params) > 1 { to = params[0] - max = log.NewLoglevel(params[1]) + max, err = log.ParseLevel(params[1]) + } else { + max, err = log.ParseLevel(params[0]) + } + if err != nil { + answer("invalid priority: CMD Priority\n or\n CMD IPAddress Priority") + return } b.state.MaxPrioIn[to] = max diff --git a/circle.yml b/circle.yml index d35345a..2b790b7 100644 --- a/circle.yml +++ b/circle.yml @@ -9,7 +9,7 @@ dependencies: - mkdir -p ~/.go_workspace/src/github.com/${CIRCLE_PROJECT_USERNAME} - ln -s ${HOME}/${CIRCLE_PROJECT_REPONAME} ${HOME}/.go_workspace/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME} - go get -t -d -v ./... - - go install github.com/genofire/logmania/cmd/logmania + - go install github.com/genofire/logmania post: - cp ~/.go_workspace/bin/logmania logmania.bin - tar -cvzf logmania-builded.tar.gz logmania.bin logmania_example.conf diff --git a/cmd/logmania/main.go b/cmd/logmania/main.go deleted file mode 100644 index 7bff75d..0000000 --- a/cmd/logmania/main.go +++ /dev/null @@ -1,110 +0,0 @@ -// logmania Server -// -// reload config with SIGUSR1 -// -// Usage of logmania: -// -config string -// config file (default "logmania.conf") -// -debug -// enable debuging -package main - -import ( - "flag" - "os" - "os/signal" - "syscall" - "time" - - "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 - notifyState *configNotify.NotifyState - notifier notify.Notifier - receiver receive.Receiver - logChannel chan *log.Entry - logmaniaBot *bot.Bot -) - -func main() { - flag.StringVar(&configPath, "config", "logmania.conf", "config file") - flag.Parse() - - config, err := lib.ReadConfig(configPath) - if config == nil || err != nil { - log.Panicf("Could not load '%s' for configuration.", configPath) - } - - notifyState := configNotify.ReadStateFile(config.Notify.StateFile) - go notifyState.Saver(config.Notify.StateFile) - - logmaniaBot = bot.NewBot(notifyState) - - notifier = allNotify.Init(&config.Notify, notifyState, logmaniaBot) - log.Save = notifier.Send - logChannel = make(chan *log.Entry) - - go func() { - for a := range logChannel { - log.Save(a) - } - }() - if config.Notify.AlertCheck.Duration > time.Duration(time.Second) { - go notifyState.Alert(config.Notify.AlertCheck.Duration, log.Save) - } - - log.Info("starting logmania") - - receiver = allReceiver.Init(&config.Receive, logChannel) - - go receiver.Listen() - - // Wait for system signal - sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1) - for sig := range sigchan { - switch sig { - case syscall.SIGTERM: - log.Panic("terminated of logmania") - os.Exit(0) - case syscall.SIGQUIT: - quit() - case syscall.SIGHUP: - quit() - case syscall.SIGUSR1: - reload() - } - } -} - -func quit() { - receiver.Close() - notifier.Close() - log.Info("quit of logmania") - os.Exit(0) -} - -func reload() { - log.Info("reload config file") - config, err := lib.ReadConfig(configPath) - if config == nil || err != nil { - log.Errorf("reload: could not load '%s' for new configuration. Skip reload.", configPath) - return - } - receiver.Close() - receiver = allReceiver.Init(&config.Receive, logChannel) - go receiver.Listen() - - notifier.Close() - notifier = allNotify.Init(&config.Notify, notifyState, logmaniaBot) -} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..81b7740 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,22 @@ +package cmd + +import ( + log "github.com/sirupsen/logrus" + + "github.com/spf13/cobra" +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "logmania", + Short: "logging centralize", + Long: `centralize logging manager`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + log.Panic(err) + } +} diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..e06cb9e --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,114 @@ +package cmd + +import ( + "os" + "os/signal" + "syscall" + "time" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/genofire/logmania/bot" + "github.com/genofire/logmania/lib" + "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 + notifyState *configNotify.NotifyState + notifier notify.Notifier + receiver receive.Receiver + logChannel chan *log.Entry + logmaniaBot *bot.Bot +) + +// serverCmd represents the serve command +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Runs the logmania server", + Example: "logmania server --config /etc/yanic.toml", + Run: func(cmd *cobra.Command, args []string) { + log.SetFormatter(&log.TextFormatter{ + DisableTimestamp: true, + }) + config, err := lib.ReadConfig(configPath) + if config == nil || err != nil { + log.Panicf("Could not load '%s' for configuration.", configPath) + } + + notifyState := configNotify.ReadStateFile(config.Notify.StateFile) + go notifyState.Saver(config.Notify.StateFile) + + logmaniaBot = bot.NewBot(notifyState) + + notifier = allNotify.Init(&config.Notify, notifyState, logmaniaBot) + log.AddHook(notifier) + logChannel = make(chan *log.Entry) + + go func() { + for a := range logChannel { + notifier.Fire(a) + } + }() + + if config.Notify.AlertCheck.Duration > time.Duration(time.Second) { + go notifyState.Alert(config.Notify.AlertCheck.Duration, notifier.Fire) + } + + log.Info("starting logmania") + + receiver = allReceiver.Init(&config.Receive, logChannel) + + go receiver.Listen() + + // Wait for system signal + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1) + for sig := range sigchan { + switch sig { + case syscall.SIGTERM: + log.Panic("terminated of logmania") + os.Exit(0) + case syscall.SIGQUIT: + quit() + case syscall.SIGHUP: + quit() + case syscall.SIGUSR1: + reload() + } + } + }, +} + +func quit() { + receiver.Close() + notifier.Close() + log.Info("quit of logmania") + os.Exit(0) +} + +func reload() { + log.Info("reload config file") + config, err := lib.ReadConfig(configPath) + if config == nil || err != nil { + log.Errorf("reload: could not load '%s' for new configuration. Skip reload.", configPath) + return + } + receiver.Close() + receiver = allReceiver.Init(&config.Receive, logChannel) + go receiver.Listen() + + notifier.Close() + notifier = allNotify.Init(&config.Notify, notifyState, logmaniaBot) +} + +func init() { + RootCmd.AddCommand(serverCmd) + serverCmd.Flags().StringVarP(&configPath, "config", "c", "logmania.conf", "Path to configuration file") +} diff --git a/lib/config.go b/lib/config.go index 8383ced..f83065c 100644 --- a/lib/config.go +++ b/lib/config.go @@ -5,7 +5,7 @@ import ( "github.com/BurntSushi/toml" - "github.com/genofire/logmania/log" + log "github.com/sirupsen/logrus" ) // Struct of the configuration diff --git a/lib/syslog/main.go b/lib/syslog/main.go index a8b23d6..4d33a19 100644 --- a/lib/syslog/main.go +++ b/lib/syslog/main.go @@ -45,7 +45,7 @@ func Parse(binaryMsg []byte) *SyslogMessage { } } - msg.Content = match[2][timeLength:] + msg.Content = strings.TrimLeft(match[2][timeLength:], " ") /* TODO: detect other parts in content diff --git a/log/client/main.go b/log/client/main.go deleted file mode 100644 index 406925c..0000000 --- a/log/client/main.go +++ /dev/null @@ -1,84 +0,0 @@ -package client - -/* logger to bind at github.com/genofire/logmania/log.AddLogger to send log entries to logmania server - -import ( - "fmt" - - "github.com/gorilla/websocket" - - "github.com/genofire/logmania/log" -) - -// client logger -type Logger struct { - log.Logger - AboveLevel log.LogLevel - conn *websocket.Conn -} - -const LOGGER_NAME = "client" - -// CurrentLogger (for override settings e.g. AboveLevel) -var CurrentLogger *Logger - -// create a new logmania client logger -func NewLogger(url, token string, AboveLevel log.LogLevel) *Logger { - c, _, err := websocket.DefaultDialer.Dial(fmt.Sprint(url, "/logger"), nil) - if err != nil { - log.Error("[logmania] error on connect: ", err) - return nil - } - err = c.WriteMessage(websocket.TextMessage, []byte(token)) - if err != nil { - log.Error("[logmania] could not send token:", err) - return nil - } - return &Logger{ - AboveLevel: AboveLevel, - conn: c, - } -} - -// handle a log entry (send to logmania server) -func (l *Logger) Hook(e *log.Entry) { - if e.Level < l.AboveLevel { - return - } - err := l.conn.WriteJSON(e) - if err != nil { - l.Close() - log.Error("[logmania] could not send log entry:", err) - } -} - -// Listen if logmania server want to close the connection -func (l *Logger) Listen() { - for { - msgType, _, err := l.conn.ReadMessage() - if msgType == -1 { - l.conn.Close() - l.Close() - return - } - if err != nil { - l.Close() - log.Warn("[logmania] close listener:", err) - } - } -} - -// close connection to logger -func (l *Logger) Close() { - l.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - log.RemoveLogger(LOGGER_NAME) -} - -// init logmania's client logger and bind -func Init(url, token string, AboveLevel log.LogLevel) *Logger { - CurrentLogger = NewLogger(url, token, AboveLevel) - go CurrentLogger.Listen() - log.AddLogger(LOGGER_NAME, CurrentLogger) - return CurrentLogger -} -*/ diff --git a/log/client/main_deadtest.go b/log/client/main_deadtest.go deleted file mode 100644 index da13c8e..0000000 --- a/log/client/main_deadtest.go +++ /dev/null @@ -1 +0,0 @@ -package client diff --git a/log/init.go b/log/init.go deleted file mode 100644 index c8d2b2c..0000000 --- a/log/init.go +++ /dev/null @@ -1,46 +0,0 @@ -package log - -import ( - "net/http" - - wsGozilla "github.com/gorilla/websocket" - "golang.org/x/net/websocket" -) - -func getIP(r *http.Request) string { - ip := r.Header.Get("X-Forwarded-For") - if ip == "" { - ip = r.RemoteAddr - } - return ip -} - -// init log entry with extra fields of interesting http request context -func HTTP(r *http.Request) *Entry { - return New().AddFields(map[string]interface{}{ - "remote": getIP(r), - "method": r.Method, - "url": r.URL.RequestURI(), - }) -} - -// init log entry with extra fields of interesting websocket request context -func WebsocketX(ws *websocket.Conn) *Entry { - r := ws.Request() - if r == nil { - return New().AddField("websocket", true) - } - return New().AddFields(map[string]interface{}{ - "remote": getIP(r), - "websocket": true, - "url": r.URL.RequestURI(), - }) -} - -// init log entry with extra fields of interesting websocket request context -func WebsocketGozilla(ws *wsGozilla.Conn) *Entry { - return New().AddFields(map[string]interface{}{ - "remote": ws.RemoteAddr().String(), - "websocket": true, - }) -} diff --git a/log/init_test.go b/log/init_test.go deleted file mode 100644 index d7339a5..0000000 --- a/log/init_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package log - -import ( - "net/http" - "testing" - - "golang.org/x/net/websocket" - - "github.com/stretchr/testify/assert" -) - -func TestGetIP(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("GET", "https://google.com/lola/duda?q=wasd", nil) - req.RemoteAddr = "127.0.0.1" - assert.Equal("127.0.0.1", getIP(req)) - req.Header.Set("X-Forwarded-For", "8.8.8.8") - assert.Equal("8.8.8.8", getIP(req)) - -} - -func TestHTTP(t *testing.T) { - assert := assert.New(t) - - req, _ := http.NewRequest("GET", "https://google.com/lola/duda?q=wasd", nil) - entry := HTTP(req) - _, ok := entry.Fields["remote"] - - assert.NotNil(ok, "remote address not set in logger") - assert.Equal("GET", entry.Fields["method"], "method not set in logger") - assert.Equal("/lola/duda?q=wasd", entry.Fields["url"], "path not set in logger") -} - -func TestWebsocketX(t *testing.T) { - assert := assert.New(t) - ws := &websocket.Conn{} - entry := WebsocketX(ws) - _, ok := entry.Fields["remote"] - - assert.NotNil(ok, "remote address not set in logger") - assert.True(entry.Fields["websocket"].(bool)) -} diff --git a/log/level.go b/log/level.go deleted file mode 100644 index a1bab92..0000000 --- a/log/level.go +++ /dev/null @@ -1,156 +0,0 @@ -package log - -// definition of loglevel -type LogLevel int32 - -// accepted LogLevels and his internal int values -const ( - DebugLevel = LogLevel(-1) - InfoLevel = LogLevel(0) - WarnLevel = LogLevel(1) - ErrorLevel = LogLevel(2) - PanicLevel = LogLevel(3) -) - -// string of loglevel -func (l LogLevel) String() string { - switch l { - case DebugLevel: - return "Debug" - case InfoLevel: - return "Info" - case WarnLevel: - return "Warn" - case ErrorLevel: - return "ERROR" - case PanicLevel: - return "PANIC" - - } - 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 - */ - -// close logentry with debug -func (e *Entry) Debug(v ...interface{}) { - e.Log(DebugLevel, v...) -} - -// close logentry with formated debug -func (e *Entry) Debugf(format string, v ...interface{}) { - e.Logf(DebugLevel, format, v...) -} - -// close logentry with info -func (e *Entry) Info(v ...interface{}) { - e.Log(InfoLevel, v...) -} - -// close logentry with formated info -func (e *Entry) Infof(format string, v ...interface{}) { - e.Logf(InfoLevel, format, v...) -} - -// close logentry with warning -func (e *Entry) Warn(v ...interface{}) { - e.Log(WarnLevel, v...) -} - -// close logentry with formated warning -func (e *Entry) Warnf(format string, v ...interface{}) { - e.Logf(WarnLevel, format, v...) -} - -// close logentry with error -func (e *Entry) Error(v ...interface{}) { - e.Log(ErrorLevel, v...) -} - -// close logentry with formated error -func (e *Entry) Errorf(format string, v ...interface{}) { - e.Logf(ErrorLevel, format, v...) -} - -// close logentry with panic -func (e *Entry) Panic(v ...interface{}) { - e.Log(PanicLevel, v...) -} - -// close logentry with formated panic -func (e *Entry) Panicf(format string, v ...interface{}) { - e.Logf(PanicLevel, format, v...) -} - -/** - * Direct log command - */ - -// direct log with debug -func Debug(v ...interface{}) { - New().Log(DebugLevel, v...) -} - -// direct log with formated debug -func Debugf(format string, v ...interface{}) { - New().Logf(DebugLevel, format, v...) -} - -// direct log with info -func Info(v ...interface{}) { - New().Log(InfoLevel, v...) -} - -// direct log with formated info -func Infof(format string, v ...interface{}) { - New().Logf(InfoLevel, format, v...) -} - -// direct log with warning -func Warn(v ...interface{}) { - New().Log(WarnLevel, v...) -} - -// direct log with formated warning -func Warnf(format string, v ...interface{}) { - New().Logf(WarnLevel, format, v...) -} - -// direct log with error -func Error(v ...interface{}) { - New().Log(ErrorLevel, v...) -} - -// direct log with formated error -func Errorf(format string, v ...interface{}) { - New().Logf(ErrorLevel, format, v...) -} - -// direct log with panic -func Panic(v ...interface{}) { - New().Log(PanicLevel, v...) -} - -// direct log with formated panic -func Panicf(format string, v ...interface{}) { - New().Logf(PanicLevel, format, v...) -} diff --git a/log/logger.go b/log/logger.go deleted file mode 100644 index 91f5cbf..0000000 --- a/log/logger.go +++ /dev/null @@ -1,9 +0,0 @@ -package log - -type loggerFunc func(*Entry) - -var Save loggerFunc - -func init() { - Save = func(*Entry) {} -} diff --git a/log/main.go b/log/main.go deleted file mode 100644 index ed1aa7b..0000000 --- a/log/main.go +++ /dev/null @@ -1,55 +0,0 @@ -// log package with entry as a lib in other go applications -package log - -import "fmt" - -// a struct with all information of a log entry -type Entry struct { - Level LogLevel `json:"level"` - Hostname string `json:"hostname"` - Service string `json:"service"` - Fields map[string]interface{} `json:"fields"` - Text string `json:"text"` -} - -// Save/out current state of log entry -func (e *Entry) Log(level LogLevel, v ...interface{}) { - e.Text = fmt.Sprint(v...) - e.Level = level - Save(e) -} - -// Save/out current state of log entry with formation -func (e *Entry) Logf(level LogLevel, format string, v ...interface{}) { - e.Text = fmt.Sprintf(format, v...) - e.Level = level - Save(e) -} - -// init new log entry -func New() *Entry { - return &Entry{Fields: make(map[string]interface{})} -} - -// add extra value to entry (log entry with context) -func (e *Entry) AddField(key string, value interface{}) *Entry { - e.Fields[key] = value - return e -} - -// add multi extra values to entry (log entry with context) -func (e *Entry) AddFields(fields map[string]interface{}) *Entry { - for key, value := range fields { - e.Fields[key] = value - } - return e -} - -// create a readable string of extra values (log entry with context) -func (e *Entry) FieldString() string { - text := "" - for key, value := range e.Fields { - text = fmt.Sprintf("%s %s=%v", text, key, value) - } - return text[1:] -} diff --git a/log/main_test.go b/log/main_test.go deleted file mode 100644 index 75b2829..0000000 --- a/log/main_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package log - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestLog(t *testing.T) { - assert := assert.New(t) - //save := func(e *Entry) {} - entry := New() - assert.Equal(0, int(entry.Level)) - assert.Equal("", entry.Text) - - entry.Log(WarnLevel, "blub") - assert.Equal(1, int(entry.Level)) - assert.Equal("blub", entry.Text) - - entry.Logf(ErrorLevel, "lola %.1f", 13.13431) - assert.Equal(2, int(entry.Level)) - assert.Equal("lola 13.1", entry.Text) -} - -func TestAddFields(t *testing.T) { - assert := assert.New(t) - entry := New() - assert.Len(entry.Fields, 0) - - entry.AddField("a", "lola") - assert.Len(entry.Fields, 1) - assert.Equal("lola", entry.Fields["a"]) - - entry.AddFields(map[string]interface{}{"a": 232., "foo": "bar"}) - assert.Len(entry.Fields, 2) - assert.Equal(232.0, entry.Fields["a"]) -} - -func TestFieldString(t *testing.T) { - assert := assert.New(t) - entry := New() - entry.AddFields(map[string]interface{}{"a": 232., "foo": "bar"}) - str := entry.FieldString() - assert.Contains(str, "a=232") - assert.Contains(str, "foo=bar") -} diff --git a/main.go b/main.go new file mode 100644 index 0000000..7e929ac --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/genofire/logmania/cmd" + +func main() { + cmd.Execute() +} diff --git a/notify/all/internal.go b/notify/all/internal.go index b433fb2..de572cc 100644 --- a/notify/all/internal.go +++ b/notify/all/internal.go @@ -1,9 +1,10 @@ package all import ( + log "github.com/sirupsen/logrus" + "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" ) @@ -36,13 +37,14 @@ func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bo func (n *Notifier) sender() { for c := range n.channelNotify { for _, item := range n.list { - item.Send(c) + item.Fire(c) } } } -func (n *Notifier) Send(e *log.Entry) { +func (n *Notifier) Fire(e *log.Entry) error { n.channelNotify <- e + return nil } func (n *Notifier) Close() { @@ -50,3 +52,14 @@ func (n *Notifier) Close() { item.Close() } } + +func (n *Notifier) Levels() []log.Level { + return []log.Level{ + log.DebugLevel, + log.InfoLevel, + log.WarnLevel, + log.ErrorLevel, + log.FatalLevel, + log.PanicLevel, + } +} diff --git a/notify/all/main.go b/notify/all/main.go index 7a1e7bc..0c3dec8 100644 --- a/notify/all/main.go +++ b/notify/all/main.go @@ -1,6 +1,5 @@ package all import ( - _ "github.com/genofire/logmania/notify/console" _ "github.com/genofire/logmania/notify/xmpp" ) diff --git a/notify/config/config.go b/notify/config/config.go index 363d40a..42fe78a 100644 --- a/notify/config/config.go +++ b/notify/config/config.go @@ -2,12 +2,11 @@ package config import ( "encoding/json" - "fmt" "os" "regexp" "time" - "github.com/genofire/logmania/log" + log "github.com/sirupsen/logrus" ) const AlertMsg = "alert service from logmania, device did not send new message for a while" @@ -15,26 +14,30 @@ const AlertMsg = "alert service from logmania, device did not send new message f type NotifyState struct { Hostname map[string]string `json:"hostname"` HostTo map[string]map[string]bool `json:"host_to"` - MaxPrioIn map[string]log.LogLevel `json:"maxLevel"` + MaxPrioIn map[string]log.Level `json:"maxLevel"` RegexIn map[string]map[string]*regexp.Regexp `json:"regexIn"` Lastseen map[string]time.Time `json:"lastseen,omitempty"` LastseenNotify map[string]time.Time `json:"-"` } func (state *NotifyState) SendTo(e *log.Entry) []string { - if to, ok := state.HostTo[e.Hostname]; ok { - if e.Text != AlertMsg && e.Hostname != "" { - state.Lastseen[e.Hostname] = time.Now() + hostname, ok := e.Data["hostname"].(string) + if !ok { + return nil + } + if to, ok := state.HostTo[hostname]; ok { + if e.Message != AlertMsg && hostname != "" { + state.Lastseen[hostname] = time.Now() } var toList []string for toEntry, _ := range to { - if lvl := state.MaxPrioIn[toEntry]; e.Level < lvl { + if lvl := state.MaxPrioIn[toEntry]; e.Level >= lvl { continue } if regex, ok := state.RegexIn[toEntry]; ok { stopForTo := false for _, expr := range regex { - if expr.MatchString(e.Text) { + if expr.MatchString(e.Message) { stopForTo = true continue } @@ -45,12 +48,12 @@ func (state *NotifyState) SendTo(e *log.Entry) []string { } toList = append(toList, toEntry) } - if hostname, ok := state.Hostname[e.Hostname]; ok { - e.Hostname = hostname + if replaceHostname, ok := state.Hostname[hostname]; ok { + e.WithField("hostname", replaceHostname) } return toList } else { - state.HostTo[e.Hostname] = make(map[string]bool) + state.HostTo[hostname] = make(map[string]bool) } return nil } @@ -71,7 +74,7 @@ func ReadStateFile(path string) *NotifyState { var state NotifyState if f, err := os.Open(path); err == nil { // transform data to legacy meshviewer if err = json.NewDecoder(f).Decode(&state); err == nil { - fmt.Println("loaded", len(state.HostTo), "hosts") + log.Infof("loaded %d hosts", len(state.HostTo)) if state.Lastseen == nil { state.Lastseen = make(map[string]time.Time) } @@ -89,15 +92,15 @@ func ReadStateFile(path string) *NotifyState { } return &state } else { - fmt.Println("failed to unmarshal nodes:", err) + log.Error("failed to unmarshal nodes:", err) } } else { - fmt.Println("failed to open state notify file: ", path, ":", err) + log.Error("failed to open state notify file: ", path, ":", err) } return &NotifyState{ Hostname: make(map[string]string), HostTo: make(map[string]map[string]bool), - MaxPrioIn: make(map[string]log.LogLevel), + MaxPrioIn: make(map[string]log.Level), RegexIn: make(map[string]map[string]*regexp.Regexp), Lastseen: make(map[string]time.Time), LastseenNotify: make(map[string]time.Time), @@ -112,7 +115,7 @@ func (state *NotifyState) Saver(path string) { } } -func (state *NotifyState) Alert(expired time.Duration, send func(e *log.Entry)) { +func (state *NotifyState) Alert(expired time.Duration, send func(e *log.Entry) error) { c := time.Tick(time.Minute) for range c { @@ -121,11 +124,11 @@ func (state *NotifyState) Alert(expired time.Duration, send func(e *log.Entry)) if time.Before(now.Add(expired * -2)) { if timeNotify, ok := state.LastseenNotify[host]; !ok || !time.Before(timeNotify) { state.LastseenNotify[host] = now - send(&log.Entry{ - Hostname: host, - Level: log.ErrorLevel, - Text: AlertMsg, - }) + entry := log.NewEntry(log.New()) + entry.Level = log.ErrorLevel + entry.Message = AlertMsg + entry.WithField("hostname", host) + send(entry) } } } diff --git a/notify/console/main.go b/notify/console/main.go deleted file mode 100644 index 1110826..0000000 --- a/notify/console/main.go +++ /dev/null @@ -1,104 +0,0 @@ -package console - -import ( - "fmt" - "io" - "os" - "time" - - "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 ( - errOutput io.Writer = os.Stderr - output io.Writer = os.Stdout -) - -// logger for output -type Notifier struct { - notify.Notifier - TimeFormat string - ShowTime bool - Debug bool -} - -func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bot) notify.Notifier { - return &Notifier{ - TimeFormat: "2006-01-02 15:04:05", - ShowTime: true, - Debug: config.Console, - } -} - -// handle a log entry (print it on the terminal with color) -func (n *Notifier) Send(e *log.Entry) { - if e == nil || n == nil { - return - } - if e.Hostname != "" && !n.Debug { - return - } - v := []interface{}{} - format := "[%s]" - - if n.ShowTime { - format = "%s [%s]" - v = append(v, color.LightBlue(time.Now().Format(n.TimeFormat))) - } - - // Hostname and Service - if e.Hostname != "" && e.Service != "" { - format = fmt.Sprintf("%s [%%s-%%s]", format) - v = append(v, color.Purple(e.Hostname), color.Cyan(e.Service)) - } else if e.Hostname != "" { - format = fmt.Sprintf("%s [%%s]", format) - v = append(v, color.Purple(e.Hostname)) - } else if e.Service != "" { - format = fmt.Sprintf("%s [%%s]", format) - v = append(v, color.Cyan(e.Service)) - } - - format = fmt.Sprintf("%s %%s", format) - lvl := e.Level.String() - switch e.Level { - case log.DebugLevel: - lvl = color.DarkGray(lvl) - case log.InfoLevel: - lvl = color.Green(lvl) - case log.WarnLevel: - lvl = color.Yellow(lvl) - case log.ErrorLevel: - lvl = color.Red(lvl) - case log.PanicLevel: - lvl = color.BRed(lvl) - } - - v = append(v, lvl, e.Text) - - if len(e.Fields) > 0 { - v = append(v, color.Purple(e.FieldString())) - format = fmt.Sprintf("%s (%%s)\n", format) - } else { - format = fmt.Sprintf("%s\n", format) - } - - text := fmt.Sprintf(format, v...) - - if e.Level > log.WarnLevel { - errOutput.Write([]byte(text)) - } else { - output.Write([]byte(text)) - } -} - -func (n *Notifier) Close() {} - -func init() { - notify.AddNotifier(Init) -} diff --git a/notify/console/main_test.go b/notify/console/main_test.go deleted file mode 100644 index ca7bb9f..0000000 --- a/notify/console/main_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package console - -import ( - "bytes" - "os" - "testing" - - "github.com/genofire/logmania/log" - "github.com/stretchr/testify/assert" -) - -func captureOutput(f func()) (string, string) { - var bufErrOutput bytes.Buffer - var bufOutput bytes.Buffer - errOutput = &bufErrOutput - output = &bufOutput - f() - errOutput = os.Stderr - output = os.Stdout - return bufOutput.String(), bufErrOutput.String() -} - -// Warning: colors are not tested (it should be in the imported package) -func TestOutput(t *testing.T) { - assert := assert.New(t) - assert.True(true) - out, err := captureOutput(func() { - log.Info("test") - }) - assert.Equal("", err) - - out, err = captureOutput(func() { - log.Warn("test") - }) - assert.NotRegexp("-.*\\[.{5}Warn.{4}\\] test", out) - assert.Equal("", err) - - out, err = captureOutput(func() { - log.Error("test") - }) - assert.Equal("", out) - - out, err = captureOutput(func() { - log.Debug("test") - }) - assert.Equal("", out) - assert.Equal("", err) - - out, err = captureOutput(func() { - log.Info("test") - }) - assert.Equal("", out) - assert.Equal("", err) - -} diff --git a/notify/main.go b/notify/main.go index dc123a2..518e2b9 100644 --- a/notify/main.go +++ b/notify/main.go @@ -1,16 +1,18 @@ package notify import ( + log "github.com/sirupsen/logrus" + "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 type Notifier interface { - Send(entry *log.Entry) + Fire(entry *log.Entry) error + Levels() []log.Level Close() } diff --git a/notify/xmpp/internal.go b/notify/xmpp/internal.go deleted file mode 100644 index 6966adc..0000000 --- a/notify/xmpp/internal.go +++ /dev/null @@ -1,21 +0,0 @@ -package xmpp - -import ( - "fmt" - - "github.com/genofire/logmania/log" -) - -func formatEntry(e *log.Entry) string { - if e.Hostname != "" && e.Service != "" { - return fmt.Sprintf("[%s-%s] [%s] %s", e.Hostname, e.Service, e.Level, e.Text) - - } else if e.Hostname != "" { - return fmt.Sprintf("[%s] [%s] %s", e.Hostname, e.Level, e.Text) - - } else if e.Service != "" { - return fmt.Sprintf("[%s] [%s] %s", e.Service, e.Level, e.Text) - - } - return fmt.Sprintf("[%s] %s", e.Level, e.Text) -} diff --git a/notify/xmpp/main.go b/notify/xmpp/main.go index fe8c823..cf62e16 100644 --- a/notify/xmpp/main.go +++ b/notify/xmpp/main.go @@ -1,22 +1,26 @@ package xmpp import ( + "errors" "fmt" "strings" xmpp "github.com/mattn/go-xmpp" + log "github.com/sirupsen/logrus" "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 logger = log.WithField("notify", "xmpp") + type Notifier struct { notify.Notifier - client *xmpp.Client - state *configNotify.NotifyState + client *xmpp.Client + state *configNotify.NotifyState + formatter *log.TextFormatter } func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bot) notify.Notifier { @@ -32,13 +36,14 @@ func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bo } client, err := options.NewClient() if err != nil { + logger.Error(err) return nil } go func() { for { chat, err := client.Recv() if err != nil { - log.Warn(err) + logger.Warn(err) } switch v := chat.(type) { case xmpp.Chat: @@ -48,30 +53,52 @@ func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bo } } }() - log.Info("xmpp startup") - return &Notifier{client: client, state: state} + logger.Info("startup") + return &Notifier{ + client: client, + state: state, + formatter: &log.TextFormatter{ + DisableTimestamp: true, + }, + } } -func (n *Notifier) Send(e *log.Entry) { +func (n *Notifier) Fire(e *log.Entry) error { to := n.state.SendTo(e) if to == nil { - return + return errors.New("no reciever found") + } + text, err := n.formatter.Format(e) + if err != nil { + return err } for _, toAddr := range to { to := strings.TrimPrefix(toAddr, "xmpp:") if strings.Contains(toAddr, "conference") || strings.Contains(toAddr, "irc") { n.client.JoinMUCNoHistory(to, "logmania") - _, err := n.client.SendHtml(xmpp.Chat{Remote: to, Type: "groupchat", Text: formatEntry(e)}) + _, err = n.client.SendHtml(xmpp.Chat{Remote: to, Type: "groupchat", Text: string(text)}) if err != nil { - fmt.Println("xmpp to ", to, " error:", err) + logger.Error("xmpp to ", to, " error:", err) } } else { - _, err := n.client.SendHtml(xmpp.Chat{Remote: to, Type: "chat", Text: formatEntry(e)}) + _, err := n.client.SendHtml(xmpp.Chat{Remote: to, Type: "chat", Text: string(text)}) if err != nil { - fmt.Println("xmpp to ", to, " error:", err) + logger.Error("xmpp to ", to, " error:", err) } } } + return nil +} + +func (n *Notifier) Levels() []log.Level { + return []log.Level{ + log.DebugLevel, + log.InfoLevel, + log.WarnLevel, + log.ErrorLevel, + log.FatalLevel, + log.PanicLevel, + } } func (n *Notifier) Close() {} diff --git a/receive/all/internal.go b/receive/all/internal.go index ce774e7..7314d21 100644 --- a/receive/all/internal.go +++ b/receive/all/internal.go @@ -1,8 +1,9 @@ package all import ( + log "github.com/sirupsen/logrus" + "github.com/genofire/logmania/lib" - "github.com/genofire/logmania/log" "github.com/genofire/logmania/receive" ) diff --git a/receive/journald_json/internal.go b/receive/journald_json/internal.go index a7218e6..c471bfa 100644 --- a/receive/journald_json/internal.go +++ b/receive/journald_json/internal.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strconv" - "github.com/genofire/logmania/log" + log "github.com/sirupsen/logrus" ) type JournalMessage struct { @@ -34,7 +34,7 @@ type JournalMessage struct { Message string `json:"MESSAGE"` } -var PriorityMap = map[int]log.LogLevel{ +var PriorityMap = map[int]log.Level{ 0: log.PanicLevel, // emerg 1: log.PanicLevel, // alert 2: log.PanicLevel, // crit @@ -57,15 +57,16 @@ func toLogEntry(msg []byte, from string) *log.Entry { if err != nil { return nil } - e := &log.Entry{ - Level: PriorityMap[prio], - Hostname: from, - Service: data.SyslogIdentifier, - Text: data.Message, - Fields: mapEntry, - } + entry := log.NewEntry(nil) + entry = entry.WithFields(mapEntry) + entry = entry.WithFields(log.Fields{ + "hostname": from, + "service": data.SyslogIdentifier, + }) if data.SystemdUnit == "" { - e.Service = data.SystemdUnit + entry = entry.WithField("service", data.SystemdUnit) } - return e + entry.Level = PriorityMap[prio] + entry.Message = data.Message + return entry } diff --git a/receive/journald_json/main.go b/receive/journald_json/main.go index 696c34d..b168fd6 100644 --- a/receive/journald_json/main.go +++ b/receive/journald_json/main.go @@ -3,11 +3,14 @@ package journald_json import ( "net" + log "github.com/sirupsen/logrus" + "github.com/genofire/logmania/lib" - "github.com/genofire/logmania/log" "github.com/genofire/logmania/receive" ) +var logger = log.WithField("receive", "journald_json") + type Receiver struct { receive.Receiver exportChannel chan *log.Entry @@ -19,7 +22,7 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece ln, err := net.ListenUDP(config.JournaldJSON.Type, addr) if err != nil { - log.Error("journald-json init ", err) + logger.Error("init ", err) return nil } recv := &Receiver{ @@ -27,7 +30,7 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece exportChannel: exportChannel, } - log.Info("journald-json init") + logger.Info("init") return recv } @@ -35,12 +38,12 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece const maxDataGramSize = 8192 func (rc *Receiver) Listen() { - log.Info("journald-json listen") + logger.Info("listen") for { buf := make([]byte, maxDataGramSize) n, src, err := rc.serverSocket.ReadFromUDP(buf) if err != nil { - log.Warn("failed to accept connection", err) + logger.Warn("failed to accept connection", err) continue } diff --git a/receive/main.go b/receive/main.go index 263cd26..0be1b04 100644 --- a/receive/main.go +++ b/receive/main.go @@ -2,7 +2,7 @@ package receive import ( "github.com/genofire/logmania/lib" - "github.com/genofire/logmania/log" + log "github.com/sirupsen/logrus" ) var Register = make(map[string]ReceiverInit) diff --git a/receive/syslog/internal.go b/receive/syslog/internal.go index e4b1e48..6a3a8a0 100644 --- a/receive/syslog/internal.go +++ b/receive/syslog/internal.go @@ -1,11 +1,12 @@ package syslog import ( + log "github.com/sirupsen/logrus" + libSyslog "github.com/genofire/logmania/lib/syslog" - "github.com/genofire/logmania/log" ) -var SyslogPriorityMap = map[int]log.LogLevel{ +var SyslogPriorityMap = map[int]log.Level{ 0: log.PanicLevel, 1: log.PanicLevel, 2: log.PanicLevel, @@ -19,9 +20,10 @@ var SyslogPriorityMap = map[int]log.LogLevel{ func toLogEntry(msg []byte, from string) *log.Entry { syslogMsg := libSyslog.Parse(msg) - return &log.Entry{ - Level: SyslogPriorityMap[syslogMsg.Severity], - Text: syslogMsg.Content, - Hostname: from, - } + entry := log.NewEntry(nil) + entry = entry.WithField("hostname", from) + entry.Time = syslogMsg.Timestemp + entry.Level = SyslogPriorityMap[syslogMsg.Severity] + entry.Message = syslogMsg.Content + return entry } diff --git a/receive/syslog/internal_test.go b/receive/syslog/internal_test.go new file mode 100644 index 0000000..ff52ebf --- /dev/null +++ b/receive/syslog/internal_test.go @@ -0,0 +1,21 @@ +package syslog + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + log "github.com/sirupsen/logrus" +) + +func TestToEntry(t *testing.T) { + assert := assert.New(t) + entry := toLogEntry([]byte("<11>Aug 17 11:43:33 Msg"), "::1") + assert.Equal("Msg", entry.Message) + assert.Equal(log.ErrorLevel, entry.Level) + + hostname, ok := entry.Data["hostname"] + assert.True(ok) + assert.Equal("::1", hostname) + +} diff --git a/receive/syslog/main.go b/receive/syslog/main.go index feb7382..0ef86be 100644 --- a/receive/syslog/main.go +++ b/receive/syslog/main.go @@ -3,11 +3,14 @@ package syslog import ( "net" + log "github.com/sirupsen/logrus" + "github.com/genofire/logmania/lib" - "github.com/genofire/logmania/log" "github.com/genofire/logmania/receive" ) +var logger = log.WithField("receive", "syslog") + type Receiver struct { receive.Receiver exportChannel chan *log.Entry @@ -19,7 +22,7 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece ln, err := net.ListenUDP(config.Syslog.Type, addr) if err != nil { - log.Error("syslog init ", err) + logger.Error("init ", err) return nil } recv := &Receiver{ @@ -27,7 +30,7 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece exportChannel: exportChannel, } - log.Info("syslog init") + logger.Info("init") return recv } @@ -35,18 +38,21 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece const maxDataGramSize = 8192 func (rc *Receiver) Listen() { - log.Info("syslog listen") + logger.Info("listen") for { buf := make([]byte, maxDataGramSize) n, src, err := rc.serverSocket.ReadFromUDP(buf) if err != nil { - log.Warn("failed to accept connection", err) + logger.Warn("failed to accept connection", err) continue } raw := make([]byte, n) copy(raw, buf) - rc.exportChannel <- toLogEntry(raw, src.IP.String()) + entry := toLogEntry(raw, src.IP.String()) + if entry != nil { + rc.exportChannel <- entry + } } }