switch logrus and cobra

This commit is contained in:
Martin Geno 2017-10-25 00:36:16 +02:00
parent f10a4f5918
commit 2851513cfb
No known key found for this signature in database
GPG Key ID: F0D39A37E925E941
31 changed files with 304 additions and 807 deletions

View File

@ -6,8 +6,7 @@ import (
"time" "time"
timeago "github.com/ararog/timeago" timeago "github.com/ararog/timeago"
log "github.com/sirupsen/logrus"
"github.com/genofire/logmania/log"
) )
type commandFunc func(func(string), string, []string) type commandFunc func(func(string), string, []string)
@ -156,11 +155,18 @@ func (b *Bot) setMaxfilter(answer func(string), from string, params []string) {
return return
} }
to := from to := from
max := log.NewLoglevel(params[0]) var max log.Level
var err error
if len(params) > 1 { if len(params) > 1 {
to = params[0] 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 b.state.MaxPrioIn[to] = max

View File

@ -9,7 +9,7 @@ dependencies:
- mkdir -p ~/.go_workspace/src/github.com/${CIRCLE_PROJECT_USERNAME} - 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} - ln -s ${HOME}/${CIRCLE_PROJECT_REPONAME} ${HOME}/.go_workspace/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}
- go get -t -d -v ./... - go get -t -d -v ./...
- go install github.com/genofire/logmania/cmd/logmania - go install github.com/genofire/logmania
post: post:
- cp ~/.go_workspace/bin/logmania logmania.bin - cp ~/.go_workspace/bin/logmania logmania.bin
- tar -cvzf logmania-builded.tar.gz logmania.bin logmania_example.conf - tar -cvzf logmania-builded.tar.gz logmania.bin logmania_example.conf

View File

@ -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)
}

22
cmd/root.go Normal file
View File

@ -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)
}
}

114
cmd/server.go Normal file
View File

@ -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")
}

View File

@ -5,7 +5,7 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/genofire/logmania/log" log "github.com/sirupsen/logrus"
) )
// Struct of the configuration // Struct of the configuration

View File

@ -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 TODO: detect other parts in content

View File

@ -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
}
*/

View File

@ -1 +0,0 @@
package client

View File

@ -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,
})
}

View File

@ -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))
}

View File

@ -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...)
}

View File

@ -1,9 +0,0 @@
package log
type loggerFunc func(*Entry)
var Save loggerFunc
func init() {
Save = func(*Entry) {}
}

View File

@ -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:]
}

View File

@ -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")
}

7
main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "github.com/genofire/logmania/cmd"
func main() {
cmd.Execute()
}

View File

@ -1,9 +1,10 @@
package all package all
import ( import (
log "github.com/sirupsen/logrus"
"github.com/genofire/logmania/bot" "github.com/genofire/logmania/bot"
"github.com/genofire/logmania/lib" "github.com/genofire/logmania/lib"
"github.com/genofire/logmania/log"
"github.com/genofire/logmania/notify" "github.com/genofire/logmania/notify"
configNotify "github.com/genofire/logmania/notify/config" 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() { func (n *Notifier) sender() {
for c := range n.channelNotify { for c := range n.channelNotify {
for _, item := range n.list { 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 n.channelNotify <- e
return nil
} }
func (n *Notifier) Close() { func (n *Notifier) Close() {
@ -50,3 +52,14 @@ func (n *Notifier) Close() {
item.Close() item.Close()
} }
} }
func (n *Notifier) Levels() []log.Level {
return []log.Level{
log.DebugLevel,
log.InfoLevel,
log.WarnLevel,
log.ErrorLevel,
log.FatalLevel,
log.PanicLevel,
}
}

View File

@ -1,6 +1,5 @@
package all package all
import ( import (
_ "github.com/genofire/logmania/notify/console"
_ "github.com/genofire/logmania/notify/xmpp" _ "github.com/genofire/logmania/notify/xmpp"
) )

View File

@ -2,12 +2,11 @@ package config
import ( import (
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"regexp" "regexp"
"time" "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" 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 { type NotifyState struct {
Hostname map[string]string `json:"hostname"` Hostname map[string]string `json:"hostname"`
HostTo map[string]map[string]bool `json:"host_to"` 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"` RegexIn map[string]map[string]*regexp.Regexp `json:"regexIn"`
Lastseen map[string]time.Time `json:"lastseen,omitempty"` Lastseen map[string]time.Time `json:"lastseen,omitempty"`
LastseenNotify map[string]time.Time `json:"-"` LastseenNotify map[string]time.Time `json:"-"`
} }
func (state *NotifyState) SendTo(e *log.Entry) []string { func (state *NotifyState) SendTo(e *log.Entry) []string {
if to, ok := state.HostTo[e.Hostname]; ok { hostname, ok := e.Data["hostname"].(string)
if e.Text != AlertMsg && e.Hostname != "" { if !ok {
state.Lastseen[e.Hostname] = time.Now() return nil
}
if to, ok := state.HostTo[hostname]; ok {
if e.Message != AlertMsg && hostname != "" {
state.Lastseen[hostname] = time.Now()
} }
var toList []string var toList []string
for toEntry, _ := range to { for toEntry, _ := range to {
if lvl := state.MaxPrioIn[toEntry]; e.Level < lvl { if lvl := state.MaxPrioIn[toEntry]; e.Level >= lvl {
continue continue
} }
if regex, ok := state.RegexIn[toEntry]; ok { if regex, ok := state.RegexIn[toEntry]; ok {
stopForTo := false stopForTo := false
for _, expr := range regex { for _, expr := range regex {
if expr.MatchString(e.Text) { if expr.MatchString(e.Message) {
stopForTo = true stopForTo = true
continue continue
} }
@ -45,12 +48,12 @@ func (state *NotifyState) SendTo(e *log.Entry) []string {
} }
toList = append(toList, toEntry) toList = append(toList, toEntry)
} }
if hostname, ok := state.Hostname[e.Hostname]; ok { if replaceHostname, ok := state.Hostname[hostname]; ok {
e.Hostname = hostname e.WithField("hostname", replaceHostname)
} }
return toList return toList
} else { } else {
state.HostTo[e.Hostname] = make(map[string]bool) state.HostTo[hostname] = make(map[string]bool)
} }
return nil return nil
} }
@ -71,7 +74,7 @@ func ReadStateFile(path string) *NotifyState {
var state NotifyState var state NotifyState
if f, err := os.Open(path); err == nil { // transform data to legacy meshviewer if f, err := os.Open(path); err == nil { // transform data to legacy meshviewer
if err = json.NewDecoder(f).Decode(&state); err == nil { 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 { if state.Lastseen == nil {
state.Lastseen = make(map[string]time.Time) state.Lastseen = make(map[string]time.Time)
} }
@ -89,15 +92,15 @@ func ReadStateFile(path string) *NotifyState {
} }
return &state return &state
} else { } else {
fmt.Println("failed to unmarshal nodes:", err) log.Error("failed to unmarshal nodes:", err)
} }
} else { } else {
fmt.Println("failed to open state notify file: ", path, ":", err) log.Error("failed to open state notify file: ", path, ":", err)
} }
return &NotifyState{ return &NotifyState{
Hostname: make(map[string]string), Hostname: make(map[string]string),
HostTo: make(map[string]map[string]bool), 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), RegexIn: make(map[string]map[string]*regexp.Regexp),
Lastseen: make(map[string]time.Time), Lastseen: make(map[string]time.Time),
LastseenNotify: 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) c := time.Tick(time.Minute)
for range c { 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 time.Before(now.Add(expired * -2)) {
if timeNotify, ok := state.LastseenNotify[host]; !ok || !time.Before(timeNotify) { if timeNotify, ok := state.LastseenNotify[host]; !ok || !time.Before(timeNotify) {
state.LastseenNotify[host] = now state.LastseenNotify[host] = now
send(&log.Entry{ entry := log.NewEntry(log.New())
Hostname: host, entry.Level = log.ErrorLevel
Level: log.ErrorLevel, entry.Message = AlertMsg
Text: AlertMsg, entry.WithField("hostname", host)
}) send(entry)
} }
} }
} }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -1,16 +1,18 @@
package notify package notify
import ( import (
log "github.com/sirupsen/logrus"
"github.com/genofire/logmania/bot" "github.com/genofire/logmania/bot"
"github.com/genofire/logmania/lib" "github.com/genofire/logmania/lib"
"github.com/genofire/logmania/log"
configNotify "github.com/genofire/logmania/notify/config" configNotify "github.com/genofire/logmania/notify/config"
) )
var NotifyRegister []NotifyInit var NotifyRegister []NotifyInit
type Notifier interface { type Notifier interface {
Send(entry *log.Entry) Fire(entry *log.Entry) error
Levels() []log.Level
Close() Close()
} }

View File

@ -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)
}

View File

@ -1,22 +1,26 @@
package xmpp package xmpp
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
xmpp "github.com/mattn/go-xmpp" xmpp "github.com/mattn/go-xmpp"
log "github.com/sirupsen/logrus"
"github.com/genofire/logmania/bot" "github.com/genofire/logmania/bot"
"github.com/genofire/logmania/lib" "github.com/genofire/logmania/lib"
"github.com/genofire/logmania/log"
"github.com/genofire/logmania/notify" "github.com/genofire/logmania/notify"
configNotify "github.com/genofire/logmania/notify/config" configNotify "github.com/genofire/logmania/notify/config"
) )
var logger = log.WithField("notify", "xmpp")
type Notifier struct { type Notifier struct {
notify.Notifier notify.Notifier
client *xmpp.Client client *xmpp.Client
state *configNotify.NotifyState state *configNotify.NotifyState
formatter *log.TextFormatter
} }
func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bot) notify.Notifier { 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() client, err := options.NewClient()
if err != nil { if err != nil {
logger.Error(err)
return nil return nil
} }
go func() { go func() {
for { for {
chat, err := client.Recv() chat, err := client.Recv()
if err != nil { if err != nil {
log.Warn(err) logger.Warn(err)
} }
switch v := chat.(type) { switch v := chat.(type) {
case xmpp.Chat: case xmpp.Chat:
@ -48,30 +53,52 @@ func Init(config *lib.NotifyConfig, state *configNotify.NotifyState, bot *bot.Bo
} }
} }
}() }()
log.Info("xmpp startup") logger.Info("startup")
return &Notifier{client: client, state: state} 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) to := n.state.SendTo(e)
if to == nil { 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 { for _, toAddr := range to {
to := strings.TrimPrefix(toAddr, "xmpp:") to := strings.TrimPrefix(toAddr, "xmpp:")
if strings.Contains(toAddr, "conference") || strings.Contains(toAddr, "irc") { if strings.Contains(toAddr, "conference") || strings.Contains(toAddr, "irc") {
n.client.JoinMUCNoHistory(to, "logmania") 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 { if err != nil {
fmt.Println("xmpp to ", to, " error:", err) logger.Error("xmpp to ", to, " error:", err)
} }
} else { } 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 { 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() {} func (n *Notifier) Close() {}

View File

@ -1,8 +1,9 @@
package all package all
import ( import (
log "github.com/sirupsen/logrus"
"github.com/genofire/logmania/lib" "github.com/genofire/logmania/lib"
"github.com/genofire/logmania/log"
"github.com/genofire/logmania/receive" "github.com/genofire/logmania/receive"
) )

View File

@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"strconv" "strconv"
"github.com/genofire/logmania/log" log "github.com/sirupsen/logrus"
) )
type JournalMessage struct { type JournalMessage struct {
@ -34,7 +34,7 @@ type JournalMessage struct {
Message string `json:"MESSAGE"` Message string `json:"MESSAGE"`
} }
var PriorityMap = map[int]log.LogLevel{ var PriorityMap = map[int]log.Level{
0: log.PanicLevel, // emerg 0: log.PanicLevel, // emerg
1: log.PanicLevel, // alert 1: log.PanicLevel, // alert
2: log.PanicLevel, // crit 2: log.PanicLevel, // crit
@ -57,15 +57,16 @@ func toLogEntry(msg []byte, from string) *log.Entry {
if err != nil { if err != nil {
return nil return nil
} }
e := &log.Entry{ entry := log.NewEntry(nil)
Level: PriorityMap[prio], entry = entry.WithFields(mapEntry)
Hostname: from, entry = entry.WithFields(log.Fields{
Service: data.SyslogIdentifier, "hostname": from,
Text: data.Message, "service": data.SyslogIdentifier,
Fields: mapEntry, })
}
if data.SystemdUnit == "" { 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
} }

View File

@ -3,11 +3,14 @@ package journald_json
import ( import (
"net" "net"
log "github.com/sirupsen/logrus"
"github.com/genofire/logmania/lib" "github.com/genofire/logmania/lib"
"github.com/genofire/logmania/log"
"github.com/genofire/logmania/receive" "github.com/genofire/logmania/receive"
) )
var logger = log.WithField("receive", "journald_json")
type Receiver struct { type Receiver struct {
receive.Receiver receive.Receiver
exportChannel chan *log.Entry 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) ln, err := net.ListenUDP(config.JournaldJSON.Type, addr)
if err != nil { if err != nil {
log.Error("journald-json init ", err) logger.Error("init ", err)
return nil return nil
} }
recv := &Receiver{ recv := &Receiver{
@ -27,7 +30,7 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece
exportChannel: exportChannel, exportChannel: exportChannel,
} }
log.Info("journald-json init") logger.Info("init")
return recv return recv
} }
@ -35,12 +38,12 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece
const maxDataGramSize = 8192 const maxDataGramSize = 8192
func (rc *Receiver) Listen() { func (rc *Receiver) Listen() {
log.Info("journald-json listen") logger.Info("listen")
for { for {
buf := make([]byte, maxDataGramSize) buf := make([]byte, maxDataGramSize)
n, src, err := rc.serverSocket.ReadFromUDP(buf) n, src, err := rc.serverSocket.ReadFromUDP(buf)
if err != nil { if err != nil {
log.Warn("failed to accept connection", err) logger.Warn("failed to accept connection", err)
continue continue
} }

View File

@ -2,7 +2,7 @@ package receive
import ( import (
"github.com/genofire/logmania/lib" "github.com/genofire/logmania/lib"
"github.com/genofire/logmania/log" log "github.com/sirupsen/logrus"
) )
var Register = make(map[string]ReceiverInit) var Register = make(map[string]ReceiverInit)

View File

@ -1,11 +1,12 @@
package syslog package syslog
import ( import (
log "github.com/sirupsen/logrus"
libSyslog "github.com/genofire/logmania/lib/syslog" 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, 0: log.PanicLevel,
1: log.PanicLevel, 1: log.PanicLevel,
2: log.PanicLevel, 2: log.PanicLevel,
@ -19,9 +20,10 @@ var SyslogPriorityMap = map[int]log.LogLevel{
func toLogEntry(msg []byte, from string) *log.Entry { func toLogEntry(msg []byte, from string) *log.Entry {
syslogMsg := libSyslog.Parse(msg) syslogMsg := libSyslog.Parse(msg)
return &log.Entry{ entry := log.NewEntry(nil)
Level: SyslogPriorityMap[syslogMsg.Severity], entry = entry.WithField("hostname", from)
Text: syslogMsg.Content, entry.Time = syslogMsg.Timestemp
Hostname: from, entry.Level = SyslogPriorityMap[syslogMsg.Severity]
} entry.Message = syslogMsg.Content
return entry
} }

View File

@ -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)
}

View File

@ -3,11 +3,14 @@ package syslog
import ( import (
"net" "net"
log "github.com/sirupsen/logrus"
"github.com/genofire/logmania/lib" "github.com/genofire/logmania/lib"
"github.com/genofire/logmania/log"
"github.com/genofire/logmania/receive" "github.com/genofire/logmania/receive"
) )
var logger = log.WithField("receive", "syslog")
type Receiver struct { type Receiver struct {
receive.Receiver receive.Receiver
exportChannel chan *log.Entry 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) ln, err := net.ListenUDP(config.Syslog.Type, addr)
if err != nil { if err != nil {
log.Error("syslog init ", err) logger.Error("init ", err)
return nil return nil
} }
recv := &Receiver{ recv := &Receiver{
@ -27,7 +30,7 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece
exportChannel: exportChannel, exportChannel: exportChannel,
} }
log.Info("syslog init") logger.Info("init")
return recv return recv
} }
@ -35,18 +38,21 @@ func Init(config *lib.ReceiveConfig, exportChannel chan *log.Entry) receive.Rece
const maxDataGramSize = 8192 const maxDataGramSize = 8192
func (rc *Receiver) Listen() { func (rc *Receiver) Listen() {
log.Info("syslog listen") logger.Info("listen")
for { for {
buf := make([]byte, maxDataGramSize) buf := make([]byte, maxDataGramSize)
n, src, err := rc.serverSocket.ReadFromUDP(buf) n, src, err := rc.serverSocket.ReadFromUDP(buf)
if err != nil { if err != nil {
log.Warn("failed to accept connection", err) logger.Warn("failed to accept connection", err)
continue continue
} }
raw := make([]byte, n) raw := make([]byte, n)
copy(raw, buf) copy(raw, buf)
rc.exportChannel <- toLogEntry(raw, src.IP.String()) entry := toLogEntry(raw, src.IP.String())
if entry != nil {
rc.exportChannel <- entry
}
} }
} }